এই পর্বে যা পাবেন
- কাগজে-কলমে Java কোড চালানোর (dry run) পদ্ধতি — ভ্যারিয়েবলের টেবিল, call stack আর class ও object তৈরির ধাপ একসাথে
- কোন overload, কোন override, কোন field আর কোন initializer চলবে — তা ঠিক করার নিয়মগুলো এক জায়গায়
- মডিউল ১ থেকে ৭ পর্যন্ত ৬৭টি প্রশ্ন — সহজ থেকে কঠিন, প্রতিটির পূর্ণ ব্যাখ্যা আর কোন পর্ব ঝালাবেন তার লিংক
- compile error, runtime exception আর “নিশ্চিত নয়” — তিনটি “অদ্ভুত” উত্তর আলাদা করে চেনা ও কারণসহ লেখা
- AI-কে দিয়ে নতুন প্রশ্ন বানানো, আর তার উত্তর JDK-তে চালিয়ে যাচাইয়ের নিয়ম
১ · মডিউল ৮ শুরু — পরীক্ষা ও ইন্টারভিউর প্রস্তুতি
অভিনন্দন! ৫৫টি পর্বে আপনি Java-র টাইপ আর অপারেটর থেকে শুরু করে class, record, sealed type, generics, collection, stream, thread, JVM, Spring Boot, JPA, Spring Security আর একটি পূর্ণ ক্যাপস্টোন প্রজেক্ট পর্যন্ত এসেছেন। এবার শেখা জিনিস এমনভাবে ঝালিয়ে নেওয়ার পালা, যাতে পরীক্ষার হলে বা ইন্টারভিউর টেবিলে চাপের মধ্যেও ঠিক উত্তর মাথায় আসে।
এই শেষ মডিউলে নতুন কোনো বিষয় নেই। আছে চারটি অনুশীলনের পর্ব, প্রতিটি আলাদা ধরনের প্রশ্নের জন্য:
পর্ব ৫৬ · আউটপুট কী হবে?
এই পর্ব। ছোট কোড দেখে বলতে হবে কী ছাপা হবে — বা আদৌ compile হবে কি না, অথবা কোন exception উঠবে। লিখিত পরীক্ষা, MCQ আর টেকনিক্যাল ইন্টারভিউ, তিন জায়গাতেই আসে।
কৌশল: trace tableপর্ব ৫৭ · MCQ প্রশ্নব্যাংক
ধারণা, সংজ্ঞা আর তথ্যের দ্রুত প্রশ্ন — keyword, JVM, collection-এর জটিলতা, exception-এর শ্রেণি। চাকরির প্রাথমিক বাছাই পরীক্ষায় এ ধরনের প্রশ্ন প্রায়ই আসে।
কৌশল: সময় ধরে চর্চাপর্ব ৫৮ · লিখিত ও ভাইভা
“পার্থক্য লিখুন”, “উদাহরণসহ ব্যাখ্যা করুন”, “class লিখুন” আর মুখোমুখি প্রশ্নের উত্তর কীভাবে সাজাবেন।
কৌশল: উত্তরের কাঠামোপর্ব ৫৯ · কোডিং ইন্টারভিউ
সমস্যা শুনে প্রশ্ন করা, পরিকল্পনা বলা, Java-তে কোড লেখা, টেস্ট করা — সফটওয়্যার কোম্পানির ইন্টারভিউর পুরো ধারা।
কৌশল: জোরে ভাবা“আউটপুট কী হবে?” প্রশ্ন এত বেশি আসে কেন
প্রশ্নকর্তা আসলে জানতে চান: আপনি কি compiler আর JVM-এর মতো করে কোড পড়তে পারেন? Java-তে অনেক কাজ চোখের আড়ালে ঘটে — new লিখলে আগে class initialize হয়, constructor-এর প্রথম লাইনে না লিখলেও super() চলে, compiler নিজে overload বেছে নেয়, autoboxing নিজে থেকে object বানায়, আর stream কোনো terminal operation না পেলে কিছুই করে না। যিনি এই অদৃশ্য কাজগুলো দেখতে পান, তিনিই বাগ খুঁজে পান, AI-এর লেখা কোড যাচাই করতে পারেন, আর code review-তে কাজের মন্তব্য দিতে পারেন।
| আপনার লক্ষ্য | শুরু করুন | তারপর |
|---|---|---|
| বিশ্ববিদ্যালয়ের লিখিত পরীক্ষা | পর্ব ৫৬ (আউটপুট) ও ৫৮ (লিখিত) | পর্ব ৫৭ দিয়ে দ্রুত ঝালাই |
| সরকারি বা ব্যাংকের IT/প্রোগ্রামার পদের পরীক্ষা | পর্ব ৫৭ (MCQ) ও ৫৬ | পর্ব ৫৮-এর লিখিত ও ভাইভা অংশ |
| সফটওয়্যার কোম্পানির ইন্টারভিউ | পর্ব ৫৯ (কোডিং) | পর্ব ৫৬ — অনেক টেকনিক্যাল রাউন্ড “এই কোড কী ছাপবে?” দিয়েই শুরু হয় |
| শুধু নিজের শেখা যাচাই | এই পর্ব, প্রথম থেকে শেষ | যে ভাগে বেশি ভুল, সেই পর্বে ফিরে যান |
এই পাতা কীভাবে ব্যবহার করবেন
- প্রথম দফা — অপশন ঢেকে রাখুন। প্রতিটি কোড পড়ে খাতায় নিজের উত্তর লিখুন, তারপর অপশনে চাপুন। প্রথম চেষ্টায় কতগুলো ঠিক হলো, ৮ নম্বর ভাগের শেষে স্কোর দেখাবে।
- দ্বিতীয় দফা — ভুলগুলোর কারণ খুঁজুন। প্রতিটি ভুল অপশনের ব্যাখ্যা পড়ুন। প্রতিটি ভাগের শুরুতে লেখা আছে কোন পর্বগুলো ঝালাতে হবে।
- তৃতীয় দফা — নিজে চালান। প্রতিটি ভাগের “নিজে চালিয়ে দেখুন” কোড চালান। একটি লাইন বদলে আগে ফল অনুমান করুন, তারপর চালিয়ে মেলান।
সব উত্তর OpenJDK 21-এর জন্য। কোডের টুকরোতে class বা main না থাকলে ধরে নিন লাইনগুলো class Main-এর main method-এর ভেতরে, আর দরকারি import (java.util.*, java.util.stream.*, java.time.*, java.nio.file.*) দেওয়া আছে। Method বা আলাদা class থাকলে সেগুলো Main-এর পাশে বা ভেতরে, যেমন দেখানো। “নিজে চালিয়ে দেখুন” কোডে main class public ছাড়া লেখা, কারণ অনলাইন রানার ফাইলটিকে অন্য নামে compile করে; নিজের প্রজেক্টে সাধারণত Main.java ফাইলে public class Main লিখবেন।
C ও C++-এ অনেক আউটপুট প্রশ্নের উত্তর “undefined behavior” বা “unspecified”, কারণ সেখানে operand-এর মূল্যায়নের ক্রম আর int-এর আকার সবসময় নির্দিষ্ট নয়। Java ইচ্ছা করে সব নির্দিষ্ট করেছে: বাঁ থেকে ডানে মূল্যায়ন, int সবসময় 32 বিট, ভুল index-এ নিশ্চিত exception — তাই Java-তে i = i++ + ++i-এরও একটিই সঠিক উত্তর। Python থেকে এলে সাবধান: Python-এর -7 // 2 হলো -4, Java-র -7 / 2 হলো -3; আর Python-এর int কখনো overflow করে না। তুলনার জন্য দেখুন C++-এর আউটপুট প্রশ্ন ও Python-এর আউটপুট প্রশ্ন।
পরীক্ষা ও ইন্টারভিউতে এ ধরনের প্রশ্ন সাধারণত এই চেহারায় আসে:
- “নিচের প্রোগ্রামের আউটপুট লিখুন” — খাতায় trace table এঁকে দেখান; শুধু উত্তর নয়, ধাপগুলোও নম্বর পায়।
- “static block, instance block আর constructor কোন ক্রমে চলবে?” — প্রথমে class initialization, তারপর object তৈরির ধাপ আলাদা করে লিখুন।
- MCQ-তে চারটি আউটপুট থেকে বেছে নেওয়া — একটি অপশনে প্রায়ই “Compile error” বা “Runtime exception” থাকে; আগে নিজে হিসাব করুন, তারপর অপশন দেখুন।
- ইন্টারভিউতে “এই কোডে কী সমস্যা?” — String-এ
==, equals ছাড়া hashCode, লুপের ভেতরে list থেকে মোছা — এগুলো আগে বলুন; এটাই অভিজ্ঞতার লক্ষণ।
২ · Java কোড মাথায় চালানোর পদ্ধতি
আউটপুট বলার একটাই নির্ভরযোগ্য উপায়: JVM যেভাবে চালায়, ঠিক সেভাবে এক লাইন এক লাইন করে চালানো, আর প্রতিটি পরিবর্তন লিখে রাখা। একে বলে dry run, আর যে টেবিলে লেখেন সেটি trace table. Java-তে টেবিলে তিনটি জিনিস রাখতে হয়: ভ্যারিয়েবলের মান, কোন method কাকে ডেকেছে (call stack), আর কোন class এর মধ্যেই initialize হয়ে গেছে।
ঢাকার একটি গার্মেন্টস কারখানার কথা ভাবুন। নতুন লাইন চালুর দিন একবারই মেশিন বসানো হয় (class initialization — static block). তারপর প্রতিটি শার্ট তৈরির সময় একই ক্রমে কাজ চলে: আগে মূল কাপড় কাটা (superclass-এর অংশ), তারপর কলার আর বোতাম (subclass-এর অংশ)। দ্বিতীয় শার্টের দিন মেশিন আর বসাতে হয় না — শুধু শার্ট তৈরির ধাপগুলো আবার চলে। Java-র new-ও ঠিক এভাবে কাজ করে।
Java-র trace table-এর ছয়টি নিয়ম
- প্রতিটি ভ্যারিয়েবলের জন্য একটি কলাম, আর একটি কলাম আউটপুটের জন্য।
staticfield একটি class-এ একটাই, তাই তার কলামও একটা — সব object সেটাই ভাগ করে নেয়। - প্রতিটি এক্সপ্রেশন বাঁ থেকে ডানে। Java-র নিয়ম (JLS §15.7): বাঁ পাশের operand আগে পুরো হিসাব হয়, তারপর ডান পাশের; method-এর argument-ও বাঁ থেকে ডানে। Precedence শুধু বলে কোন অপারেটর কোন operand নেবে, কোনটা আগে চলবে তা নয়।
- প্রতিটি
+-এ জিজ্ঞেস করুন: সংখ্যা যোগ, নাকি String জোড়া? বাঁ থেকে প্রথম String পাওয়া পর্যন্ত সংখ্যা যোগ হয়; তারপর সবকিছু String-এ জোড়া লাগে। - প্রতিটি
new-এ দুটি ধাপ। class প্রথমবার ব্যবহার হলে আগে তার initialization (superclass আগে, তারপর subclass-এরstaticfield ও block, লেখার ক্রমে) — এটা একবারই। তারপর object: সব field ডিফল্ট মান (0, null, false) পায়, constructor-এরsuper(...)চলে, তারপর এই class-এর instance field ও block লেখার ক্রমে, শেষে constructor-এর বাকি শরীর। - প্রতিটি method ডাকে জিজ্ঞেস করুন: কোন method? Overload বাছাই হয় compile-এর সময়, variable-এর ঘোষিত টাইপ দেখে; override বাছাই হয় চলার সময়, object-এর আসল টাইপ দেখে (dynamic dispatch). Field আর
staticmethod-এ dynamic dispatch নেই। - Exception উঠলে call stack ধরে পেছনে হাঁটুন। প্রতিটি স্তরে আগে
finallyচলে, তারপর ওপরের স্তরে মেলানোcatchখোঁজা হয়। কিছুই না মিললে প্রোগ্রাম থামে আর stack trace ছাপে — ততক্ষণে যা ছাপা হয়েছে, সেটুকু আউটপুটে থেকে যায়।
নিচের ডায়াগ্রামে চতুর্থ নিয়মটি একটি পূর্ণ প্রোগ্রামে চালানো হয়েছে — পরীক্ষায় সবচেয়ে বেশি আসা “static block, instance block আর constructor-এর ক্রম” প্রশ্ন, inheritance-সহ। “পরে” চাপার আগে নিজে পুরো আউটপুট লিখে নিন — দ্বিতীয় new B()-এ কোন সংখ্যাগুলো আর ছাপা হবে না?
1class A {2 static { print("1 "); }3 { print("2 "); }4 A() { print("3 "); }5}6class B extends A {7 static { print("4 "); }8 { print("5 "); }9 B() { print("6 "); }10}11// main:12print("main ");13new B();14print("/ ");15new B();
| A initialized? | |
| B initialized? | |
| object | |
| call stack |
- লাইন ১২ — main শুরু।
Mainclass-এ কোনো static block নেই, তাই প্রথমেই ছাপা হয় main. A বা B এখনো initialize হয়নি — Java class initialize করে প্রথম সক্রিয় ব্যবহারের সময়, প্রোগ্রাম শুরুতে নয়। - লাইন ১৩ — new B(), কিন্তু আগে class. B-কে initialize করার আগে তার superclass A-কে initialize করতে হয় — JLS §12.4.2-এর নিয়ম। A-এর static block চলে: 1.
- B-এর static block: 4. এবার দুটি class-ই প্রস্তুত। লক্ষ করুন, এখনো কোনো object তৈরি হয়নি — static block দুটি class-এর, object-এর নয়।
- object তৈরি — B() ডাকে A(). মেমরিতে একটি B object বসল, সব field ডিফল্ট মানে।
B()-এর প্রথম কাজ লুকানোsuper(), তাইA()চলে। A()-এর শরীরের আগে A-এর instance block: 2. - A()-এর শরীর: 3. superclass-এর অংশ এখন তৈরি। call stack-এ A() শেষ, নিয়ন্ত্রণ ফিরল B()-তে।
- super() শেষ, এবার B-এর instance block: 5. Instance initializer আর field initializer চলে
super()-এর ঠিক পরে, constructor-এর বাকি শরীরের আগে। তাই এটি B() লাইনের আগে ছাপা হয়। - B()-এর শরীর: 6. প্রথম object তৈরি শেষ। এই পর্যন্ত আউটপুট: main 1 4 2 3 5 6.
- লাইন ১৪: ছাপা হয় / আর একটি space.
- লাইন ১৫ — দ্বিতীয় new B(). দুটি class আগেই initialize হয়েছে, তাই 1 আর 4 আর ছাপা হয় না। শুধু object তৈরির চারটি ধাপ: 2 3 5 6. পুরো আউটপুট: main 1 4 2 3 5 6 / 2 3 5 6. নিয়মটি এক লাইনে: static একবার (superclass আগে), instance প্রতিবার (superclass-এর block ও constructor আগে)।
নিচের কোডটি হুবহু ডায়াগ্রামের প্রোগ্রাম। চালানোর আগে একটি পরিবর্তনের ফল অনুমান করুন: main-এ new B()-এর বদলে শুধু new A() দুইবার লিখলে কী ছাপা হবে?
class A {
static { System.out.print("1 "); }
{ System.out.print("2 "); }
A() { System.out.print("3 "); }
}
class B extends A {
static { System.out.print("4 "); }
{ System.out.print("5 "); }
B() { System.out.print("6 "); }
}
class Main {
public static void main(String[] args) {
System.out.print("main ");
new B();
System.out.print("/ ");
new B();
System.out.println();
}
}আউটপুট: main 1 4 2 3 5 6 / 2 3 5 6. শুধু new A() দুইবার লিখলে B কখনো ব্যবহারই হয় না, তাই B initialize হয় না: main 1 2 3 / 2 3. এই কারণেই বলা হয় static block “class লোড হওয়ার সময়” নয়, বরং “class প্রথম সক্রিয়ভাবে ব্যবহারের ঠিক আগে” চলে। ঝালিয়ে নিন: পর্ব ১৬ · constructor আর this, পর্ব ১৮ · static আর final, পর্ব ১৯ · inheritance.
ক্রমএকটি প্রোগ্রামে class Dog extends Animal, দুটি class-এরই static block, instance block আর constructor আছে, আর এর আগে কোনোটিই ব্যবহার হয়নি। প্রথম new Dog()-এ ঘটনাগুলো কোন ক্রমে ঘটবে? (▲▼ চেপে সাজান)
- Animal-এর static block
- Dog-এর static block
- Animal-এর instance block
- Animal-এর constructor-এর শরীর
- Dog-এর instance block
- Dog-এর constructor-এর শরীর
চার রকম উত্তর হতে পারে
পরীক্ষার্থীরা ধরে নেন প্রতিটি কোড একটি নির্দিষ্ট লেখা ছাপবে। বাস্তবে উত্তর চার রকম, আর ভালো প্রশ্নকর্তা ঠিক এটাই যাচাই করেন। C বা C++-এর মতো “undefined behavior” Java-তে নেই — নিয়ম ভাঙলে Java হয় compile করতে অস্বীকার করে, নয়তো একটি নির্দিষ্ট exception ছোড়ে।
✅ নির্দিষ্ট আউটপুট
Java-র নিয়ম ফল পুরোপুরি ঠিক করে দেয়। যেকোনো সঠিক JVM-এ একই লেখা আসবে। বেশিরভাগ প্রশ্ন এই ধরনের।
যেমন: -7 / 2 হলো -3⛔ Compile error
javac প্রোগ্রাম তৈরিই করে না, তাই কিছুই ছাপা হয় না — প্রথম লাইনের println-ও না। কোন error, সেটাও বলুন: possible lossy conversion, ambiguous, private access, effectively final.
যেমন: byte b = b + 5;⚠️ কিছু আউটপুট, তারপর exception
প্রোগ্রাম চলতে চলতে একটি লাইনে থামে। আগের ছাপা থাকে, পরেরটা আসে না। exception-এর ঠিক নাম লিখুন: ArithmeticException, NullPointerException, ConcurrentModificationException…
যেমন: 1 / 0🔀 ক্রম নিশ্চিত নয়
HashSet বা Set.of-এর ছাপার ক্রম, আর একাধিক thread একসাথে ছাপলে লাইনের ক্রম — Java এগুলোর কোনো নিশ্চয়তা দেয় না। “নিশ্চিত নয়, কারণ…” লেখাই সঠিক উত্তর।
(মাঝারি) একই প্রোগ্রাম দুইবার চালানো হলো, কোড একটুও বদলানো হয়নি। OpenJDK 21-এ কী ছাপা হবে?
System.out.println(Set.of("dhaka", "khulna", "sylhet", "rajshahi"));
[dhaka, khulna, sylhet, rajshahi]— প্রতিবার[dhaka, khulna, rajshahi, sylhet]— প্রতিবার- চারটির মধ্যে যেকোনো তিনটি
- চারটি শহরই, কিন্তু ক্রম নিশ্চিত নয় — দুই রানে আলাদা হতে পারে
৩ · ভিত্তি — টাইপ, অপারেটর আর String
এই ভাগের কোড ছোট, কিন্তু ফাঁদ গভীর। প্রতিটি এক্সপ্রেশনে তিনটি প্রশ্ন করুন: হিসাব কোন টাইপে হচ্ছে (numeric promotion)? + কি যোগ, নাকি String জোড়া? আর == কি মান তুলনা করছে, নাকি দুটি reference একই object কি না (reference equality)? মনে রাখুন, বাঁ পাশের টাইপ ডান পাশের হিসাব বদলায় না — long x = max + 1;-এ যোগটা আগে int-এ হয়ে যায়।
ঝালিয়ে নিন: পর্ব ০৪ · প্রথম প্রোগ্রাম, পর্ব ০৬ · টাইপ ও ভ্যারিয়েবল, পর্ব ০৭ · অপারেটর, পর্ব ০৮ · String ও StringBuilder, পর্ব ০৯ · ইনপুট-আউটপুট. অপশনে একাধিক লাইনের আউটপুট “ · ” চিহ্ন দিয়ে আলাদা করে দেখানো হয়েছে।
(সহজ) ঋণাত্মক সংখ্যায় ভাগ আর ভাগশেষ — কী ছাপা হবে?
System.out.println(7 / 2 + " " + (-7 / 2) + " " + (-7 % 3) + " " + (7 % -3));
3 -4 2 -23 -3 -1 13 -3 -1 -13.5 -3 -1 1
(মাঝারি) char-এর সাথে সংখ্যা — তিন লাইনে কী ছাপা হবে?
char c = 'a';
System.out.println(c + 1);
System.out.println((char) (c + 1));
System.out.println("" + c + 1);
b·b·b98·b·b98·b·a198·98·a1
(মাঝারি) + কখন যোগ, কখন জোড়া — চার লাইনে কী ছাপা হবে?
System.out.println(1 + 2 + "3");
System.out.println("1" + 2 + 3);
System.out.println("1" + (2 + 3));
System.out.println('a' + 'b' + "c");
33·123·15·195c123·123·15·abc33·15·15·195c33·123·15·abc
(মাঝারি) এক এক্সপ্রেশনে i++ আর ++i — কী ছাপা হবে?
int i = 5;
int a = i++ + ++i;
System.out.println(a + " " + i);
11 612 711 7- Compile error
(কঠিন) লুপে তিনবার i = i++ — কী ছাপা হবে?
int i = 0;
for (int k = 0; k < 3; k++) {
i = i++;
}
System.out.println(i);
031- Compile error
(মাঝারি) byte-এ যোগ — কী ঘটবে?
byte b = 10;
b += 5; // লাইন A
b = b + 5; // লাইন B
System.out.println(b);
20- লাইন A-তে compile error
- লাইন B-তে compile error: possible lossy conversion from int to byte
- ArithmeticException
(মাঝারি) Overflow আর long — তিন লাইনে কী ছাপা হবে?
int max = Integer.MAX_VALUE;
System.out.println(max + 1);
System.out.println(Math.abs(Integer.MIN_VALUE));
long x = max + 1;
System.out.println(x);
- প্রথম লাইনেই ArithmeticException
-2147483648·2147483648·2147483648-2147483648·-2147483648·2147483648-2147483648·-2147483648·-2147483648
(কঠিন) cast আর Math.round — তিন লাইনে কী ছাপা হবে?
System.out.println((int) 3.99 + " " + (int) -3.99);
System.out.println(Math.round(2.5) + " " + Math.round(-2.5));
System.out.println((byte) 200);
4 -4·3 -3·2003 -3·3 -3·-563 -3·3 -2·-563 -3·3 -2· Compile error
(সহজ) 0.1 + 0.2 — দুই লাইনে কী ছাপা হবে?
System.out.println(0.1 + 0.2);
System.out.println(0.1 + 0.2 == 0.3);
0.3·true0.30000000000000004·false0.3·false0.30000000000000004·true
(মাঝারি) && বনাম &, পার্শ্বপ্রতিক্রিয়াসহ — দুই লাইনে কী ছাপা হবে?
int x = 0;
boolean r = (x > 0) && (++x > 0);
System.out.println(r + " " + x);
r = (x > 0) & (++x > 0);
System.out.println(r + " " + x);
false 0·false 1false 1·false 2false 0·false 0- দ্বিতীয় লাইনে compile error
(মাঝারি) String-এ == — কী ছাপা হবে?
String a = "java";
String b = "java";
String c = new String("java");
String d = "ja" + "va";
String e = "ja";
String f = e + "va";
System.out.println((a == b) + " " + (a == c) + " " + (a == d)
+ " " + (a == f) + " " + (a == f.intern()));
true true true true truetrue false false false truetrue false true false truetrue false true false false
(সহজ) String বদলায় না, StringBuilder বদলায় — দুই লাইনে কী ছাপা হবে?
String s = "dhaka";
s.toUpperCase();
s.concat("!");
System.out.println(s);
StringBuilder sb = new StringBuilder("dhaka");
sb.append("!").reverse();
System.out.println(sb);
DHAKA!·!akahddhaka·!akahddhaka·dhaka!dhaka·dhaka
(কঠিন) StringBuilder-এ insert, আর argument কখন হিসাব হয় — কী ছাপা হবে?
StringBuilder sb = new StringBuilder("abc");
sb.insert(1, "XY").append(sb.length()).deleteCharAt(0);
System.out.println(sb);
XYbc3Ybc5aXYbc5XYbc5
(মাঝারি) Integer-এ ==, ১২৭ আর ১২৮ — কী ছাপা হবে?
Integer a = 127, b = 127;
Integer c = 128, d = 128;
System.out.println((a == b) + " " + (c == d) + " " + c.equals(d));
true false truetrue true truefalse false truetrue false false
(মাঝারি) Text block-এর indentation — কী ছাপা হবে?
String t = """
Hello
Dhaka
""";
System.out.println(t.replace("\n", "|"));
Hello|Dhaka||Hello| Dhaka|Hello| Dhaka|Hello| Dhaka
নিজে চালিয়ে দেখুন — সংখ্যা আর অপারেটর
ওপরের কয়েকটি প্রশ্ন এক প্রোগ্রামে। চালানোর আগে ৫ নম্বর লাইনের max + 1L-কে (long) max + 1 লিখলে ফল কী হবে, আর (long) (max + 1) লিখলে কী হবে — অনুমান করুন।
class Main {
public static void main(String[] args) {
System.out.println("1) " + 7 / 2 + " " + (-7 / 2) + " " + (-7 % 3) + " " + (7 % -3));
char c = 'a';
System.out.println("2) " + (c + 1) + " " + (char) (c + 1));
System.out.println("3) " + (1 + 2 + "3") + " " + ("1" + 2 + 3));
int i = 5;
int a = i++ + ++i;
System.out.println("4) " + a + " " + i);
int max = Integer.MAX_VALUE;
long wrong = max + 1, right = max + 1L;
System.out.println("5) " + wrong + " " + right);
System.out.println("6) " + Math.round(2.5) + " " + Math.round(-2.5) + " " + (byte) 200);
System.out.println("7) " + (0.1 + 0.2) + " " + (0.1 + 0.2 == 0.3));
}
}আউটপুট: 1) 3 -3 -1 1, 2) 98 b, 3) 33 123, 4) 12 7, 5) -2147483648 2147483648, 6) 3 -2 -56, 7) 0.30000000000000004 false. লক্ষ করুন ১ নম্বর লাইনে "1) " + 7 / 2 ঠিকই 3 দেয়, কারণ /-এর precedence +-এর চেয়ে বেশি — ভাগ আগে, তারপর জোড়া। (long) max + 1 লিখলে cast আগে বসে, যোগ long-এ: 2147483648. কিন্তু (long) (max + 1)-এ যোগ আগে int-এ overflow করে, তারপর cast: -2147483648.
নিজে চালিয়ে দেখুন — String, boxing আর text block
এই প্রোগ্রামে ৫ নম্বর লাইনটি এই ভাগের প্রশ্নে নেই — নিজে অনুমান করুন। সংকেত: ternary-র দুই শাখায় int আর double থাকলে ফলের টাইপ কী হয়?
class Main {
public static void main(String[] args) {
String a = "java", b = "ja" + "va";
String part = "ja";
String c = part + "va";
System.out.println("1) " + (a == b) + " " + (a == c) + " " + a.equals(c) + " " + (a == c.intern()));
String s = "dhaka";
s.toUpperCase();
System.out.println("2) " + s + " " + s.toUpperCase());
StringBuilder sb = new StringBuilder("abc");
sb.insert(1, "XY").append(sb.length()).deleteCharAt(0);
System.out.println("3) " + sb + " " + new StringBuilder("dhaka").reverse());
Integer x = 127, y = 127, p = 128, q = 128;
System.out.println("4) " + (x == y) + " " + (p == q) + " " + p.equals(q));
Object o = true ? 1 : 2.0;
System.out.println("5) " + o);
String t = """
Hello
Dhaka
""";
System.out.println("6) " + t.replace("\n", "|"));
}
}আউটপুট: 1) true false true true, 2) dhaka DHAKA, 3) XYbc5 akahd, 4) true false true, 5) 1.0, 6) Hello| Dhaka|. ৫ নম্বরটি অনেককে চমকে দেয়: ternary-র দুই শাখা সংখ্যা হলে binary numeric promotion হয় — int আর double মিলে ফলের টাইপ double, তাই 1 হয়ে যায় 1.0, তারপর সেটি Double হিসেবে box হয়ে o-তে বসে। আর ৩ নম্বরে শুধু new StringBuilder("dhaka").reverse() — কোনো ! নেই, তাই akahd. যদি String part-কে final String part লেখেন, তাহলে part + "va"-ও compile-time constant হয়ে যায় এবং a == c সত্য হয় — চালিয়ে দেখুন।
৪ · নিয়ন্ত্রণ, method আর array
এই ভাগে trace table-এ একটি কলাম যোগ করুন: call stack. Java-তে সব argument pass-by-value — primitive হলে মানের কপি, object বা array হলে reference-এর কপি। তাই method-এর ভেতরে object বদলানো বাইরে দেখা যায়, কিন্তু প্যারামিটারে নতুন object বসানো দেখা যায় না। আর overload বাছাই হয় তিন ধাপে (overload resolution): আগে boxing ও varargs ছাড়া (widening চলবে), তারপর boxing সহ, শেষে varargs.
ঝালিয়ে নিন: পর্ব ১০ · if/else আর switch expression, পর্ব ১১ · লুপ, পর্ব ১২ · method, overloading আর pass-by-value, পর্ব ১৩ · array, পর্ব ১৪ · regular expression.
(সহজ) পুরোনো case: বনাম নতুন case -> — দুই লাইনে কী ছাপা হবে?
int day = 2;
switch (day) {
case 1: System.out.print("A");
case 2: System.out.print("B");
case 3: System.out.print("C"); break;
default: System.out.print("D");
}
System.out.println();
switch (day) {
case 1 -> System.out.print("A");
case 2 -> System.out.print("B");
case 3 -> System.out.print("C");
default -> System.out.print("D");
}
B·BBC·BBCD·BCDBC·BC
(মাঝারি) Labeled continue আর break — কী ছাপা হবে?
outer:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (j == 2) continue outer;
if (i == 3) break outer;
System.out.print(i + "" + j + " ");
}
}
System.out.println("|");
11 12 13 21 22 23 |11 21 31 |11 21 |- Compile error
(মাঝারি) for-এর শেষে একটি সেমিকোলন, আর do-while — কী ছাপা হবে?
int i;
for (i = 0; i < 3; i++);
{
System.out.print(i);
}
int n = 0;
do { System.out.print(n); } while (n++ < 2);
System.out.println(" " + n);
3012 3012012 33012 2312 3
(মাঝারি) primitive আর array প্যারামিটার — কী ছাপা হবে?
static void change(int x, int[] arr) {
x = 99;
arr[0] = 99;
}
// main:
int a = 1;
int[] b = {1, 2};
change(a, b);
System.out.println(a + " " + b[0]);
99 991 199 11 99
(কঠিন) প্যারামিটারে নতুন object বসানো — কী ছাপা হবে?
static void reset(int[] arr) {
arr = new int[]{0, 0};
arr[0] = 5;
}
static void grow(StringBuilder sb) {
sb.append("!");
sb = new StringBuilder("new");
sb.append("?");
}
// main:
int[] a = {1, 2};
reset(a);
StringBuilder s = new StringBuilder("hi");
grow(s);
System.out.println(a[0] + " " + s);
5 new?1 hi!1 hi1 new?
(মাঝারি) Recursion-এ ডাকের আগে আর পরে print — কী ছাপা হবে?
static void p(int n) {
if (n == 0) return;
System.out.print(n);
p(n - 1);
System.out.print(n);
}
// main:
p(3);
3213213213211233210123
(মাঝারি) null দিয়ে overloaded method ডাকা — কী ছাপা হবে?
static void f(Object o) { System.out.println("Object"); }
static void f(String s) { System.out.println("String"); }
// main:
f(null);
StringObject- Compile error: reference to f is ambiguous
- NullPointerException
(কঠিন) Widening, boxing আর varargs — কী ছাপা হবে?
static void g(long x) { System.out.print("long "); }
static void g(Integer x) { System.out.print("Integer "); }
static void g(int... x) { System.out.print("varargs "); }
// main:
g(5);
byte b = 5;
g(b);
g(5, 6);
Integer Integer varargsvarargs varargs varargslong Integer varargslong long varargs
(সহজ) লুপের শর্তে <= — কী ঘটবে?
int[] a = {10, 20, 30};
for (int i = 0; i <= a.length; i++) {
System.out.print(a[i] + " ");
}
10 20 30 010 20 30, তারপর ArrayIndexOutOfBoundsException: Index 3 out of bounds for length 3- Compile error
- কিছু ছাপার আগেই ArrayIndexOutOfBoundsException
নিজে চালিয়ে দেখুন — method, overload আর লুপ
চালানোর আগে অনুমান করুন: g(int... x) মুছে দিলে g(5, 6) লাইনে কী হবে? আর g(long x) মুছে দিলে g(5) কী ছাপবে?
class Main {
static void change(int x, int[] arr) { x = 99; arr[0] = 99; }
static void grow(StringBuilder sb) { sb.append("!"); sb = new StringBuilder("new"); }
static void f(Object o) { System.out.print("Object "); }
static void f(String s) { System.out.print("String "); }
static void g(long x) { System.out.print("long "); }
static void g(Integer x) { System.out.print("Integer "); }
static void g(int... x) { System.out.print("varargs "); }
static void p(int n) { if (n == 0) return; System.out.print(n); p(n - 1); System.out.print(n); }
public static void main(String[] args) {
int a = 1; int[] b = {1, 2};
StringBuilder s = new StringBuilder("hi");
change(a, b); grow(s);
System.out.println("1) " + a + " " + b[0] + " " + s);
System.out.print("2) "); f(null); g(5); g(5, 6); System.out.println();
System.out.print("3) "); p(3); System.out.println();
System.out.print("4) ");
outer:
for (int i = 1; i <= 3; i++) {
for (int j = 1; j <= 3; j++) {
if (j == 2) continue outer;
if (i == 3) break outer;
System.out.print(i + "" + j + " ");
}
}
System.out.println();
}
}আউটপুট: 1) 1 99 hi!, 2) String long varargs, 3) 321123, 4) 11 21. g(int... x) মুছলে g(5, 6) compile error দেয় — দুই argument নেওয়ার মতো আর কোনো method নেই। g(long x) মুছলে প্রথম ধাপে কিছু মেলে না; দ্বিতীয় ধাপে boxing দিয়ে g(Integer) মেলে — ছাপে Integer. নিজের কোডে এমন “কাছাকাছি” overload এড়িয়ে চলুন; পাঠকের কাছে কোনটা চলবে তা স্পষ্ট নয়।
regex — find, group, replaceAll আর split
regex-এর আউটপুট প্রশ্নে চারটি জিনিস দেখুন: find() প্রতিবার আগের মিলের শেষ থেকে খোঁজে; group(n)-এর n বাঁ দিক থেকে খোলা বন্ধনী গুনে; replaceAll-এর বদলি লেখায় $1 মানে group 1; আর split শুরুর ফাঁকা টুকরো রাখে, শেষেরগুলো ফেলে দেয়। কোডে import java.util.regex.*; আর java.util.Arrays ধরে নিন।
(সহজ) while (m.find()) আর group(2) — কী ছাপা হবে?
Matcher m = Pattern.compile("(\\w+)@(\\w+)\\.com")
.matcher("rahim@gmail.com, karim@yahoo.com");
while (m.find()) System.out.print(m.group(2) + " ");
rahim karimgmail yahoorahim@gmail.com karim@yahoo.comgmail
(মাঝারি) named group, start() আর groupCount() — কী ছাপা হবে?
Matcher m = Pattern.compile("(?<d>\\d{2})/(?<mo>\\d{2})/(?<y>\\d{4})")
.matcher("Date: 14/10/2026");
if (m.find())
System.out.println(m.group("y") + "-" + m.group("mo") + "-" + m.group("d")
+ " " + m.start() + " " + m.groupCount());
2026-10-14 6 32026-10-14 0 32026-10-14 6 4- কিছুই ছাপা হয় না
(মাঝারি) replaceAll-এর বদলি লেখায় $1 — কী ছাপা হবে?
System.out.println("Tk 1,500 and Tk 20"
.replaceAll("Tk (\\d[\\d,]*)", "$1 taka"));
$1 taka and $1 taka1,500 taka and 20 taka1 taka,500 and 20 takaIllegalArgumentException
(মাঝারি) শুরুতে ফাঁকা থাকলে split("\\s+") — কী ছাপা হবে?
String t = " a b ";
System.out.println(Arrays.toString(t.split("\\s+")) + " " + t.split("\\s+").length);
[a, b] 2[, a, b] 3[, a, b, ] 4[, a, , b] 4
(মাঝারি) অঙ্ক দিয়ে কাটা আর results().count() — কী ছাপা হবে?
String t = "a1b22c333";
System.out.println(Arrays.toString(t.split("\\d+")) + " "
+ Pattern.compile("\\d+").matcher(t).results().count());
[a, b, c, ] 3[a, b, c] 3[a, b, c] 6[a, b, c] 1
৫ · অবজেক্ট-ওরিয়েন্টেড Java — static, constructor, override আর hiding
OOP-এর আউটপুট প্রশ্নে একটি মাত্র প্রশ্ন বারবার করুন: “এই নামটি কে ঠিক করছে — compiler, নাকি চলার সময়ের object?” Instance method override হলে ঠিক করে object-এর আসল টাইপ (dynamic dispatch নামে পরিচিত)। কিন্তু field আর static method ঠিক করে variable-এর ঘোষিত টাইপ — subclass-এ একই নামের field থাকলে সেটা override নয়, field hiding; একই নামের static method হলে method hiding. আর static field পুরো class-এ একটি, প্রতিটি object-এর জন্য আলাদা নয়।
ঝালিয়ে নিন: পর্ব ১৫ · class ও object, পর্ব ১৬ · constructor আর this, পর্ব ১৭ · encapsulation, পর্ব ১৮ · static আর final, পর্ব ১৯ · inheritance, পর্ব ২০ · polymorphism, পর্ব ২১ · abstract class আর interface, পর্ব ২২ · মাঝপথের প্রজেক্ট.
(সহজ) static আর instance field, তিনটি object — কী ছাপা হবে?
class Counter {
static int total = 0;
int mine = 0;
Counter() { total++; mine++; }
}
// main:
Counter a = new Counter();
Counter b = new Counter();
Counter c = new Counter();
System.out.println(a.total + " " + a.mine + " " + Counter.total);
1 1 33 3 33 1 3- Compile error: object দিয়ে static field পড়া যায় না
(মাঝারি) Field initializer আর instance block-এর ক্রম — কী ছাপা হবে?
class P {
int x = init("x");
{ System.out.print("block "); }
int y = init("y");
P() { System.out.print("ctor"); }
int init(String n) { System.out.print(n + " "); return 1; }
}
// main:
new P();
x block y ctorx y block ctorctor x block yx y ctor
(কঠিন) Superclass-এর constructor থেকে overridden method ডাকা — কী ছাপা হবে?
class Base {
Base() { show(); }
void show() { System.out.println("Base"); }
}
class Child extends Base {
int v = 5;
String s = "hi";
final int k = 7;
@Override void show() { System.out.println("Child " + v + " " + s + " " + k); }
}
// main:
new Child();
BaseChild 0 null 7Child 5 hi 7Child 0 null 0
(মাঝারি) Override বনাম field hiding — কী ছাপা হবে?
class Animal {
String name = "Animal";
String sound() { return "..."; }
}
class Dog extends Animal {
String name = "Dog";
@Override String sound() { return "Woof"; }
}
// main:
Animal a = new Dog();
System.out.println(a.name + " " + a.sound() + " " + ((Dog) a).name);
Dog Woof DogAnimal ... DogAnimal Woof AnimalAnimal Woof Dog
(কঠিন) static method-এর “override” — কী ছাপা হবে?
class Parent {
static String who() { return "Parent"; }
String me() { return "Parent"; }
}
class Kid extends Parent {
static String who() { return "Kid"; }
@Override String me() { return "Kid"; }
}
// main:
Parent p = new Kid();
System.out.println(p.who() + " " + p.me());
Parent KidKid KidParent Parent- Compile error
(মাঝারি) this(...) আর super()-এর শিকল — কী ছাপা হবে?
class Box {
Box() { this(1); System.out.print("A"); }
Box(int n) { System.out.print("B" + n); }
}
class Crate extends Box {
Crate() { super(); System.out.print("C"); }
}
// main:
new Crate();
AB1CCAB1B1AC- Compile error
(কঠিন) দুটি interface-এ একই default method — কী ঘটবে?
interface X { default String hi() { return "X"; } }
interface Y { default String hi() { return "Y"; } }
class Z implements X, Y { }
// main:
System.out.println(new Z().hi());
X- Compile error: class Z inherits unrelated defaults for hi() from types X and Y
XY- চলার সময় একটি exception
(মাঝারি) Superclass-এর method বনাম interface-এর default method — কী ছাপা হবে?
interface Greeter { default String greet() { return "interface"; } }
class Base2 { public String greet() { return "class"; } }
class Impl extends Base2 implements Greeter { }
// main:
System.out.println(new Impl().greet());
interface- Compile error: inherits unrelated defaults
classinterfaceclass
নিজে চালিয়ে দেখুন — constructor-এর ভেতর থেকে override, hiding
চালানোর আগে পাঁচটি লাইন অনুমান করে লিখুন। তারপর final int k = 7;-কে final int k; করে constructor-এ k = 7; লিখলে প্রথম লাইনে k কত দেখাবে, ভাবুন।
class Base {
String name = "Base";
Base() { show(); }
void show() { System.out.println("Base.show"); }
static String who() { return "Base"; }
}
class Child extends Base {
String name = "Child";
int v = 5;
final int k = 7;
Child() { super(); System.out.println("Child() body: v=" + v); }
@Override void show() { System.out.println("Child.show: v=" + v + " name=" + name + " k=" + k); }
static String who() { return "Child"; }
}
class Main {
public static void main(String[] args) {
Base b = new Child();
System.out.println(b.name + " " + ((Child) b).name);
b.show();
System.out.println(b.who() + " " + Child.who());
}
}আউটপুট: Child.show: v=0 name=null k=7, Child() body: v=5, Base Child, Child.show: v=5 name=Child k=7, Base Child. প্রথম লাইন ছাপা হয় Base()-এর ভেতর থেকে, Child-এর field তৈরির আগে। k-কে constructor-এ মান দিলে সেটি আর constant variable থাকে না, তাই প্রথম লাইনে k=0 দেখাবে। শেষ লাইনের b.who() লেখা বৈধ হলেও বিভ্রান্তিকর — static method সবসময় class-এর নাম দিয়ে ডাকুন।
৬ · আধুনিক টাইপ ও exception — equals, record, enum, sealed, generics
এই ভাগের দুটি নিয়ম মাথায় রাখুন। প্রথমত, hash-ভিত্তিক collection (HashSet, HashMap) আগে hashCode দিয়ে ঘর খোঁজে, তারপর equals দিয়ে মেলায় — তাই equals override করলে hashCode-ও করতে হয় (equals contract); record দুটোই নিজে বানিয়ে দেয়। দ্বিতীয়ত, exception উঠলে JVM call stack ধরে পেছনে যায় (stack unwinding), পথে প্রতিটি finally চালায় — আর finally-র নিজের return আগের সবকিছু মুছে দিতে পারে।
ঝালিয়ে নিন: পর্ব ২৩ · equals, hashCode, toString, পর্ব ২৪ · enum, পর্ব ২৫ · record, পর্ব ২৬ · sealed আর pattern matching, পর্ব ২৭ · nested ও inner class, পর্ব ২৮ · generics, পর্ব ২৯ · exception.
(কঠিন) equals আছে, hashCode নেই — HashSet-এ কী ছাপা হবে?
class Point {
int x, y;
Point(int x, int y) { this.x = x; this.y = y; }
@Override public boolean equals(Object o) {
return o instanceof Point p && p.x == x && p.y == y;
}
}
// main:
Set<Point> set = new HashSet<>();
set.add(new Point(1, 2));
set.add(new Point(1, 2));
System.out.println(set.size() + " " + set.contains(new Point(1, 2)));
1 true2 true2 false- Compile error: hashCode() override করা বাধ্যতামূলক
(সহজ) record-এর toString আর equals — কী ছাপা হবে?
record Point(int x, int y) { }
// main:
Point a = new Point(1, 2), b = new Point(1, 2);
System.out.println(a + " " + (a == b) + " " + a.equals(b) + " " + a.x());
Point[x=1, y=2] false true 1Point@1b6d3586 false false 1Point[x=1, y=2] true true 1- Compile error: a.x() নয়, a.getX() লিখতে হবে
(মাঝারি) enum-এর ordinal, compareTo আর valueOf — কী ঘটবে?
enum Size { SMALL, MEDIUM, LARGE }
// main:
System.out.println(Size.LARGE.ordinal() + " " + Size.SMALL.compareTo(Size.LARGE)
+ " " + Size.valueOf("MEDIUM"));
System.out.println(Size.valueOf("medium"));
3 -1 MEDIUM·MEDIUM2 -2 MEDIUM, তারপর IllegalArgumentException: No enum constant Size.medium2 -2 MEDIUM·null2 -1 MEDIUM·MEDIUM
(মাঝারি) sealed interface আর pattern-matching switch — কী ছাপা হবে?
sealed interface Shape permits Circle, Square { }
record Circle(double r) implements Shape { }
record Square(double side) implements Shape { }
static String describe(Shape s) {
return switch (s) {
case Circle c when c.r() > 10 -> "big circle";
case Circle c -> "circle " + c.r();
case Square(double side) -> "square " + side;
};
}
// main:
System.out.println(describe(new Circle(2)) + ", " + describe(new Circle(20))
+ ", " + describe(new Square(3)));
circle 2, big circle, square 3circle 2.0, circle 20.0, square 3.0- Compile error: default case নেই
circle 2.0, big circle, square 3.0
(কঠিন) guard-ওয়ালা case-কে নিচে রাখলে — কী ঘটবে?
static String describe(Shape s) { // Shape, Circle, Square আগের মতো
return switch (s) {
case Circle c -> "circle";
case Circle c when c.r() > 10 -> "big circle";
case Square q -> "square";
};
}
- সবসময়
circleছাপে, big circle কখনো আসে না - চলার সময় MatchException
- Compile error: this case label is dominated by a preceding case label
- Compile error: the switch expression does not cover all possible input values
(মাঝারি) Inner class-এ তিনটি একই নামের v — কী ছাপা হবে?
class Outer {
int v = 1;
class Inner {
int v = 2;
void show() {
int v = 3;
System.out.println(v + " " + this.v + " " + Outer.this.v);
}
}
}
// main:
new Outer().new Inner().show();
3 2 13 1 22 2 1- Compile error: Outer.this লেখা যায় না
(মাঝারি) Generics আর type erasure — দুই লাইনে কী ছাপা হবে?
List<String> a = new ArrayList<>();
List<Integer> b = new ArrayList<>();
System.out.println(a.getClass() == b.getClass());
System.out.println(a.getClass().getName());
false·java.util.ArrayList<String>true·java.util.ArrayListtrue·java.util.List- Compile error: incomparable types
(কঠিন) finally-তে return, আর return-এর পরে ভ্যারিয়েবল বদল — কী ছাপা হবে?
static int f() {
try { return 1; }
finally { return 2; }
}
static int g() {
int x = 1;
try { return x; }
finally { x = 100; }
}
// main:
System.out.println(f() + " " + g());
1 12 1001 1002 1
(মাঝারি) Exception call stack ধরে ওপরে ওঠে — কী ছাপা হবে?
static void c() { System.out.print("c1 "); int z = 1 / 0; System.out.print("c2 " + z); }
static void b() { try { c(); } finally { System.out.print("bF "); } }
// main:
try { b(); System.out.print("after "); }
catch (ArithmeticException e) { System.out.print("caught " + e.getMessage() + " "); }
finally { System.out.print("mainF"); }
c1 bF caught / by zero mainFc1 bF after mainFc1 caught / by zero bF mainFc1 c2 bF mainF
(কঠিন) try-with-resources-এ দুটি resource আর একটি exception — কী ছাপা হবে?
record Res(String name) implements AutoCloseable {
Res { System.out.print("open " + name + " "); }
public void close() { System.out.print("close " + name + " "); }
}
// main:
try (Res a = new Res("A"); Res b = new Res("B")) {
System.out.print("body ");
throw new RuntimeException("x");
} catch (RuntimeException e) {
System.out.print("catch " + e.getMessage());
}
open A open B body catch x close A close Bopen A open B body close A close B catch xopen A open B body close B close A catch xopen A open B body catch x
নিজে চালিয়ে দেখুন — record, enum, sealed আর generics
এখানে record দিয়ে HashSet-এর প্রশ্নটি আবার করা হয়েছে। চালানোর আগে অনুমান করুন set.size() কত হবে — record নিজে hashCode বানায় কি না, তার ওপর উত্তর নির্ভর করে।
import java.util.*;
record Point(int x, int y) { }
enum Size { SMALL, MEDIUM, LARGE }
sealed interface Shape permits Circle, Square { }
record Circle(double r) implements Shape { }
record Square(double side) implements Shape { }
class Main {
static String describe(Shape s) {
return switch (s) {
case Circle c when c.r() > 10 -> "big circle";
case Circle c -> "circle " + c.r();
case Square(double side) -> "square " + side;
};
}
public static void main(String[] args) {
Point a = new Point(1, 2), b = new Point(1, 2);
Set<Point> set = new HashSet<>(List.of(a));
set.add(b);
System.out.println("1) " + a + " " + (a == b) + " " + a.equals(b) + " " + set.size());
System.out.println("2) " + Size.LARGE.ordinal() + " " + Size.SMALL.compareTo(Size.LARGE) + " " + Size.valueOf("MEDIUM"));
System.out.println("3) " + describe(new Circle(2)) + ", " + describe(new Circle(20)) + ", " + describe(new Square(3)));
List<String> ls = new ArrayList<>();
List<Integer> li = new ArrayList<>();
System.out.println("4) " + (ls.getClass() == li.getClass()));
}
}আউটপুট: 1) Point[x=1, y=2] false true 1, 2) 2 -2 MEDIUM, 3) circle 2.0, big circle, square 3.0, 4) true. record সব component থেকে equals আর hashCode দুটোই বানায়, তাই b যোগ করার সময় HashSet সেটিকে a-এর সমান ধরে এবং size থাকে 1 — ওপরের class Point প্রশ্নের সাথে পার্থক্যটা লক্ষ করুন। describe-এ প্রথম দুটি case-এর ক্রম উল্টে দিয়ে compile করে দেখুন — dominance error নিজের চোখে দেখবেন।
নিজে চালিয়ে দেখুন — finally, propagation আর try-with-resources
চালানোর আগে তিনটি লাইন লিখে নিন। তারপর একটি পরীক্ষা: c()-এর 1 / 0-কে 1 / 1 করলে ২ নম্বর লাইনে কী আসবে?
class Res implements AutoCloseable {
private final String name;
Res(String name) { this.name = name; System.out.print("open " + name + " "); }
@Override public void close() { System.out.print("close " + name + " "); }
}
class Main {
static int f() { try { return 1; } finally { return 2; } }
static int g() { int x = 1; try { return x; } finally { x = 100; } }
static void c() { System.out.print("c1 "); int z = 1 / 0; System.out.print("c2 " + z); }
static void b() { try { c(); } finally { System.out.print("bF "); } }
public static void main(String[] args) {
System.out.println("1) " + f() + " " + g());
System.out.print("2) ");
try { b(); System.out.print("after "); }
catch (ArithmeticException e) { System.out.print("caught " + e.getMessage() + " "); }
finally { System.out.println("mainF"); }
System.out.print("3) ");
try (Res r1 = new Res("A"); Res r2 = new Res("B")) {
System.out.print("body ");
throw new IllegalStateException("x");
} catch (IllegalStateException e) {
System.out.println("catch " + e.getMessage());
}
}
}আউটপুট: 1) 2 1, 2) c1 bF caught / by zero mainF, 3) open A open B body close B close A catch x. 1 / 1 করলে কোনো exception নেই: 2) c1 c2 1bF after mainF — লক্ষ করুন "c2 " + z-এর পরে কোনো space নেই, তাই 1 আর bF জোড়া লেগে যায়। আউটপুট প্রশ্নে এমন space-এর হিসাবও নম্বর কাটে। আর finally-তে return লেখা বাস্তব কোডে এড়িয়ে চলুন — সেটি exception-ও গিলে ফেলে।
৭ · Collections আর functional Java — List, Map, stream, Optional
এই ভাগে trace table-এ collection-এর পুরো অবস্থা লিখুন — আকার, ক্রম, আর সেটি বদলানো যায় কি না। Arrays.asList একটি নির্দিষ্ট আকারের তালিকা দেয় (set চলে, add চলে না), আর List.of কিছুই বদলাতে দেয় না। আর stream-এর বেলায় মনে রাখুন lazy evaluation: intermediate operation (filter, map, peek) কিছুই করে না, যতক্ষণ না একটি terminal operation আসে — আর তখনো উপাদানগুলো যায় একটি একটি করে পুরো পাইপলাইন ধরে, ধাপে ধাপে পুরো তালিকা নয়।
ঝালিয়ে নিন: পর্ব ৩০ · List, পর্ব ৩১ · Set আর Map, পর্ব ৩২ · Queue ও Deque, পর্ব ৩৩ · Comparable আর Comparator, পর্ব ৩৪ · lambda, পর্ব ৩৫ · Stream API, পর্ব ৩৬ · Optional.
(মাঝারি) Arrays.asList-এ set আর add — কী ঘটবে?
List<String> list = Arrays.asList("a", "b", "c");
list.set(0, "z");
System.out.println(list);
list.add("d");
System.out.println(list);
[z, b, c]·[z, b, c, d][z, b, c], তারপর UnsupportedOperationException- প্রথম লাইনেই (set-এ) UnsupportedOperationException
- Compile error
(মাঝারি) remove(1) বনাম remove(Integer.valueOf(1)) — কী ছাপা হবে?
List<Integer> nums = new ArrayList<>(List.of(10, 20, 30, 1));
nums.remove(1);
nums.remove(Integer.valueOf(1));
System.out.println(nums);
[10, 20, 30][10, 30, 1][10, 30]- IndexOutOfBoundsException
(মাঝারি) for-each চলার সময় প্রথম নাম মোছা — কী ঘটবে?
List<String> names = new ArrayList<>(List.of("Rahim", "Karim", "Salma"));
for (String n : names) {
if (n.startsWith("R")) names.remove(n);
}
System.out.println(names);
[Karim, Salma][Rahim, Karim, Salma]- Compile error
ConcurrentModificationException, কিছু ছাপা হয় না
(কঠিন) একই লুপ, কিন্তু এবার মাঝের নাম মোছা — কী ঘটবে?
List<String> names = new ArrayList<>(List.of("Rahim", "Karim", "Salma"));
for (String n : names) {
if (n.startsWith("K")) names.remove(n);
}
System.out.println(names);
[Rahim, Salma]ConcurrentModificationException[Rahim, Karim, Salma][Rahim]
(মাঝারি) TreeMap-এর ক্রম আর put-এর ফেরত মান — কী ছাপা হবে?
TreeMap<String, Integer> m = new TreeMap<>();
m.put("banana", 2);
m.put("Apple", 1);
m.put("cherry", 3);
System.out.println(m.put("banana", 9) + " " + m);
2 {banana=9, Apple=1, cherry=3}9 {Apple=1, banana=9, cherry=3}2 {Apple=1, banana=9, cherry=3}2 {banana=9, cherry=3, Apple=1}
(মাঝারি) দৈর্ঘ্য অনুযায়ী উল্টো সাজানো, সমান দৈর্ঘ্যে কী হয় — কী ছাপা হবে?
List<String> l = new ArrayList<>(List.of("bb", "a", "ccc", "dd"));
l.sort(Comparator.comparing(String::length).reversed());
System.out.println(l);
[ccc, dd, bb, a][ccc, bb, dd, a][a, bb, dd, ccc]- Compile error
পরের প্রশ্নটি stream-এর সবচেয়ে পরিচিত ফাঁদ। প্রশ্নের আগে ডায়াগ্রামে দেখে নিন উপাদানগুলো আসলে কোন পথে চলে — “পরে” চাপার আগে প্রতিবার অনুমান করুন পরের লেখা কী হবে।
1Stream.of("a", "bb", "ccc")2 .peek(s -> print("P" + s + " "))3 .filter(s -> s.length() > 1)4 .map(s -> { print("M" + s + " "); return s.toUpperCase(); })5 .findFirst()6 .ifPresent(System.out::println);
| এখন কোন উপাদান | |
| filter-এর ফল | |
| findFirst-এর হাতে | |
| ccc ছোঁয়া হয়েছে? |
- পাইপলাইন তৈরি, কিন্তু কিছুই চলেনি।
Stream.of,peek,filter,map— এগুলো শুধু কাজের তালিকা বানায়।findFirst()(terminal operation) ডাকার মুহূর্তে কাজ শুরু হয়। terminal না থাকলে একটিP-ও ছাপা হতো না। - প্রথম উপাদান “a” — peek. উপাদানটি পাইপলাইনে ঢুকে প্রথম ধাপে যায়: ছাপে Pa. লক্ষ করুন, “bb” আর “ccc” এখনো অপেক্ষায় — পুরো তালিকা আগে peek হয় না।
- “a” filter-এ বাদ। দৈর্ঘ্য 1, শর্ত মিথ্যা। map পর্যন্ত যায়ই না — তাই
Maকখনো ছাপা হবে না। এবার পরের উপাদান। - “bb” — peek, তারপর filter পাস। ছাপে Pbb; দৈর্ঘ্য 2, শর্ত সত্য, তাই “bb” এগিয়ে যায় map-এর দিকে।
- “bb” — map. ছাপে Mbb, ফল “BB” পৌঁছায় findFirst-এর হাতে। findFirst একটি short-circuiting operation — একটি পেলেই সে বাকি কাজ বাতিল করে।
- “ccc” কখনো ছোঁয়াই হয়নি। Optional-এ BB, আর ifPresent ছাপে BB. পুরো আউটপুট: Pa Pbb Mbb BB. যদি ধাপে ধাপে পুরো তালিকা চলত, আউটপুট হতো Pa Pbb Pccc Mbb Mccc BB — বহু মানুষ এই ভুল উত্তরটাই দেন।
(কঠিন) peek, filter, map আর findFirst — কী ছাপা হবে?
Stream.of("x", "yy", "zzz")
.peek(s -> System.out.print("P" + s + " "))
.filter(s -> s.length() > 1)
.map(s -> { System.out.print("M" + s + " "); return s.toUpperCase(); })
.findFirst()
.ifPresent(System.out::println);
Px Pyy Myy YYPx Pyy Pzzz Myy Mzzz YYPyy Myy YY- কিছুই ছাপা হয় না — stream অলস
(কঠিন) map-এর ভেতরে তালিকায় যোগ, তারপর count — OpenJDK 21-এ কী ছাপা হবে?
List<Integer> seen = new ArrayList<>();
long n = Stream.of(1, 2, 3).map(x -> { seen.add(x); return x * 2; }).count();
System.out.println(n + " " + seen.size());
3 33 00 0- Compile error: seen effectively final নয়
(মাঝারি) একই stream দুবার ব্যবহার — কী ঘটবে?
Stream<String> s = Stream.of("a", "b");
System.out.println(s.count());
System.out.println(s.count());
2·22·02, তারপর IllegalStateException: stream has already been operated upon or closed- Compile error
(মাঝারি) Optional-এ orElse বনাম orElseGet — দুই লাইনে কী ছাপা হবে?
static String load() { System.out.print("load "); return "default"; }
// main:
Optional<String> name = Optional.of("Rahim");
System.out.println(name.orElse(load()));
System.out.println(name.orElseGet(() -> load()));
Rahim·Rahimload default·load defaultload Rahim·load Rahimload Rahim·Rahim
(মাঝারি) lambda-র ভেতরে local ভ্যারিয়েবল বাড়ানো — কী ঘটবে?
int count = 0;
List.of(1, 2, 3).forEach(x -> count++);
System.out.println(count);
- Compile error: local variables referenced from a lambda expression must be final or effectively final
30- ConcurrentModificationException
নিজে চালিয়ে দেখুন — List, Iterator আর TreeMap
চালানোর আগে পাঁচটি লাইন লিখে নিন। ৩ নম্বর লাইনে "K"-এর বদলে "S" লিখলে কী হবে — exception, নাকি নীরবে চলবে?
import java.util.*;
class Main {
public static void main(String[] args) {
List<Integer> nums = new ArrayList<>(List.of(10, 20, 30, 1));
nums.remove(1);
nums.remove(Integer.valueOf(1));
System.out.println("1) " + nums);
List<String> fixed = Arrays.asList("a", "b", "c");
fixed.set(0, "z");
try { fixed.add("d"); }
catch (UnsupportedOperationException e) { System.out.println("2) " + fixed + " add -> " + e.getClass().getSimpleName()); }
List<String> names = new ArrayList<>(List.of("Rahim", "Karim", "Salma"));
for (String n : names) { if (n.startsWith("K")) names.remove(n); }
System.out.println("3) " + names);
List<String> again = new ArrayList<>(List.of("Rahim", "Karim", "Salma"));
try { for (String n : again) { if (n.startsWith("R")) again.remove(n); } }
catch (ConcurrentModificationException e) { System.out.println("4) CME, list = " + again); }
TreeMap<String, Integer> m = new TreeMap<>();
m.put("banana", 2); m.put("Apple", 1); m.put("cherry", 3);
System.out.println("5) " + m.put("banana", 9) + " " + m);
}
}আউটপুট: 1) [10, 30], 2) [z, b, c] add -> UnsupportedOperationException, 3) [Rahim, Salma], 4) CME, list = [Karim, Salma], 5) 2 {Apple=1, banana=9, cherry=3}. ৪ নম্বরে লক্ষ করুন, exception ওঠার আগেই Rahim মোছা হয়ে গেছে — CME তালিকাকে আগের অবস্থায় ফেরায় না। "S" লিখলে Salma (শেষ উপাদান) মোছার পরে cursor = 3, size = 2; hasNext() সত্য বলে next() ডাকে, আর তখন ConcurrentModificationException ওঠে — ধরার মতো catch না থাকায় প্রোগ্রাম থেমে stack trace ছাপবে।
নিজে চালিয়ে দেখুন — stream, Optional আর Comparator
২ নম্বর লাইনে .map(...)-এর আগে .filter(x -> true) যোগ করলে seen.size() কত হবে — অনুমান করে তারপর চালান।
import java.util.*;
import java.util.stream.*;
class Main {
static String load() { System.out.print("load "); return "default"; }
public static void main(String[] args) {
System.out.print("1) ");
Stream.of("a", "bb", "ccc")
.peek(s -> System.out.print("P" + s + " "))
.filter(s -> s.length() > 1)
.map(s -> { System.out.print("M" + s + " "); return s.toUpperCase(); })
.findFirst()
.ifPresent(System.out::println);
List<Integer> seen = new ArrayList<>();
long n = Stream.of(1, 2, 3).map(x -> { seen.add(x); return x * 2; }).count();
System.out.println("2) " + n + " " + seen.size());
Optional<String> name = Optional.of("Rahim");
System.out.print("3) ");
System.out.println(name.orElse(load()));
System.out.print("4) ");
System.out.println(name.orElseGet(() -> load()));
List<String> l = new ArrayList<>(List.of("bb", "a", "ccc", "dd"));
l.sort(Comparator.comparing(String::length).reversed());
System.out.println("5) " + l);
}
}আউটপুট: 1) Pa Pbb Mbb BB, 2) 3 0, 3) load Rahim, 4) Rahim, 5) [ccc, bb, dd, a]. filter(x -> true) যোগ করলে stream আর জানে না শেষে কয়টি উপাদান থাকবে (filter আকার বদলাতে পারে), তাই count-কে সত্যিই পাইপলাইন চালাতে হয় — seen.size() হয় 3. একই কোড, একটি অকেজো লাইন যোগে ভিন্ন পার্শ্বপ্রতিক্রিয়া: এজন্যই stream-এর ভেতরে side effect বিপজ্জনক।
৮ · সিস্টেম ও পেশাদার Java — ফাইল, তারিখ, thread, annotation আর অ্যালগরিদম
মডিউল ৬ ও ৭-এর অনেক বিষয় (JDBC, Spring Boot, Maven, JUnit) আউটপুট প্রশ্নের চেয়ে লিখিত ও ভাইভা প্রশ্নে বেশি আসে — সেগুলো পাবেন পর্ব ৫৮-এ। এখানে আছে সেই অংশগুলো, যাদের আউটপুট নির্দিষ্টভাবে বলা যায়: java.nio.file, LocalDate, thread-এর run() বনাম start(), annotation-এর retention আর অ্যালগরিদমের overflow. একটি সতর্কতা: একাধিক thread একসাথে ছাপলে বা একই ভ্যারিয়েবল বদলালে উত্তর প্রায়ই “নিশ্চিত নয়” — race condition চেনাও এখানে পূর্ণ নম্বরের উত্তর।
ঝালিয়ে নিন: পর্ব ৩৭ · ফাইল I/O, পর্ব ৩৮ · java.time, পর্ব ৪০ · thread আর synchronization, পর্ব ৪১ · Executor আর CompletableFuture, পর্ব ৪২ · virtual thread, পর্ব ৪৫ · annotation আর reflection, পর্ব ৫৪ · অ্যালগরিদম ও কনটেস্টে Java. বাকিগুলোর জন্য: পর্ব ৩৯, পর্ব ৪৩, পর্ব ৪৪, পর্ব ৪৬–৫১, পর্ব ৫২ · Spring Data JPA (শেষ প্রশ্নটির জন্য), পর্ব ৫৩, পর্ব ৫৫.
(মাঝারি) একই ফাইলে তিনবার লেখা — কী ছাপা হবে?
Path p = Path.of("note.txt");
Files.writeString(p, "abc");
Files.writeString(p, "d");
Files.writeString(p, "e", StandardOpenOption.APPEND);
System.out.println(Files.readString(p));
abcdedeedbce
(মাঝারি) ৩১ জানুয়ারির পরে এক মাস — তিন লাইনে কী ছাপা হবে?
LocalDate d = LocalDate.of(2024, 1, 31);
System.out.println(d.plusMonths(1));
System.out.println(d.plusMonths(1).plusMonths(1));
System.out.println(d.plusMonths(2));
2024-03-02·2024-04-02·2024-03-312024-02-29·2024-03-31·2024-03-312024-02-29·2024-03-29·2024-03-31- DateTimeException
(মাঝারি) Thread-এ run() বনাম start() — কী ছাপা হবে?
Thread t = new Thread(
() -> System.out.print("[" + Thread.currentThread().getName() + "]"),
"worker");
t.run();
t.start();
t.join();
System.out.println("done");
[main][worker]done[worker][worker]done[main]done[worker]- IllegalThreadStateException, কারণ run() আগেই ডাকা হয়েছে
(মাঝারি) দুটি thread একই counter বাড়ায়, synchronization ছাড়া — কী ছাপা হবে?
int[] c = {0};
Runnable r = () -> { for (int i = 0; i < 100000; i++) c[0]++; };
Thread a = new Thread(r), b = new Thread(r);
a.start(); b.start();
a.join(); b.join();
System.out.println(c[0]);
- সবসময়
200000 - সবসময়
100000 - ConcurrentModificationException
- নিশ্চিত নয় — 200000 বা তার কম, প্রতিবার আলাদা হতে পারে
(কঠিন) Retention ছাড়া annotation আর reflection — কী ছাপা হবে?
@interface Tag { }
@Retention(RetentionPolicy.RUNTIME) @interface Live { }
@Tag @Live class Item { }
// main:
System.out.println(Item.class.isAnnotationPresent(Tag.class) + " "
+ Item.class.isAnnotationPresent(Live.class));
true truefalse truefalse false- Compile error: @Retention বাধ্যতামূলক
(মাঝারি) CompletableFuture-এর শিকল — কী ছাপা হবে?
int r = CompletableFuture.supplyAsync(() -> 20)
.thenApply(x -> x + 1)
.thenCombine(CompletableFuture.supplyAsync(() -> 2), (a, b) -> a * b)
.join();
System.out.println(r);
402342- নিশ্চিত নয় — thread-এর ক্রমের ওপর নির্ভর করে
(কঠিন) Binary search-এর mid বের করা — কী ছাপা হবে?
int low = 2_000_000_000, high = 2_100_000_000;
System.out.println((low + high) / 2 + " " + (low + (high - low) / 2)
+ " " + ((low + high) >>> 1));
-97483648 2050000000 20500000002050000000 2050000000 2050000000-97483648 2050000000 -97483648- ArithmeticException: integer overflow
(মাঝারি) optimistic locking-এর সাধারণ Java নকল — দুটি transaction একই সারি পড়ে লিখতে চায়। কী ছাপা হবে?
class Main {
static int dbStock = 5, dbVersion = 1;
static boolean update(int newStock, int readVersion) { // update ... where version = ?
if (dbVersion != readVersion) return false; // ০টি সারি বদলাল
dbStock = newStock; dbVersion++; return true;
}
public static void main(String[] args) {
int s1 = dbStock, v1 = dbVersion; // T1 পড়ল
int s2 = dbStock, v2 = dbVersion; // T2 পড়ল
System.out.println(update(s1 - 3, v1) + " " + update(s2 - 4, v2)
+ " " + dbStock + " " + dbVersion);
}
}
true true 1 3true false 2 2false true 1 2true false 2 1
সব প্রশ্নের উত্তর দেওয়া শেষ হলে এখানে আপনার প্রথম চেষ্টার স্কোর দেখাবে:
নিজে চালিয়ে দেখুন — তারিখ, ফাইল, thread আর CompletableFuture
এই প্রোগ্রামের প্রতিটি লাইনের আউটপুট নিশ্চিত — thread-এর ভেতর থেকে শুধু একবার ছাপা হয়, আর তার পরেই join(). চালানোর আগে অনুমান করুন: ২০২৪ সালের ৩১ জানুয়ারি সপ্তাহের কোন দিন ছিল?
import java.nio.file.*;
import java.time.*;
import java.util.concurrent.*;
class Main {
public static void main(String[] args) throws Exception {
LocalDate d = LocalDate.of(2024, 1, 31);
System.out.println("1) " + d.plusMonths(1) + " " + d.plusMonths(1).plusMonths(1) + " " + d.plusMonths(2));
d.plusDays(1);
System.out.println("2) " + d + " " + d.getDayOfWeek());
Path p = Path.of("note.txt");
Files.writeString(p, "abc");
Files.writeString(p, "d");
Files.writeString(p, "e", StandardOpenOption.APPEND);
System.out.println("3) " + Files.readString(p));
Thread mainThread = Thread.currentThread();
Thread t = new Thread(() -> System.out.print(Thread.currentThread() == mainThread ? "main " : "new "));
System.out.print("4) ");
t.run();
t.start(); t.join();
System.out.println(t.getState());
int r = CompletableFuture.supplyAsync(() -> 20).thenApply(x -> x + 1)
.thenCombine(CompletableFuture.supplyAsync(() -> 2), (a, b) -> a * b).join();
System.out.println("5) " + r);
}
}আউটপুট: 1) 2024-02-29 2024-03-29 2024-03-31, 2) 2024-01-31 WEDNESDAY, 3) de, 4) main new TERMINATED, 5) 42. ২ নম্বরে d.plusDays(1)-এর ফল কোথাও রাখা হয়নি — LocalDate-ও String-এর মতো immutable, তাই d বদলায়নি। ৪ নম্বরে thread-এর নাম ছাপার বদলে তুলনা করা হয়েছে, কারণ নাম না দিলে JVM যে নাম দেয় (যেমন Thread-0) তার ওপর ভরসা করা উচিত নয়। join-এর পরে thread-এর অবস্থা TERMINATED; এখন আবার t.start() ডাকলে IllegalThreadStateException উঠত।
৯ · AI-এর যুগে — JDK-ই শেষ বিচারক
আজ অনেকে AI-কে কোড দেখিয়ে জিজ্ঞেস করেন “এটা কী ছাপবে?” সাধারণ কোডে উত্তর প্রায়ই ঠিক আসে। কিন্তু এই পাতায় যে জায়গাগুলো অনুশীলন করলেন — i = i++, Integer cache, constructor থেকে override ডাকা, শেষের আগের উপাদান মুছলে CME না ওঠা, count()-এ map না চলা — সেখানে ভাষা-মডেল প্রায়ই আত্মবিশ্বাসী সুরে ভুল বলে। কারণ সে কোড চালায় না; সে অনুরূপ লেখা দেখে সম্ভাব্য উত্তর অনুমান করে। আর ইন্টারনেটের পুরোনো লেখায় Java 8-এর আগের নিয়মই বেশি, তাই পরের বদলগুলো (Java 9-এর count অপটিমাইজেশন, Java 14-এর switch expression, Java 21-এর pattern matching) সে প্রায়ই ভুলে যায় বা মিশিয়ে ফেলে।
আর “নিশ্চিত নয়” ধরনের কোডে একবারের আউটপুটও প্রমাণ নয় — প্রমাণ হলো Java-র নিয়ম।
AI সাধারণত যেখানে Java-র আউটপুট ভুল বলে
| যে ধরনের কোড | AI-এর সাধারণ ভুল | কীভাবে ধরবেন |
|---|---|---|
String বা Integer-এ == | compile-time constant আর runtime জোড়ার পার্থক্য ভুলে যায়; 128-এর ক্ষেত্রেও true বলে | নিজে চালান; final যোগ-বিয়োগ করে দেখুন ফল বদলায় কি না |
| initialization-এর ক্রম | static block প্রতিটি object-এ চলবে বলে, বা field initializer-কে constructor-এর পরে বসায় | প্রতিটি block-এ একটি print রেখে চালান |
| লুপে collection বদলানো | সবসময় ConcurrentModificationException বলে, অথবা কখনোই না | শুরু, মাঝ আর শেষের আগের উপাদান — তিনটি আলাদা করে চালান |
| stream-এর side effect | ধাপে ধাপে পুরো তালিকা চলবে ধরে নেয়; count-এর অপটিমাইজেশন জানে না | peek-এ print রেখে আসল ক্রম দেখুন; JDK সংস্করণ লিখে প্রশ্ন করুন |
| HashSet, Set.of, thread | একটি নির্দিষ্ট ক্রম বা সংখ্যা লিখে দেয় | কয়েকবার চালান; জিজ্ঞেস করুন: Java কি এই ক্রমের নিশ্চয়তা দেয়? |
AI দিয়ে নতুন অনুশীলনের প্রশ্ন বানাতে পারেন — শর্ত একটাই, উত্তর যাচাইয়ের দায়িত্ব আপনার। নিচের prompt-টি AI-কে এমনভাবে বাঁধে, যাতে সে আগে প্রশ্ন দেয়, উত্তর পরে, আর “compile error” বা “নিশ্চিত নয়” উত্তর লুকাতে না পারে:
AI-এর দেওয়া প্রশ্ন বা উত্তর হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন · যে ফাঁদে সবাই পড়ে
Java-র “আউটপুট কী হবে?” প্রশ্নের পেছনের ছয়টি বড় প্রশ্ন। প্রতিটি নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।
প্র ০১C ও C++ যেখানে মূল্যায়নের ক্রম অনেক জায়গায় খোলা রেখেছে, Java কেন সবকিছু বাঁ থেকে ডানে বেঁধে দিল?
C-র জন্ম ১৯৭০-এর দশকে, যখন প্রতিটি নতুন CPU-তে সবচেয়ে দ্রুত কোড বানানোই ছিল বড় লক্ষ্য। তাই C standard অনেক কিছু compiler-এর হাতে ছেড়ে দিয়েছিল — f() + g()-এ কোন ফাংশন আগে চলবে, int কত বিটের হবে, signed overflow-এ কী হবে। এতে compiler ইচ্ছামতো অপটিমাইজ করতে পারে, কিন্তু একই কোড দুই compiler-এ দুই রকম ফল দিতে পারে, আর i = i++ + 1-এর মতো কোড undefined behavior.
১৯৯০-এর দশকে Sun Microsystems-এ James Gosling-এর দল Java বানানোর সময় লক্ষ্য ছিল উল্টো: “একবার লিখুন, সব জায়গায় চালান”। একই bytecode Windows, Solaris বা মোবাইলে একই ফল দেবে — এটাই ছিল প্রতিশ্রুতি। সেই প্রতিশ্রুতি রাখতে Java ভাষার স্তরেই সব বেঁধে দিল:
- মূল্যায়নের ক্রম: JLS §15.7 বলে operand-গুলো বাঁ থেকে ডানে মূল্যায়ন হবে — বাঁ operand পুরো শেষ, তারপর ডান; method-এর argument-ও বাঁ থেকে ডানে। তাই
i++ + ++i-এর উত্তর একটিই। - টাইপের আকার: int সবসময় 32 বিট, long 64 বিট, char 16 বিট — প্ল্যাটফর্ম যা-ই হোক। overflow-এ মান two's complement নিয়মে ঘোরে, undefined নয়।
- নিয়ম ভাঙলে নিশ্চিত প্রতিক্রিয়া: array-র বাইরে গেলে, null-এর ওপর method ডাকলে, শূন্য দিয়ে int ভাগ করলে — একটি নির্দিষ্ট exception, চুপচাপ ভুল মান নয়।
এর দাম ছিল কিছুটা গতি। floating point-এর গল্পটি শিক্ষণীয়: পুরোনো x87 প্রসেসরে কঠোর নিয়ম মানতে গিয়ে Java ধীর হচ্ছিল, তাই Java 1.2-এ ডিফল্ট হিসাব কিছুটা শিথিল করা হয় আর কঠোর হিসাবের জন্য strictfp keyword আনা হয়। পরে হার্ডওয়্যার বদলে যাওয়ায় Java 17-এ (JEP 306) আবার সব floating point হিসাব সবসময় কঠোর করা হয় — strictfp এখন অপ্রয়োজনীয়।
তবে JLS নিজেই পরামর্শ দেয়: ক্রম নির্দিষ্ট হলেও কোড যেন এর ওপর জোর করে নির্ভর না করে। এক এক্সপ্রেশনে একই ভ্যারিয়েবল একাধিকবার বদলানো কোড পড়তে কঠিন, review-তে বাগ লুকায়।
মূল উপলব্ধি: Java-র আউটপুট প্রশ্নে “undefined” উত্তর নেই — প্রতিটি কোডের একটি নির্দিষ্ট ফল, compile error, বা নির্দিষ্ট exception আছে। এই নিশ্চয়তাই প্রশ্নগুলোকে ন্যায্য করে, আর আপনার trace-কে যাচাইযোগ্য করে।
প্র ০২পরীক্ষা আর ইন্টারভিউতে “আউটপুট কী হবে?” প্রশ্ন এত জনপ্রিয় কেন — আর এর সীমাবদ্ধতা কী?
প্রশ্নকর্তার দিক থেকে এটি একটি দক্ষ মাপকাঠি। পাঁচ লাইনের একটি কোড এক মিনিটে পড়া যায়, উত্তর একটিই, নম্বর দেওয়া সহজ — অথচ ঠিক উত্তর দিতে হলে ভাষার একটি নির্দিষ্ট নিয়ম সত্যিই বুঝতে হয়। মুখস্থ সংজ্ঞা (“polymorphism কী?”) অনেকে বলতে পারেন; কিন্তু Animal a = new Dog(); a.name কী দেবে, তা বলতে পারেন কেবল যিনি জানেন field-এ dynamic dispatch নেই।
- মানসিক মডেল যাচাই: প্রোগ্রামার আসলে সারাদিন কোড লেখেন না, পড়েন — নিজের, সহকর্মীর, আর এখন AI-এর লেখা। কোড চালানোর আগেই তার আচরণ বলতে পারা মানে মাথায় JVM-এর একটি নির্ভরযোগ্য মডেল আছে।
- বাগ খোঁজার দক্ষতা: এই পাতার প্রায় প্রতিটি ফাঁদ বাস্তব প্রজেক্টের বাগ — String-এ
==, equals ছাড়া hashCode, লুপে collection বদলানো,orElse-এ ব্যয়বহুল ডাক। - সার্টিফিকেশনের ঐতিহ্য: Oracle-এর Java সার্টিফিকেশন পরীক্ষা বহু বছর ধরে কোড পড়ে আউটপুট বা compile error চেনার প্রশ্নের জন্য পরিচিত। Joshua Bloch ও Neal Gafter-এর Java Puzzlers (২০০৫) বইটি পুরোটাই এমন ধাঁধা নিয়ে, প্রতিটির শেষে প্রোগ্রামারদের জন্য একটি শিক্ষা, আর অনেকগুলোর শেষে ভাষা ডিজাইনারদের জন্যও।
সীমাবদ্ধতাও আছে। প্রথমত, অতি-চালাক ধাঁধা (এক লাইনে তিনটি ++) বাস্তবে কেউ লেখে না, লিখলে code review-তে বাতিল হয়; IntelliJ IDEA বা SpotBugs-এর মতো টুল এমন অনেক কোডে সতর্কবার্তা দেয়। দ্বিতীয়ত, এ ধরনের প্রশ্ন ডিজাইন, পরীক্ষা লেখা বা দলের সাথে কাজের দক্ষতা মাপে না — সেজন্য পর্ব ৫৮-এর লিখিত প্রশ্ন আর পর্ব ৫৯-এর কোডিং ইন্টারভিউ আলাদা করে লাগে। তৃতীয়ত, শুধু উত্তর মুখস্থ করলে প্রশ্নের সামান্য বদলেই ভুল হয় — যেমন এই পাতার CME-এর দুটি প্রশ্ন, যেখানে শুধু কোন নাম মোছা হচ্ছে তা বদলেছে।
মূল উপলব্ধি: আউটপুট প্রশ্নকে ধাঁধা হিসেবে নয়, নিয়ম শেখার উপায় হিসেবে দেখুন। প্রতিটি প্রশ্নের শেষে নিজেকে জিজ্ঞেস করুন — “এই নিয়ম বাস্তব কোডে কোথায় বাগ বানাতে পারে, আর কোন লেখার ধরন সেটা এড়ায়?”
প্র ০৩অচেনা জটিল কোড পেলে কোন ক্রমে trace করবেন, যাতে চাপের মধ্যেও ভুল না হয়?
পরীক্ষার হলে সবচেয়ে বড় শত্রু তাড়াহুড়া — চোখ কোডের ওপর দিয়ে দৌড়ায়, আর মাথা “স্বাভাবিক” উত্তর বসিয়ে দেয়। প্রতিকার একটি নির্দিষ্ট ক্রম, যা প্রতিবার একইভাবে মানবেন:
- ১. আগে compile হবে কি না দেখুন। টাইপ মিলছে (byte-এ int বসানো)? lambda-য় বাইরের ভ্যারিয়েবল বদলানো? private member? একই ধরনের দুটি catch-এর ভুল ক্রম? switch-এ case dominance বা অসম্পূর্ণতা? compile error থাকলে বাকি সব হিসাব অর্থহীন — উত্তর “কিছুই ছাপা হবে না”।
- ২. শুরু কোথা থেকে, তা চিহ্নিত করুন।
main-এর প্রথম লাইন, কিন্তু তার আগে Main class-এর static block. প্রতিটি নতুন class প্রথম ব্যবহারে initialize হয় (JLS §12.4) — superclass আগে। - ৩. টেবিল আঁকুন। প্রতিটি local ভ্যারিয়েবল, প্রতিটি object-এর field (object-কে O1, O2 নাম দিন), static field-এর আলাদা কলাম; আর একটি “আউটপুট” কলাম, যেখানে space ও newline-সহ হুবহু লিখবেন।
- ৪. প্রতিটি new-এ object তৈরির ধাপ মানুন (JLS §12.5): ডিফল্ট মান, super() শিকল, instance initializer লেখার ক্রমে, তারপর constructor-এর শরীর।
- ৫. প্রতিটি method ডাকে দুটি প্রশ্ন: কোন overload (compile-time, ঘোষিত টাইপ দেখে) আর কোন override (runtime, object দেখে)? call stack-এ নতুন ঘর খুলুন; ফেরার সময় সেটি মুছুন।
- ৬. exception উঠলে থামুন আর পেছনে হাঁটুন — প্রতিটি স্তরের finally, তারপর মেলানো catch. যা ছাপা হয়ে গেছে, তা মুছবেন না।
কম্পিউটারের সামনে থাকলে আরও তিনটি সাহায্য আছে। JShell (Java 9 থেকে) দিয়ে class ছাড়াই এক লাইন চালিয়ে দেখা যায়। IDE-র debugger দিয়ে এক লাইন এক লাইন এগিয়ে প্রতিটি ভ্যারিয়েবল দেখা যায় — এটাই আসলে স্বয়ংক্রিয় trace table. আর javap -c compiled class-এর bytecode দেখায়: i = i++-এর জন্য দেখবেন iload (পুরোনো মান stack-এ), iinc (ভ্যারিয়েবল বাড়ানো), istore (পুরোনো মান ফিরিয়ে লেখা) — রহস্যের পুরো উত্তর তিন লাইনে।
মূল উপলব্ধি: ভালো trace মানে ধীর trace নয়, নিয়মিত trace. একই ছয় ধাপ প্রতিবার মানলে কঠিন প্রশ্নও কয়েকটি সহজ প্রশ্নে ভেঙে যায় — আর ভুল হলে ঠিক কোন ধাপে হলো, তা খুঁজে পাওয়া যায়।
প্র ০৪Integer cache কেন আছে, সীমা কেন −128 থেকে 127, আর ==-এর ফাঁদ থেকে বাঁচার উপায় কী?
Java 5 (২০০৪) autoboxing আনে: Integer x = 127; লিখলে compiler আসলে লেখে Integer x = Integer.valueOf(127);. Collection-এ primitive রাখা যায় না, তাই List<Integer>-এ প্রতিটি সংখ্যা একটি object — লাখ লাখ ছোট object বানানো মেমরি আর garbage collection-এর ওপর চাপ। অথচ বাস্তবে বেশিরভাগ সংখ্যা ছোট: লুপের index, গণনা, স্ট্যাটাস কোড।
তাই একটি নিশ্চয়তা দেওয়া হয়েছে: −128 থেকে 127-এর পূর্ণসংখ্যা, ০ থেকে ১২৭ কোডের char, আর true/false box করলে একই মানের জন্য একই object আসবে — JLS §5.1.7-এ এর উল্লেখ আছে, আর Integer.valueOf-এর javadoc বলে এই সীমা সবসময় cache করা হয়। কেন এই সীমা? এটাই এক বাইটের signed সংখ্যার পুরো পরিসর — ছোট, অনুমানযোগ্য, আর বেশিরভাগ সাধারণ মান ঢেকে দেয়। বাস্তবায়নে Integer-এর ভেতরে একটি IntegerCache class আগে থেকে এই ২৫৬টি object বানিয়ে রাখে।
- সীমার বাইরে কী হয়? JLS কিছুই নিশ্চিত করে না — নতুন object হতে পারে, না-ও হতে পারে। HotSpot-এ
-XX:AutoBoxCacheMaxদিয়ে Integer-এর ওপরের সীমা বাড়ানো যায়; তখন128 == 128-ও true হতে পারে। তাই সীমার বাইরের==-এর ফলকে “false” নয়, “নির্ভরযোগ্য নয়” বলাই সঠিক। - অন্য wrapper: Long, Short, Byte, Character-এরও একই রকম cache আছে; Double আর Float-এর নেই।
- পুরোনো constructor:
new Integer(5)সবসময় নতুন object বানাত, cache এড়িয়ে। Java 9-এ এটি deprecated হয়, আর Java 16-এ (JEP 390) মুছে ফেলার জন্য চিহ্নিত — কারণ ভবিষ্যতে wrapper-কে value class বানানোর পরিকল্পনা আছে (Project Valhalla), যেখানে object-এর “পরিচয়” আর থাকবে না।
আরেকটি লুকানো ফাঁদ: Integer null হতে পারে। int n = map.get("x");-এ key না থাকলে unboxing-এ NullPointerException. আর Long l = 127L; l.equals(127) মিথ্যা — 127 box হয় Integer-এ, আর Long-এর equals অন্য টাইপকে কখনো সমান ধরে না।
মূল উপলব্ধি: wrapper object-এ == কখনো মান তুলনা নয়। মান তুলনায় equals (null হতে পারলে Objects.equals), অথবা আগে primitive-এ নামিয়ে ==. Integer cache একটি অপটিমাইজেশন, নির্ভর করার মতো আচরণ নয়।
প্র ০৫একই লেখা, তবু == কখনো true কখনো false — string pool আর compile-time constant আসলে কীভাবে কাজ করে?
Java-তে String একটি object, আর == দুটি reference একই object কি না দেখে। লেখা মিলছে কি না দেখে equals. তাহলে "java" == "java" কেন true? কারণ JLS §3.10.5 বলে: একই লেখার সব string literal একই String object-কে দেখায় — literal-গুলো intern করা হয়, একটি সাধারণ ভাণ্ডারে (string pool) রাখা হয়। String immutable বলেই এটা নিরাপদ: কেউ একটি কপি বদলে অন্যদের অবাক করতে পারে না।
- compile-time constant (JLS §15.29):
"ja" + "va"-এ দুটিই literal, তাই javac নিজেই জুড়ে"java"বানায় — class ফাইলে আলাদা জোড়ার কোড থাকেই না, আর ফলটি pool-এর literal. একই কথাfinal String p = "ja";দিয়েp + "va"-এর ক্ষেত্রে, কারণ final আর constant দিয়ে initialize করা local ভ্যারিয়েবলও constant variable. - চলার সময়ের জোড়া:
e + "va"-এ e সাধারণ ভ্যারিয়েবল, তাই জোড়া হয় চলার সময়, আর JLS §15.18.1 অনুযায়ী ফল একটি নতুন তৈরি String. Java 9-এ জোড়ার কৌশল বদলেছে (JEP 280, invokedynamic দিয়ে), কিন্তু “নতুন object” নিয়ম বদলায়নি। new String("java"): সবসময় নতুন object, pool-এর বাইরে। আরintern()ডাকলে pool-এর কপিটি ফেরত আসে।
ইতিহাসের একটি টুকরো: Java 7-এর আগে HotSpot-এ interned string থাকত PermGen নামের সীমিত মেমরি এলাকায়, তাই অতিরিক্ত intern() ডাকলে OutOfMemoryError: PermGen space হতো। Java 7-এ string pool সাধারণ heap-এ সরানো হয়, আর Java 8-এ PermGen নিজেই উঠে গিয়ে Metaspace আসে।
বাস্তব শিক্ষা হলো, এসব নিয়ম জানা দরকার ফাঁদ চেনার জন্য, ব্যবহারের জন্য নয়। == দিয়ে String তুলনা করা কোড পরীক্ষায় হয়তো true দেয় (দুটিই literal), কিন্তু বাস্তবে ব্যবহারকারীর ইনপুট, ফাইল বা নেটওয়ার্ক থেকে আসা String সবসময় চলার সময় তৈরি — সেখানে ফল false, আর বাগটি শুধু production-এ দেখা দেয়।
মূল উপলব্ধি: String তুলনায় সবসময় equals (বা null-নিরাপদ Objects.equals, ছোট-বড় হাতের অক্ষর না মানলে equalsIgnoreCase ব্যবহার করুন)। ==-এর true/false নির্ভর করে লেখাটি কখন তৈরি হলো তার ওপর — এমন জিনিসের ওপর যুক্তি দাঁড় করানো যায় না।
প্র ০৬Superclass-এর constructor থেকে override করা method ডাকলে subclass-এর field কেন 0 বা null দেখায় — Java কেন এটা আটকায় না?
Java-তে new Child() চলার মুহূর্তে JVM পুরো Child object-এর জন্য একবারে মেমরি নেয়, সব field-কে ডিফল্ট মান (0, null, false) দেয়, আর object-এর টাইপ শুরু থেকেই Child. তারপর constructor শিকল চলে: Child() প্রথমে super() ডাকে, Base() শেষ হলে তবেই Child-এর field initializer চলে। তাই Base()-এর ভেতর থেকে show() ডাকলে dynamic dispatch ঠিকই Child-এর override চালায় — কিন্তু Child-এর field তখনো ডিফল্ট মানে।
C++ এখানে উল্টো পথ নিয়েছে: base-এর constructor চলার সময় object-কে base টাইপ হিসেবেই ধরা হয়, তাই base-এর সংস্করণ চলে। এতে অর্ধেক-তৈরি অংশ ছোঁয়ার ঝুঁকি কমে, কিন্তু “override চলল না” বলে আরেক বিভ্রান্তি তৈরি হয়। Java সরল নিয়ম বেছে নিয়েছে — object সবসময় তার আসল টাইপের — আর এর ফল হিসেবে এই ফাঁদ রয়ে গেছে। মেমরি-নিরাপত্তা তবু নষ্ট হয় না: ডিফল্ট মান সবসময় দেওয়া থাকে, আবর্জনা মান কখনো দেখা যায় না।
- ব্যতিক্রম — constant variable:
final int k = 7;-এর মতো final আর compile-time constant দিয়ে initialize করা field-এর মান javac সরাসরি ব্যবহারের জায়গায় বসিয়ে দেয়, তাই সেটি constructor থেকেও 7 দেখায়। কিন্তু constructor-এ মান দেওয়া final field (final int k; ... k = 7;) তখনো 0. - বাস্তব বিপদ: field null থাকায় override করা method-এ NullPointerException, বা ভুল হিসাব — আর stack trace দেখে কারণ বোঝা কঠিন, কারণ কোডে স্পষ্ট লেখা আছে
String s = "hi".
Joshua Bloch তাঁর Effective Java-তে inheritance-এর জন্য ডিজাইন নিয়ে আলোচনায় স্পষ্ট নিয়ম দিয়েছেন: constructor কখনো (সরাসরি বা পরোক্ষে) override করা যায় এমন method ডাকবে না। সমাধানের উপায়: constructor থেকে শুধু private, final বা static method ডাকুন; দরকারি তথ্য superclass-এর constructor-এ প্যারামিটার হিসেবে পাঠান; অথবা দুই ধাপে তৈরি করুন — আগে object, তারপর একটি static factory থেকে init(). নতুন Java সংস্করণে (Java 22-এ preview হিসেবে শুরু, পরে Java 25-এ স্থায়ী) super(...)-এর আগে কিছু statement লেখার সুযোগ এসেছে, যাতে argument আগে যাচাই করা যায় — তবে এই ফাঁদের মূল নিয়ম তাতে বদলায় না।
মূল উপলব্ধি: Java-তে object-এর টাইপ জন্ম থেকেই চূড়ান্ত, কিন্তু তার field-গুলো ধাপে ধাপে পূর্ণ হয় — superclass থেকে subclass. Constructor-এর ভেতর থেকে কিছু ডাকার আগে জিজ্ঞেস করুন: “এই মুহূর্তে কোন field-গুলো আসলে তৈরি হয়েছে?”
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বে ব্যবহৃত সব শব্দ এখানে একসাথে:
মনে রাখুন
- আউটপুট অনুমান নয় — trace table এঁকে লাইন ধরে চালান: ভ্যারিয়েবল, call stack, class initialization আর collection-এর অবস্থা। আগে দেখুন compile হবে কি না।
- বাঁ থেকে ডানে: operand আর argument সবসময় বাঁ থেকে ডানে মূল্যায়ন হয়;
+প্রথম String পাওয়া পর্যন্ত যোগ, তারপর জোড়া; int-এর হিসাব int-এই হয়, বাঁ পাশে long থাকলেও। - static একবার, instance প্রতিবার: superclass আগে; field initializer আর instance block লেখার ক্রমে, super()-এর পরে, constructor-এর শরীরের আগে। Override ঠিক হয় object দেখে; field, static method আর overload ঠিক হয় ঘোষিত টাইপ দেখে।
- উত্তর চার রকম: নির্দিষ্ট আউটপুট, compile error, কিছু আউটপুটের পরে নির্দিষ্ট exception, আর “নিশ্চিত নয়” — শেষ তিনটির ক্ষেত্রে কারণ আর ঠিক নাম লিখুন।
- AI-এর আউটপুট-অনুমান যাচাই করুন: নিজে trace করুন, JDK 21-এ চালান, “নিশ্চিত নয়” ধরনের কোড কয়েকবার চালান, আর সন্দেহ হলে javap ও JLS দেখুন।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ৫৭ · MCQ প্রশ্নব্যাংক — যেকোনো পরীক্ষার প্রস্তুতিপরবর্তী পর্বপুরো কোর্সের ধারণা যাচাইয়ের বিষয়ভিত্তিক MCQ.
- পর্ব ৫৮ · লিখিত ও মৌখিক (ভাইভা) প্রশ্নলিখিত পরীক্ষা“পার্থক্য লিখুন”, “class লিখুন” আর ভাইভার উত্তর সাজানোর কৌশল।
- পর্ব ৫৯ · Java কোডিং ইন্টারভিউ — সমস্যা থেকে সমাধানইন্টারভিউসমস্যা বোঝা, পরিকল্পনা, কোড আর টেস্ট — ইন্টারভিউর পুরো ধারা।
- পর্ব ০৭ · অপারেটর — হিসাব, তুলনা আর যুক্তিগভীরে যানinteger division, promotion,
i = i++আর short-circuit-এর পুরো ব্যাখ্যা। - C++ প্রোগ্রামিং · আউটপুট কী হবে?C++ কোর্সconstructor-destructor-এর ক্রম, virtual আর move নিয়ে আউটপুট প্রশ্ন — Java-র সাথে তুলনা করে পড়ুন।
- Java প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — যে ভাগে ভুল বেশি হলো, সেই পর্বে ফিরে যান।