এই পর্বে যা শিখবেন
- কোন পরিস্থিতিতে কোন লুপ —
for,while,do-whileনা for-each — তা যুক্তি দিয়ে বেছে নেওয়া forলুপ ঠিক কোন ক্রমে চলে, আর loop variable-এর scope কোথায় শেষ হয়break,continue, labeled break আর nested loop — নামতা থেকে তারার প্যাটার্ন পর্যন্ত- অসীম লুপ, off-by-one আর
ConcurrentModificationException— কেন ঘটে, কীভাবে এড়াবেন - Sum, count, max, min-এর accumulator প্যাটার্ন, তার আধুনিক Stream বিকল্প, আর AI-এর লেখা লুপ যাচাই
১ · লুপ কেন — একই কাজ বারবার, শর্ত যতক্ষণ সত্য
পর্ব ১০-এ আপনি শিখেছেন প্রোগ্রাম কীভাবে সিদ্ধান্ত নেয় — শর্ত সত্য হলে এক পথে, মিথ্যা হলে আরেক পথে। কিন্তু সেই সিদ্ধান্ত হয় একবারই। এখন ভাবুন, একটি দোকানের সারাদিনের বিক্রি যোগ করতে হবে। বিক্রি হয়েছে ৩টি, না ৩০০টি — আগে থেকে জানা নেই। প্রতিটির জন্য একটি করে লাইন লেখা অসম্ভব।
এখানেই আসে loop. লুপের মূল ধারণা একটি বাক্যে বলা যায়: একটি শর্ত যতক্ষণ সত্য, ততক্ষণ একই কাজ বারবার করো। প্রতিবার কাজটি একবার করাকে বলে একটি iteration — বাংলায় একটি “ঘোরা”। প্রতিটি ঘোরার আগে (বা পরে) শর্তটি আবার যাচাই হয়। শর্ত মিথ্যা হলেই লুপ শেষ, আর প্রোগ্রাম লুপের পরের লাইনে চলে যায়।
ক্লাসে রোল কল ভাবুন। শিক্ষক খাতা খুলে রোল ১ ডাকেন, উত্তর শোনেন, টিক দেন, তারপর রোল ২। কাজটি একই — শুধু রোল নম্বর বদলায়। আর থামার নিয়ম স্পষ্ট: শেষ রোল ডাকা হলে থামো। এটিই একটি লুপ: শুরু (রোল ১), শর্ত (রোল কি শেষ রোলের চেয়ে বড় হয়নি?), কাজ (ডাকো, টিক দাও), আর পরিবর্তন (পরের রোলে যাও)।
Java আপনাকে চারটি লুপ দেয়। এরা সবাই একই কাজ পারে — যেকোনো একটি দিয়ে বাকিগুলো বানানো যায়। তবু চারটি আছে, কারণ প্রতিটি একটি আলাদা প্রশ্নের সবচেয়ে পরিষ্কার উত্তর। সঠিক লুপ বাছলে কোড পড়েই বোঝা যায় লেখক কী ভাবছিলেন।
🔢 for — গোনা লুপ
কতবার ঘুরবে, তা আগেই জানা। ১২ মাসের সঞ্চয়, ১ থেকে ১০-এর নামতা, ৫০ জন ছাত্রের রোল।
“ঠিক N বার”⏳ while — ঘটনা ঘটা পর্যন্ত
কতবার, তা আগে জানা নেই; কিছু একটা ঘটলে থামবে। দোকানদার 0 লিখলে, ফাইল শেষ হলে, সঠিক পাসওয়ার্ড দিলে।
“যতক্ষণ না…”🔁 do-while — অন্তত একবার
কাজটি আগে একবার করতেই হবে, তারপর শর্ত দেখা। মেনু দেখানো, ইনপুট চেয়ে যাচাই করা।
“আগে করো, পরে দেখো”📋 for-each — প্রতিটি জিনিস
একটি array বা তালিকার প্রতিটি জিনিসে একবার করে যাও — index নিয়ে মাথা ঘামাতে হয় না।
“সবগুলোর জন্য”প্রতিটি লুপের তিনটি প্রশ্নের উত্তর থাকতেই হবে: কোথা থেকে শুরু? কখন থামবে? প্রতিবার কী বদলাবে যাতে একদিন থামে? তৃতীয় প্রশ্নের উত্তর না থাকলে লুপ কখনো থামে না। লুপের প্রায় সব বাগ এই তিন প্রশ্নের কোনো একটির ভুল উত্তর।
২ · for লুপ — শুরু; শর্ত; পরিবর্তন
for লুপ উপরের তিনটি প্রশ্নের উত্তর এক লাইনে, পাশাপাশি লিখতে দেয়। এর গঠন:
- init — শুরুতে একবারই চলে। সাধারণত এখানে একটি loop variable তৈরি হয়, যেমন
int i = 0. - condition — প্রতিটি ঘোরার আগে যাচাই হয়।
trueহলে body চলে,falseহলে লুপ শেষ। Java-তে এটি অবশ্যইbooleanহতে হবে। - update — প্রতিটি ঘোরার শেষে চলে, যেমন
i++. এটিই লুপকে থামার দিকে এগিয়ে নেয়।
লেখার ক্রম আর চলার ক্রম এক নয় — update লেখা থাকে শর্তের পরে, কিন্তু চলে body-র পরে। নিচের ছোট সঞ্চয়-হিসাবে প্রতি মাসে ৫০০ টাকা জমা হয়, দুই মাসের জন্য। “পরে” চেপে দেখুন কোন অংশ কখন চলে:
1int total = 0;2for (int m = 1; m <= 2; m++) {3 total += 500;4}5System.out.println("জমা: " + total);
| এখন চলছে | |
| m | |
| m ≤ 2? | |
| total |
- লুপের আগে।
totalতৈরি হলো 0 মান নিয়ে। এটি লুপের বাইরে, তাই লুপ শেষ হলেও থাকবে। - init — একবারই।
int m = 1চলল। পুরো লুপে এই অংশ আর কখনো চলবে না।mজন্ম নিল লুপের ভেতরে। - প্রথম শর্ত যাচাই।
1 <= 2সত্য, তাই body-তে ঢোকার অনুমতি মিলল। খেয়াল করুন, শর্ত যাচাই হয় body-র আগে — শর্ত শুরুতেই মিথ্যা হলে body একবারও চলত না। - body — প্রথম মাস।
totalহলো 500. - update. body শেষ, এবার
m++—mহলো 2. লেখায় update শর্তের পাশে থাকলেও চলে body-র পরে। - আবার শর্ত।
2 <= 2— এখনো সত্য (<=সমানকেও ধরে)। - body — দ্বিতীয় মাস।
totalহলো 1000. - update.
mহলো 3. লুপ এখনো জানে না যে শেষ — সেটা জানবে পরের যাচাইয়ে। - শর্ত মিথ্যা — লুপ শেষ।
3 <= 2মিথ্যা। body আর চলে না; প্রোগ্রাম লুপের পরের লাইনে লাফ দেয়। শেষেm-এর মান 3, কিন্তু সেটা আর কেউ দেখতে পায় না। - লুপের পরে।
mএখন আর নেই — সে ছিল শুধু লুপের ভেতরের ভ্যারিয়েবল।totalবেঁচে আছে, তাই ছাপা গেলজমা: 1000. মোট: শর্ত যাচাই ৩ বার, body ২ বার — শর্ত সবসময় body-র চেয়ে একবার বেশি যাচাই হয়।
loop variable কোথায় বাঁচে — scope
init-এ তৈরি ভ্যারিয়েবলের scope শুধু লুপের ভেতরে। লুপ শেষ হলে নামটিই মুছে যায়। নিচের কোড compile-ই হবে না:
for (int i = 0; i < 3; i++) {
System.out.println(i);
}
System.out.println(i); // লুপের বাইরে i নেইMain.java:6: error: cannot find symbol
System.out.println(i);
^
symbol: variable i
location: class Main
1 error
এটি বিরক্তিকর নয়, বরং সুরক্ষা। একটি বড় method-এ পাঁচটি লুপ থাকলে পাঁচটিতেই নিশ্চিন্তে i নাম ব্যবহার করা যায়, কেউ কারও মান নষ্ট করে না। লুপের পরে শেষ মানটি সত্যিই দরকার হলে ভ্যারিয়েবলটি লুপের আগে তৈরি করুন: int i; for (i = 0; i < 3; i++) { … }.
তিনটি অংশের যেকোনোটি খালি রাখা যায়, তবে দুটি ; থাকতেই হবে। for (;;) মানে “শর্ত নেই, তাই সবসময় সত্য” — একটি ইচ্ছাকৃত অসীম লুপ। init আর update-এ কমা দিয়ে একাধিক কাজও লেখা যায়: for (int i = 0, j = 9; i < j; i++, j--) — দুই প্রান্ত থেকে মাঝের দিকে আসার জন্য কাজে লাগে।
৩ · while আর do-while — কখন থামবে আগে জানা নেই
একটি মুদি দোকানে দিনের শেষে দোকানদার একটি একটি করে বিক্রির টাকা কম্পিউটারে লেখেন। সব লেখা হলে লেখেন 0 — মানে “আজকের হিসাব শেষ”। প্রোগ্রাম জানে না কয়টি সংখ্যা আসবে; জানে শুধু কোন সংকেতে থামতে হবে। এই বিশেষ থামার সংকেতকে বলে sentinel (পাহারাদার)। এমন কাজের জন্য সবচেয়ে মানানসই while লুপ:
for-এর মতো init বা update-এর জন্য আলাদা জায়গা নেই — শুধু শর্ত। শুরু করার কাজ লুপের আগে, আর পরিবর্তনের কাজ body-র ভেতরে নিজেকে করতে হয়। ইনপুট পড়া হয় Scanner দিয়ে (পর্ব ০৯)। ধরুন দোকানদার লিখলেন 250, 1200, 480, তারপর 0:
1int total = 0;2int sale = in.nextInt();3while (sale != 0) {4 total += sale;5 sale = in.nextInt();6}7System.out.println("মোট " + total);
| sale | |
| sale ≠ 0? | |
| total | |
| ইনপুটে বাকি |
- লুপের আগে প্রথম পড়া।
total= 0, আর লুপে ঢোকার আগেই প্রথম সংখ্যা 250 পড়া হলো। এই “আগে একবার পড়ে নেওয়া”-কে বলে priming read — শর্তে দেখার মতো একটি মান হাতে থাকতে হবে তো। - শর্ত: 250 ≠ 0. সত্য — এটি আসল বিক্রি, sentinel নয়। body-তে ঢোকো।
- যোগ, তারপর পরের সংখ্যা।
totalহলো 250, আর body-র শেষ লাইনে পরের সংখ্যা 1200 পড়া হলো। এই লাইনটিই লুপকে থামার দিকে নেয় — এটি ভুলে গেলেsaleচিরকাল 250 থাকত, লুপ কখনো থামত না। - শর্ত: 1200 ≠ 0. সত্য।
- যোগ, তারপর পরের সংখ্যা।
total= 1450, নতুনsale= 480. - শর্ত: 480 ≠ 0. সত্য।
- যোগ, তারপর পরের সংখ্যা।
total= 1930, আর এবার পড়া হলো 0 — sentinel. লক্ষ করুন, 0 পড়া হলেও এখনো যোগ হয়নি। - শর্ত: 0 ≠ 0 — মিথ্যা। লুপ শেষ। body আর চলে না, তাই sentinel 0 কখনো যোগফলে ঢোকে না। ক্রমটা খেয়াল করুন: পড়ো → যাচাই করো → ব্যবহার করো → পড়ো। ব্যবহার আর পড়ার ক্রম উল্টে দিলে sentinel-ও হিসাবে ঢুকে যেত।
- ফল।
মোট 1930. ইনপুটে ১০টি বিক্রি থাকলেও কোড একই থাকত — while লুপ সংখ্যাটা আগে জানতে চায় না।
এবার একই ধারণা পুরো প্রোগ্রামে, সাথে গোনা (count), সবচেয়ে বড় (max) আর সবচেয়ে ছোট (min) বিক্রি। নিচের রান বোতাম চাপুন:
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int total = 0, count = 0;
int max = Integer.MIN_VALUE, min = Integer.MAX_VALUE;
int sale = in.nextInt(); // প্রথম বিক্রি
while (sale != 0) { // 0 = আজকের হিসাব শেষ
total += sale;
count++;
max = Math.max(max, sale);
min = Math.min(min, sale);
sale = in.nextInt(); // পরের বিক্রি
}
if (count == 0) {
System.out.println("আজ কোনো বিক্রি হয়নি");
} else {
System.out.println("বিক্রি " + count + "টি, মোট " + total + " টাকা");
System.out.println("সবচেয়ে বড় " + max + ", সবচেয়ে ছোট " + min);
System.out.println("গড় " + (total / count) + " টাকা");
}
}
}ইনপুট হিসেবে দেওয়া আছে চারটি লাইন: 250, 1200, 480 আর 0. আউটপুট আসে বিক্রি 3টি, মোট 1930 টাকা, সবচেয়ে বড় 1200, সবচেয়ে ছোট 250 আর গড় 643 টাকা. চারটি ভ্যারিয়েবল — total, count, max, min — প্রতিটি ঘোরায় একটু একটু করে ফল জমায়; এদের বলে accumulator (অংশ ৮-এ বিস্তারিত)। max শুরু হয়েছে সবচেয়ে ছোট সম্ভাব্য int দিয়ে, যাতে প্রথম বিক্রিই তাকে ছাড়িয়ে যায়; min ঠিক উল্টো। count == 0 যাচাইটি জরুরি — প্রথম সংখ্যাই 0 হলে total / count শূন্য দিয়ে ভাগ হয়ে ArithmeticException দিত। গড় 643, কারণ দুটি int-এর ভাগে দশমিক অংশ বাদ যায় (পর্ব ০৭)। ইনপুট বদলে নিজে চালিয়ে দেখুন। (নিজের প্রজেক্টে main class সাধারণত public class Main, ফাইল Main.java — এই পাতার রানারের জন্য public বাদ রাখা হয়েছে।)
do-while — আগে একবার, তারপর শর্ত
bKash-এর USSD মেনু ভাবুন: মেনুটি অন্তত একবার দেখাতেই হবে, তারপর ব্যবহারকারী বেছে নেবেন — আবার দেখাব, না বের হব। এখানে শর্ত যাচাই body-র পরে হওয়াই স্বাভাবিক। এজন্য আছে do-while লুপ:
Scanner in = new Scanner(System.in);
int choice; // লুপের বাইরে — নিচের শর্তে লাগবে
do {
System.out.println("1. ব্যালান্স 2. টাকা পাঠান 0. বের হন");
choice = in.nextInt();
// পছন্দ অনুযায়ী কাজ — পর্ব ১০-এর switch এখানে বসবে
} while (choice != 0); // শেষে সেমিকোলন লাগেদুটি জিনিস খেয়াল করুন। প্রথমত, while (...)-এর পরে ; বাধ্যতামূলক — শুধু do-while-এ। দ্বিতীয়ত, choice তৈরি হয়েছে লুপের বাইরে। body-র { }-এর ভেতরে তৈরি করলে নিচের শর্তে তাকে দেখা যেত না, কারণ শর্তটি block-এর বাইরে — সেই scope-এর নিয়ম। বাস্তবে do-while তুলনামূলক কম ব্যবহৃত হয়; বেশিরভাগ কাজ while বা for দিয়েই পরিষ্কারভাবে হয়ে যায়। তবে “অন্তত একবার” কথাটি সত্যিই সমস্যার অংশ হলে do-while-ই সৎ উত্তর।
৪ · for-each — index ছাড়াই প্রতিটি জিনিস
বেশিরভাগ লুপের আসল কাজ একটাই: একটি তালিকার প্রতিটি জিনিস একবার করে দেখা। Index দিয়ে করলে তিনটি জিনিস ঠিক রাখতে হয় — শুরু 0 থেকে, থামা length-এর আগে, আর প্রতিবার i++. তিনটিতেই ভুলের সুযোগ। Java 5 (২০০৪) থেকে এর একটি সরল রূপ আছে — enhanced for, যাকে সবাই বলে for-each লুপ:
int[] marks = {72, 28, 20}; // array — বিস্তারিত পর্ব ১৩-এ
for (int m : marks) { // পড়ুন: "marks-এর প্রতিটি m-এর জন্য"
System.out.println(m);
}
List<String> subjects = List.of("বাংলা", "গণিত", "ইংরেজি");
for (String s : subjects) { // List-এও একইভাবে (পর্ব ৩০)
System.out.println(s);
}এখানে array আর List দুটোই ছোট উদাহরণ হিসেবে ব্যবহার করা হয়েছে — array পুরোপুরি শিখবেন পর্ব ১৩-এ, আর List পর্ব ৩০-এ। আপাতত এটুকু জানলেই চলবে: দুটোতেই কয়েকটি মান সারি বেঁধে থাকে। কোলনটিকে (:) পড়ুন “এর মধ্যে” — marks-এর মধ্যের প্রতিটি m-এর জন্য। কোনো index নেই, কোনো length নেই, তাই off-by-one ভুলের সুযোগও নেই। Java 10 থেকে টাইপের জায়গায় var লেখা যায়: for (var s : subjects).
ভেতরে কী হয়? Array হলে compiler নিজেই একটি সাধারণ index লুপ বানিয়ে নেয়। আর List-এর মতো collection হলে ব্যবহার করে একটি Iterator — একটি ছোট object, যে মনে রাখে কোথায় আছে আর “পরেরটা আছে?” (hasNext()) ও “পরেরটা দাও” (next()) প্রশ্নের উত্তর দেয়। যে class Iterable interface মানে, তার ওপর for-each চলে। তাই আপনার নিজের বানানো class-ও চাইলে for-each-এ চালানো যায় (interface পর্ব ২১-এ)।
কখন তবু index লাগে
for-each সবসময় যথেষ্ট নয়। চারটি পরিস্থিতিতে সাধারণ for লাগে:
- অবস্থান জানতে হলে — “৩ নম্বর বিষয়ে ফেল”। for-each জিনিস দেয়, তার ক্রমিক নম্বর দেয় না।
- দুটি তালিকা পাশাপাশি চালাতে হলে —
subjects[i]আরmarks[i]একসাথে। - উল্টো দিকে বা লাফিয়ে চলতে হলে — শেষ থেকে শুরুতে, বা প্রতি দ্বিতীয়টি।
- array-র ঘরে নতুন মান বসাতে হলে — এটি সবচেয়ে বিভ্রান্তিকর, নিচে দেখুন।
for-each-এর ভ্যারিয়েবল একটি কপি। for (int m : marks) { m = m + 5; } লিখলে প্রত্যেককে ৫ নম্বর গ্রেস দেওয়া হলো মনে হয়, কিন্তু array একটুও বদলায় না — m প্রতিটি ঘোরায় ঘরের মানের একটি কপি মাত্র। ঘরের মান বদলাতে লিখুন for (int i = 0; i < marks.length; i++) marks[i] += 5;. (তালিকায় object থাকলে অবশ্য m দিয়ে সেই object-এর ভেতরটা বদলানো যায় — কারণ কপি হয় reference, object নয়। পর্ব ১২-এর pass-by-value-তে এই একই নিয়ম আবার দেখবেন।)
৫ · break আর continue — লুপ থামানো আর একটি ঘোরা বাদ দেওয়া
কখনো শর্ত যাচাইয়ের নির্দিষ্ট জায়গার আগেই লুপ থামানো দরকার। ধরুন রেজাল্ট দেখছেন, আর জানতে চান প্রথম কোন বিষয়ে ফেল। প্রথমটি পেয়ে গেলে বাকি বিষয় দেখার কোনো মানে নেই। এজন্য break — এটি সবচেয়ে কাছের লুপটি সঙ্গে সঙ্গে শেষ করে দেয় (পর্ব ১০-এ switch-এর ভেতরেও break দেখেছেন — কাজ একই, “এই কাঠামো থেকে বেরিয়ে যাও”)।
আর continue লুপ থামায় না — শুধু এই ঘোরার বাকি অংশ বাদ দিয়ে পরের ঘোরায় চলে যায়। for লুপে continue-এর পর update (i++) ঠিকই চলে, তারপর শর্ত। নিচের প্রোগ্রামে দুটোই আছে — একজনের ১২ মাসের সঞ্চয় পরিকল্পনা (প্রতি তিন মাসে একবার ছাপা), আর প্রথম ফেল করা বিষয় খোঁজা:
class Main {
public static void main(String[] args) {
int monthly = 1500; // প্রতি মাসে জমা, টাকা
int saved = 0;
for (int month = 1; month <= 12; month++) {
saved += monthly;
if (month == 6) monthly += 500; // ৬ মাস পর জমা বাড়ল
if (month % 3 != 0) continue; // শুধু প্রতি ৩ মাসে ছাপো
System.out.println("মাস " + month + ": " + saved + " টাকা");
}
String[] subjects = {"বাংলা", "গণিত", "ইংরেজি"};
int[] marks = {72, 28, 20};
for (int i = 0; i < subjects.length; i++) {
if (marks[i] < 33) {
System.out.println("প্রথম ফেল: " + subjects[i] + " (" + marks[i] + ")");
break; // ইংরেজি আর দেখাই হবে না
}
}
}
}আউটপুট: মাস 3: 4500 টাকা, মাস 6: 9000 টাকা, মাস 9: 15000 টাকা, মাস 12: 21000 টাকা, তারপর প্রথম ফেল: গণিত (28). প্রথম ছয় মাসে ১৫০০ করে ৯০০০, পরের ছয় মাসে ২০০০ করে আরও ১২০০০. যে মাস ৩ দিয়ে বিভাজ্য নয়, সেখানে continue ছাপার লাইনটি বাদ দেয় — কিন্তু যোগ আগেই হয়ে গেছে, আর month++-ও চলে। দ্বিতীয় লুপে ইংরেজিতেও ২০ নম্বর, তবু ছাপা হয়নি: গণিতে break পুরো লুপ শেষ করে দিয়েছে। এখানে for-each না নিয়ে index-লুপ নেওয়া হয়েছে, কারণ দুটি array পাশাপাশি চালাতে হচ্ছে — অংশ ৪-এর “কখন তবু index লাগে” তালিকার দ্বিতীয় পরিস্থিতি।
break বেশি হলে লুপ পড়া কঠিন। একটি লুপে একটি break সাধারণত পরিষ্কার: “পেয়ে গেলে থামো”। কিন্তু পাঁচ জায়গা থেকে break আর তিন জায়গায় continue থাকলে পাঠককে প্রতিটি পথ মাথায় রাখতে হয়। তখন ভাবুন — থামার শর্তটি লুপের নিজের শর্তে লেখা যায় কি না, বা খোঁজার কাজটি একটি আলাদা method-এ নিয়ে return দিয়ে বের হওয়া যায় কি না (পর্ব ১২)।
৬ · লুপের ভেতরে লুপ — নামতা, প্যাটার্ন আর labeled break
একটি লুপের body-তে আরেকটি লুপ থাকতে পারে — একে বলে nested loop. বাইরের লুপের প্রতিটি ঘোরায় ভেতরের লুপ পুরোটা চলে। ঘড়ির কাঁটার মতো: মিনিটের কাঁটা একবার ঘুরলে ঘণ্টার কাঁটা এক ঘর এগোয়। বাইরের লুপ ৪ বার আর ভেতরের ৪ বার হলে ভেতরের body চলে ৪ × ৪ = ১৬ বার।
ধরুন ছোট ভাইয়ের জন্য ১ থেকে ৪-এর নামতার একটি ছক বানাবেন। বাইরের লুপ সারি (i), ভেতরের লুপ কলাম (j):
for (int i = 1; i <= 4; i++) { // প্রতিটি সারি
for (int j = 1; j <= 4; j++) { // সেই সারির প্রতিটি কলাম
System.out.printf("%4d", i * j); // ৪ ঘর জায়গা নিয়ে ছাপো
}
System.out.println(); // সারি শেষ — নতুন লাইন
} 1 2 3 4
2 4 6 8
3 6 9 12
4 8 12 16
তারার প্যাটার্ন একই কৌশল, শুধু ভেতরের লুপ কতবার চলবে তা বাইরের লুপের ওপর নির্ভর করে — সারি ১-এ ১টি তারা, সারি ২-এ ২টি:
for (int row = 1; row <= 4; row++) {
for (int star = 1; star <= row; star++) { // row যত, তারা তত
System.out.print("*");
}
System.out.println();
}*
**
***
****
(Java 11 থেকে ভেতরের লুপটি এক লাইনে লেখা যায়: System.out.println("*".repeat(row)); — তবে প্যাটার্ন বানানোর প্রশ্নে পরীক্ষক সাধারণত nested loop-ই দেখতে চান।)
দুই লুপ থেকে একবারে বের হওয়া — labeled break
একটি সমস্যা আছে: ভেতরের লুপে break লিখলে শুধু ভেতরের লুপ থামে, বাইরেরটি পরের সারিতে চলতে থাকে। কিন্তু ধরুন খুঁজছেন প্রথম কোন সারি-কলামে গুণফল 4 — পেয়ে গেলে দুটো লুপই থামাতে চান। Java-র উত্তর labeled break: বাইরের লুপের আগে একটি নাম (label) আর কোলন বসান, তারপর break সেই_নাম;:
1outer:2for (int r = 1; r <= 3; r++) {3 for (int c = 1; c <= 3; c++) {4 if (r * c == 4) {5 System.out.println(r + " x " + c);6 break outer;7 }8 }9}10System.out.println("শেষ");
| r (বাইরের) | |
| c (ভেতরের) | |
| r * c |
- r = 1, c = 1. গুণফল 1 — মেলেনি। ভেতরের লুপ এগোয়।
- r = 1, c = 2. গুণফল 2 — মেলেনি।
- r = 1, c = 3. গুণফল 3 — মেলেনি। এরপর
cহবে 4, ভেতরের শর্ত মিথ্যা, ভেতরের লুপ শেষ। - বাইরের লুপ এক ঘর এগোল।
r= 2, আর ভেতরের লুপ একদম নতুন করে শুরু:c= 1 (ভেতরের init আবার চলেছে)। গুণফল 2 — মেলেনি। - r = 2, c = 2. গুণফল 4 — মিলেছে!
if-এর ভেতরে ঢোকো। - ছাপা।
2 x 2. break outer. শুধু ভেতরের নয়,outerনামের বাইরের লুপটিও শেষ।r= 3 কখনো আসবে না,rআরcদুটোই scope হারাল। সাধারণbreakলিখলে কী হতো? শুধু ভেতরের লুপ থামত, তারপরr= 3-এর সারি চলত — অপ্রয়োজনীয় কাজ, আর অন্য উদাহরণে ভুল ফলও।- লুপের পরের লাইন।
শেষছাপা হলো। মোট ৯টির জায়গায় মাত্র ৫টি জোড়া দেখা হয়েছে।
একইভাবে continue outer; ভেতরের লুপের বাকি অংশ বাদ দিয়ে সরাসরি বাইরের লুপের পরের ঘোরায় চলে যায় — যেমন “এই সারিতে একটি খারাপ মান পেলেই পুরো সারিটা বাদ”।
সৎ পরামর্শ: labeled break বৈধ, পরিষ্কার, আর মাঝে মাঝে সত্যিই সবচেয়ে ভালো সমাধান। তবে বাস্তব কোডে এটি বিরল, আর অনেক দল code review-তে এটি দেখলে প্রশ্ন তোলে। প্রায়ই আরও পরিষ্কার বিকল্প হলো nested loop-টিকে একটি আলাদা method-এ রাখা আর পেয়ে গেলে return করা — method-এর নামই বলে দেয় লুপটি কী খুঁজছে (পর্ব ১২)। Label ব্যবহার করলে নামটি অর্থপূর্ণ দিন (outer-এর চেয়ে searchRows ভালো), আর একটি method-এ একটির বেশি label রাখবেন না।
C/C++-এর for, while, do-while প্রায় হুবহু একই। পার্থক্য: Java-তে শর্ত অবশ্যই boolean — while (1) বা while (n) compile হয় না (incompatible types: int cannot be converted to boolean), আর nested loop থেকে বের হতে C-র মতো goto নেই, আছে labeled break (তুলনার জন্য C-র লুপ পর্ব)। Python-এর for x in items আসলে Java-র for-each; Python-এ do-while আর labeled break নেই, কিন্তু লুপের সাথে else আছে, যা Java-তে নেই (Python-এর লুপ পর্ব)।
৭ · লুপের তিন চেনা ফাঁদ — অসীম লুপ, off-by-one আর ঘোরার মাঝে তালিকা বদলানো
ফাঁদ ১ — যে লুপ কখনো থামে না
অসীম লুপ দুই রকম। ইচ্ছাকৃত — যেমন একটি সার্ভার যে সারাদিন অনুরোধের অপেক্ষায় থাকে: while (true) { … }, ভেতরে কোনো এক জায়গায় পরিষ্কার break বা return. আর অনিচ্ছাকৃত — যেখানে অংশ ১-এর তৃতীয় প্রশ্নের (“প্রতিবার কী বদলাবে?”) উত্তর ভুল। কয়েকটি চেনা উদাহরণ:
| কোড | কেন থামে না |
|---|---|
while (i < 10) { sum += i; } | i++ ভুলে গেছেন — i কখনো বদলায় না |
sale = in.nextInt(); লুপের বাইরে শুধু একবার | sentinel লুপে body-তে পরের ইনপুট পড়া হয়নি |
for (byte b = 0; b < 200; b++) | byte-এর সর্বোচ্চ মান 127; তার পরে ঘুরে -128 হয়ে যায়, তাই শর্ত চিরকাল সত্য |
for (double x = 0; x != 1.0; x += 0.1) | দশবার 0.1 যোগে ঠিক 1.0 হয় না (0.9999999999999999), তাই != চিরকাল সত্য — দশমিক সংখ্যায় != দিয়ে থামার শর্ত দেবেন না |
অসীম লুপে প্রোগ্রাম “ঝুলে” যায়, CPU-র একটি core পুরো ব্যস্ত থাকে। Terminal-এ Ctrl + C চেপে থামান; IDE-তে লাল Stop বোতাম। আর অভ্যাস করুন: প্রতিটি লুপ লেখার পর নিজেকে জিজ্ঞেস করুন — “কোন লাইনটি শর্তকে মিথ্যার দিকে এগিয়ে নিচ্ছে?”
ফাঁদ ২ — এক ঘর বেশি বা কম: off-by-one
প্রোগ্রামিংয়ের সবচেয়ে পুরোনো আর সবচেয়ে সাধারণ ভুলগুলোর একটি off-by-one error — লুপ এক বার বেশি বা এক বার কম চলা। মূল কারণ: Java-র array-র index শুরু হয় 0 থেকে, তাই ৩টি ঘরের index 0, 1, 2 — শেষ ঘরটি length - 1, length নয়। কেউ অভ্যাসবশে <= লিখলেই বিপদ:
1int[] marks = {72, 28, 20};2for (int i = 0; i <= marks.length; i++) {3 System.out.println(marks[i]);4}
| i | |
| i ≤ 3? | |
| marks[i] |
- তিন ঘরের array.
marks.length= 3, কিন্তু বৈধ index মাত্র 0, 1 আর 2. - i = 0.
0 <= 3সত্য, ঘর 0 থেকে 72 ছাপা হলো। সব ঠিক। - i = 1. ঘর 1 থেকে 28.
- i = 2. ঘর 2 থেকে 20. সব ঘর দেখা শেষ — এখানেই লুপের থামা উচিত ছিল।
- i = 3 — এক ঘর বেশি।
3 <= 3সত্য, কারণ<=সমানকেও ধরে।marks[3]চাওয়া হলো, যে ঘর নেই। JVM প্রতিটি array-ব্যবহারের সময় সীমা যাচাই করে, তাই সাথে সাথে ছুড়ে দেয় ArrayIndexOutOfBoundsException, আর প্রোগ্রাম থেমে যায়। বার্তাটি নিজেই সব বলে: চাওয়া index 3, অথচ length 3. (C-তে এমন ভুলে কোনো সতর্কতা ছাড়াই পাশের মেমরি পড়া হতো — Java-র এই যাচাই আসলে আপনার সুরক্ষা।) - সমাধান। শর্ত
i < marks.length— “length-এর চেয়ে ছোট”। মনে রাখার নিয়ম: 0 থেকে শুরু হলে<, 1 থেকে শুরু হলে<=. আর index-এর দরকারই না থাকলে for-each নিন — সেখানে এই ভুল করাই সম্ভব নয়।
off-by-one শুধু array-তে নয়। “১ থেকে ১০ পর্যন্ত” মানে ১০ সহ, তাই i <= 10. “১০ বার” মানে for (int i = 0; i < 10; i++) অথবা for (int i = 1; i <= 10; i++) — দুটোই ঠিক, শুধু মিশিয়ে ফেলবেন না। সন্দেহ হলে সবচেয়ে ছোট ক্ষেত্রটি হাতে চালিয়ে দেখুন: তালিকায় ১টি জিনিস থাকলে লুপ কতবার চলে? ০টি থাকলে?
ফাঁদ ৩ — for-each চলার সময় তালিকা থেকে মোছা
ধরুন একটি ArrayList-এ নম্বর আছে, আর ফেল করা (৩৩-এর কম) নম্বরগুলো মুছে দিতে চান। স্বাভাবিক চিন্তা — for-each দিয়ে ঘুরি, কম পেলে মুছি:
List<Integer> marks = new ArrayList<>(List.of(30, 25, 80, 20, 90));
for (Integer m : marks) {
if (m < 33) marks.remove(m); // ঘোরার মাঝে তালিকা বদলানো — ভুল
}Exception in thread "main" java.util.ConcurrentModificationException
at java.base/java.util.ArrayList$Itr.checkForComodification(ArrayList.java:...)
at java.base/java.util.ArrayList$Itr.next(ArrayList.java:...)
at Main.main(Main.java:...)
অংশ ৪-এ দেখেছেন, for-each ভেতরে একটি Iterator ব্যবহার করে। Iterator মনে রাখে তালিকাটি কতবার বদলেছে। আপনি তাকে না জানিয়ে সরাসরি marks.remove ডাকলে পরের next()-এ সে টের পায় যে তালিকা তার অজান্তে বদলে গেছে, আর সাথে সাথে ConcurrentModificationException ছুড়ে দেয়। নামে “concurrent” থাকলেও এখানে কোনো thread নেই — একটিমাত্র লুপই যথেষ্ট। এই আচরণকে বলে fail-fast: ভুল ফল চুপচাপ দেওয়ার চেয়ে সাথে সাথে থেমে যাওয়া ভালো। (তবে এটি নিশ্চয়তা নয় — যেমন শেষের আগের জিনিসটি মুছলে লুপ কখনো কখনো কোনো exception ছাড়াই চুপচাপ শেষ হয়ে যায়, আর শেষ জিনিসটি দেখাই হয় না। তাই exception না এলেও কোডটি ভুল।)
সঠিক উপায় এক লাইনের: marks.removeIf(m -> m < 33); — ফল [80, 90]. এর ভেতরের m -> m < 33 অংশটি একটি lambda (পর্ব ৩৪)। আরেকটি উপায় হলো Iterator নিজের হাতে চালিয়ে it.remove() ডাকা। List-এর পুরো গল্প পর্ব ৩০-এ।
৮ · Accumulator প্যাটার্ন, আধুনিক বিকল্প আর লুপের গতি
অংশ ৩-এর দোকানের প্রোগ্রামে চারটি ভ্যারিয়েবল প্রতিটি ঘোরায় একটু একটু করে ফল জমাচ্ছিল। এই কাঠামো এত বেশি দেখা যায় যে একে আলাদা নামে চেনা ভালো — accumulator প্যাটার্ন। চারটি মূল রূপ মুখস্থ নয়, চিনে রাখুন:
| কী চাই | শুরুর মান | প্রতিটি ঘোরায় | সাবধানতা |
|---|---|---|---|
| যোগফল (sum) | 0 | sum += x; | বড় যোগফলে int উপচে পড়তে পারে — long নিন |
| গণনা (count) | 0 | if (শর্ত) count++; | শর্ত না থাকলে এটি শুধু তালিকার আকার |
| সবচেয়ে বড় (max) | প্রথম জিনিস, বা Integer.MIN_VALUE | max = Math.max(max, x); | শুরু 0 দিলে সব ঋণাত্মক সংখ্যার তালিকায় ভুল ফল |
| সবচেয়ে ছোট (min) | প্রথম জিনিস, বা Integer.MAX_VALUE | min = Math.min(min, x); | খালি তালিকায় শুরুর মানটাই ফল হয়ে যায় — আগে যাচাই করুন |
Java 8 (২০১৪) থেকে এই কাজগুলো লুপ না লিখেও করা যায় Stream দিয়ে। নিচের কোডটি শুধু এক ঝলক — Stream পুরোপুরি শিখবেন পর্ব ৩৫-এ:
int[] sales = {250, 1200, 480};
int total = Arrays.stream(sales).sum(); // 1930
int max = Arrays.stream(sales).max().getAsInt(); // 1200
long big = Arrays.stream(sales).filter(s -> s >= 500).count(); // 1Stream-এ আপনি বলেন কী চাই (“যোগফল”, “৫০০-র বেশিগুলো গোনো”); লুপে বলেন কীভাবে (“0 থেকে শুরু করো, প্রতিটি যোগ করো”)। শেখার শুরুতে লুপ দিয়েই লিখুন — ভেতরে কী হয় তা বোঝা জরুরি, আর পরীক্ষা-ইন্টারভিউতে প্রায়ই লুপই চাওয়া হয়। পরে দুটোই চিনে রাখলে অবস্থা বুঝে বেছে নিতে পারবেন (ভাবনার প্রশ্ন ৫ দেখুন)।
লুপ কি ধীর? — JIT-এর কথা
নতুনরা অনেক সময় ভয় পান, “এক কোটি বার লুপ চালালে প্রোগ্রাম আটকে যাবে”। Java-তে সাধারণত তা হয় না। পর্ব ০৫-এ দেখেছেন, JVM বারবার চলা কোড চিহ্নিত করে JIT compiler দিয়ে আসল machine code বানায়। লুপ ঠিক সেই “গরম” কোড। main-এর ভেতরের একটি লম্বা লুপও চলার মাঝখানেই compile হয়ে যেতে পারে — এই কৌশলের নাম on-stack replacement. compile হওয়ার পর একটি সরল সংখ্যা-যোগের লুপ সাধারণ কম্পিউটারে প্রতি সেকেন্ডে কোটি কোটি ঘোরা দিতে পারে।
তাই গতির জন্য লুপকে কুৎসিত করবেন না। আসল খরচ প্রায় সবসময় লুপের ভেতরের কাজে: প্রতিটি ঘোরায় ফাইল খোলা, ডেটাবেসে প্রশ্ন পাঠানো, বা String-এ += দিয়ে বিশাল লেখা জোড়া (পর্ব ০৮-এর StringBuilder মনে আছে?)। আর nested loop-এ গুণ করে ভাবুন — ১০,০০০ × ১০,০০০ মানে দশ কোটি ঘোরা। অ্যালগরিদমের এই হিসাব পর্ব ৫৪-এ।
- while আর do-while-এর পার্থক্য? while আগে শর্ত দেখে (০ বারও চলতে পারে); do-while আগে body চালায়, তাই অন্তত একবার চলে।
for (int i = 0; i < 5; i++);— শেষের সেমিকোলনে কী হয়? লুপের body হয়ে যায় একটি খালি statement; নিচের{ }লুপের বাইরে, একবারই চলে।breakআরcontinue-এর পার্থক্য? break পুরো লুপ শেষ করে; continue শুধু এই ঘোরার বাকিটা বাদ দেয় (for-এ এরপর update চলে)।- Java-তে
gotoআছে? শব্দটি reserved keyword, কিন্তু কোনো কাজ নেই; nested loop থেকে বেরোতে labeled break/continue. - for-each দিয়ে ঘোরার সময় ArrayList থেকে মুছলে? সাধারণত
ConcurrentModificationException; সঠিক উপায়removeIfবাIterator.remove().
৯ · AI-এর যুগে — AI-এর লেখা লুপ যাচাই
লুপ লেখা AI-এর কাছে সহজ কাজ, আর তার লেখা লুপ প্রথম দেখায় প্রায় সবসময় ঠিক মনে হয়। কিন্তু লুপের বাগ লুকিয়ে থাকে প্রান্তে — প্রথম ঘোরা, শেষ ঘোরা, খালি তালিকা। AI সাধারণ ক্ষেত্রটি ঠিক লেখে, প্রান্ত ভুলে যায়। কিছু চেনা ধরন:
| AI প্রায়ই যা লেখে | সমস্যা ও ঠিক উপায় |
|---|---|
i <= arr.length বা i = 1 থেকে শুরু | off-by-one — শেষে exception, বা প্রথম জিনিস বাদ। 0 থেকে <, বা for-each |
while (true), ভেতরে কয়েকটি এলোমেলো break | কখন থামবে বোঝা কঠিন; কোনো পথে break না এলে অসীম লুপ। থামার শর্ত লুপের মাথায় আনুন |
শুধু পড়ার জন্য for (int i = 0; i < list.size(); i++) | index-এর দরকার নেই; for-each বেশি পরিষ্কার আর ভুলের সুযোগ কম |
for-each-এর ভেতরে list.remove(x) | ConcurrentModificationException; removeIf ব্যবহার করুন |
int max = 0; দিয়ে max খোঁজা | সব ঋণাত্মক হলে ভুল ফল; প্রথম জিনিস বা Integer.MIN_VALUE দিয়ে শুরু |
AI-কে লুপ লিখতে বললে প্রান্তের ক্ষেত্রগুলো নিজেই উল্লেখ করুন, আর তাকে দিয়ে নিজের কোড পরীক্ষা করান:
AI-এর লেখা লুপ হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১Java-র প্রথম আট বছর for-each ছিল না। Java 5-এ এটি কীভাবে এলো, আর Iterable interface কেন এর চাবি?
১৯৯৬ থেকে ২০০৪ পর্যন্ত Java-তে একটি তালিকার প্রতিটি জিনিস দেখতে হলে লিখতে হতো এমন কিছু: for (Iterator it = list.iterator(); it.hasNext(); ) { String s = (String) it.next(); … }. তিনটি বিরক্তিকর জিনিস ছিল এখানে — Iterator ভ্যারিয়েবল সামলানো, hasNext()/next()-এর জোড়া মনে রাখা, আর প্রতিবার cast, কারণ তখন generics ছিল না, তালিকা থেকে সবকিছু আসত সাধারণ Object হয়ে। একটি পরিচিত বাগও ছিল: nested লুপে ভুল করে ভেতরের লুপে বাইরের Iterator-এর next() ডেকে ফেলা।
২০০৪ সালে Java 5 (কোডনাম Tiger) ভাষায় একসাথে অনেক বড় পরিবর্তন আনে। এর একটি অংশ ছিল JSR 201 — Java Community Process-এর একটি প্রস্তাব, যার আওতায় এসেছিল চারটি জিনিস: enum, autoboxing, enhanced for loop আর static import. (generics এসেছিল আলাদা প্রস্তাব JSR 14 থেকে।) নকশাকারীরা ইচ্ছা করে নতুন কোনো keyword যোগ করেননি — foreach বা in শব্দ আনলে লাখো পুরোনো প্রোগ্রাম ভেঙে যেত, যেখানে কেউ হয়তো in নামে ভ্যারিয়েবল রেখেছে (System.in-এর কথাই ভাবুন)। তাই পুরোনো for আর একটি কোলন দিয়েই নতুন রূপ।
চাবিটি হলো java.lang.Iterable interface, যা Java 5-এ যোগ হয়। এতে মূলত একটি method: iterator(). নিয়মটি সরল — যে class এই interface মানে, তার ওপর for-each চলবে। compiler ভেতরে পুরোনো Iterator-লুপটিই বানিয়ে নেয়; array-র জন্য বানায় সাধারণ index-লুপ। অর্থাৎ for-each নতুন কোনো ক্ষমতা নয়, এটি syntactic sugar — একই কাজের মিষ্টি লেখনী। কিন্তু Iterable-এর মাধ্যমে এটি একটি চুক্তি: আপনার নিজের class — ধরুন একটি Playlist — Iterable মানলে Java-র নিজের collection-এর মতোই for-each-এ ব্যবহার করা যায়। ভাষার একটি বৈশিষ্ট্য এভাবে একটি interface-এর সাথে যুক্ত থাকা Java-র নকশার একটি প্রিয় কৌশল; try-with-resources-ও একইভাবে AutoCloseable-এর সাথে যুক্ত (পর্ব ২৯)।
দামও আছে। for-each index দেয় না, মোছার সুযোগ দেয় না, আর লুকানো Iterator-এর কারণে ঘোরার মাঝে তালিকা বদলালে ConcurrentModificationException. তবু কয়েক বছরের মধ্যেই এটি Java-তে তালিকা ঘোরার স্বাভাবিক উপায় হয়ে যায়।
মূল উপলব্ধি: ভালো ভাষা-নকশা সবচেয়ে বেশি ব্যবহৃত প্যাটার্নকে সবচেয়ে সহজ করে, আর পুরোনো কোড না ভেঙে তা করে। for-each কোনো জাদু নয় — Iterable চুক্তির ওপর বসানো একটি সুন্দর মোড়ক।
প্র ০২Java-তে goto একটি reserved word, অথচ কাজ করে না। তাহলে labeled break কেন রাখা হলো?
Java Language Specification-এর keyword-তালিকায় দুটি অদ্ভুত শব্দ আছে: goto আর const. দুটোই reserved — ভ্যারিয়েবলের নাম হিসেবে ব্যবহার করা যায় না — কিন্তু ভাষায় এদের কোনো কাজ নেই। int goto = 5; লিখলে compile error আসে। কেন এমন একটি শব্দ আটকে রাখা হলো, যা কেউ ব্যবহারই করতে পারে না?
Java-র নকশাকারীরা জানতেন, তাঁদের প্রথম ব্যবহারকারীরা আসবেন C আর C++ থেকে। ওই ভাষাগুলোতে goto আছে, আর const খুব পরিচিত। শব্দ দুটি reserved রাখার একটি সুবিধা হলো, C++ থেকে আসা কেউ অভ্যাসবশে goto লিখলে compiler স্পষ্ট error দেয়, চুপচাপ অন্য অর্থ করে না। আরেকটি সুবিধা — ভবিষ্যতে কখনো দরকার হলে শব্দ দুটি হাতে রইল, পুরোনো কোড না ভেঙেই যোগ করা যাবে।
তাহলে goto বাদ কেন? কারণ ১৯৯০-এর দশকে “structured programming”-এর পক্ষে মত প্রায় সর্বসম্মত ছিল: যেখানে খুশি লাফ দেওয়া কোডকে বোঝা ও যাচাই করা কঠিন করে (পরের প্রশ্ন দেখুন)। কিন্তু নকশাকারীরা এটাও দেখলেন যে C প্রোগ্রামাররা বাস্তবে goto-র সবচেয়ে বৈধ ব্যবহার করেন মূলত দুটি কাজে — nested loop থেকে একবারে বের হওয়া, আর error হলে পরিষ্কারের কাজের জায়গায় যাওয়া। দ্বিতীয়টির জন্য Java দিল exception আর finally (পর্ব ২৯)। প্রথমটির জন্য দিল labeled break আর labeled continue.
পার্থক্যটি সূক্ষ্ম কিন্তু গুরুত্বপূর্ণ। goto যেকোনো দিকে, যেকোনো লাইনে লাফ দিতে পারে — এমনকি একটি লুপের মাঝখানে ঢুকে পড়তেও। labeled break শুধু বের হতে পারে, আর শুধু এমন একটি কাঠামো থেকে, যার ভেতরে আপনি এখন আছেন। এটি কোনো block-এর মাঝখানে ঢুকতে পারে না, পেছনে ফিরতে পারে না। ফলে কোড পড়ার সময় নিশ্চিত থাকা যায়: label-যুক্ত লুপটি এখানে শেষ, আর প্রোগ্রাম তার ঠিক পরের লাইনে যাবে। কাঠামো অক্ষুণ্ণ থাকে। Java-র label শুধু লুপে নয়, যেকোনো block-এ বসানো যায় (check: { … break check; … }), তবে বাস্তবে সেটা প্রায় দেখাই যায় না।
মূল উপলব্ধি: Java “goto খারাপ” বলে পুরোপুরি নিষেধ করেনি — goto-র যে কয়েকটি ব্যবহার সত্যিই দরকারি ছিল, প্রতিটির জন্য একটি সীমিত, নিরাপদ বিকল্প দিয়েছে। ক্ষমতা কমিয়ে নিশ্চয়তা বাড়ানো — ভাষা-নকশার একটি বারবার দেখা কৌশল।
প্র ০৩Dijkstra-র “Go To Statement Considered Harmful” (১৯৬৮) — একটি ছোট চিঠি কীভাবে প্রোগ্রামিংয়ের ধরন বদলে দিল?
১৯৬০-এর দশকে বেশিরভাগ প্রোগ্রাম লেখা হতো FORTRAN, COBOL বা assembly-তে, আর নিয়ন্ত্রণ সরানোর প্রধান উপায় ছিল GO TO — “এখন অমুক নম্বর লাইনে যাও”। লুপ, শর্ত, error — সবকিছু লাফ দিয়ে। বড় প্রোগ্রামে এই লাফের রেখাগুলো আঁকলে দেখতে লাগত এক বাটি নুডলসের মতো; এজন্য নাম হয় “spaghetti code”.
১৯৬৮ সালের মার্চে ওলন্দাজ কম্পিউটার বিজ্ঞানী Edsger W. Dijkstra-র একটি ছোট চিঠি ছাপা হয় Communications of the ACM-এ। চিঠিটির বিখ্যাত শিরোনাম “Go To Statement Considered Harmful” আসলে Dijkstra-র দেওয়া নয় — দিয়েছিলেন পত্রিকার সম্পাদক Niklaus Wirth (পরে Pascal ভাষার স্রষ্টা)। Dijkstra-র মূল যুক্তি ছিল মানুষের মন নিয়ে: আমরা স্থির লেখা পড়ে চলমান প্রক্রিয়া কল্পনা করতে ভালো নই। তাই প্রোগ্রামের লেখা আর তার চলার মধ্যে দূরত্ব যত কম, বোঝা তত সহজ। লুপ আর শর্ত এই দূরত্ব কম রাখে — কোডে কোথায় আছেন দেখেই বলা যায় প্রোগ্রাম কোন অবস্থায় আছে। এলোমেলো goto সেই সম্পর্ক ভেঙে দেয়।
তাত্ত্বিক ভিত্তি আগেই ছিল। ১৯৬৬ সালে Corrado Böhm আর Giuseppe Jacopini দেখান যে যেকোনো প্রোগ্রাম শুধু তিনটি কাঠামো দিয়ে লেখা যায়: ক্রমানুসারে চলা (sequence), বাছাই (selection — আমাদের if/switch) আর পুনরাবৃত্তি (iteration — আমাদের লুপ)। অর্থাৎ goto তাত্ত্বিকভাবে কখনো অপরিহার্য নয়। Dijkstra-র চিঠি এই তত্ত্বকে একটি প্রকৌশল-নীতিতে পরিণত করে, আর শুরু হয় “structured programming” আন্দোলন।
বিতর্কও কম হয়নি। ১৯৭৪ সালে Donald Knuth “Structured Programming with go to Statements” নামে একটি দীর্ঘ প্রবন্ধে দেখান যে কিছু ক্ষেত্রে — যেমন নেস্টেড লুপ থেকে বের হওয়া বা দক্ষতার জন্য — নিয়ন্ত্রিত goto কোডকে আরও পরিষ্কারও করতে পারে। আজও Linux kernel-এর C কোডে error হলে পরিষ্কারের জায়গায় যেতে goto সাধারণ রীতি। আধুনিক ভাষাগুলো মূলত Knuth আর Dijkstra-র মাঝামাঝি পথ নিয়েছে: সাধারণ goto নেই, কিন্তু break, continue, return আর exception — কয়েকটি নির্দিষ্ট, সীমিত লাফ আছে।
মূল উপলব্ধি: আপনি যে for, while আর break শিখলেন, সেগুলো কোনো দুর্ঘটনা নয় — অর্ধশতাব্দী আগের একটি তর্কের ফল, যার কেন্দ্রে ছিল একটিই প্রশ্ন: মানুষ কোডটি পড়ে কত সহজে বুঝতে পারবে? কোড লেখা হয় একবার, পড়া হয় বহুবার।
প্র ০৪Java প্রতিটি array-ব্যবহারে সীমা যাচাই করে, তবু তার লুপ এত দ্রুত কেন? Loop unrolling আর bounds-check elimination কী?
অংশ ৭-এ দেখেছেন, marks[3] চাইলে JVM সাথে সাথে ArrayIndexOutOfBoundsException দেয়। এর মানে প্রতিটি marks[i]-এর আগে একটি লুকানো যাচাই চলে: “0 ≤ i < length?” একটি লুপ এক কোটি বার চললে এক কোটি বাড়তি যাচাই। C-তে এমন যাচাই নেই, তাই প্রথম দেখায় মনে হয় Java এখানে অনেক পিছিয়ে থাকবে। বাস্তবে সাধারণ লুপে পার্থক্য প্রায়ই খুব সামান্য। কেন?
কারণ HotSpot-এর JIT compiler (বিশেষ করে C2) লুপকে বিশেষ যত্নে অপ্টিমাইজ করে। প্রথম কৌশল bounds-check elimination: for (int i = 0; i < a.length; i++) দেখে compiler গাণিতিকভাবে প্রমাণ করতে পারে যে i কখনো 0-এর কম বা a.length-এর সমান বা বেশি হবে না। প্রমাণ হয়ে গেলে প্রতিটি ঘোরার যাচাই সরিয়ে ফেলে। প্রমাণ জটিল হলে কখনো যাচাইটি লুপের আগে একবারে করে নেয়। এই কারণেই সোজাসাপ্টা লুপ লেখা ভালো — চালাকি করা index compiler-কে বিভ্রান্ত করে, আর যাচাই থেকে যায়।
দ্বিতীয় কৌশল loop unrolling: প্রতিটি ঘোরায় একটি কাজের বদলে কয়েকটি করা। যেমন sum += a[i]-কে compiler বানাতে পারে sum += a[i]; sum += a[i+1]; sum += a[i+2]; sum += a[i+3]; আর i += 4. এতে শর্ত যাচাই আর jump চার ভাগের এক ভাগ হয়ে যায়, আর আধুনিক CPU একসাথে কয়েকটি নির্দেশ চালানোর সুযোগ পায়। এর ওপর আছে loop-invariant code motion (প্রতিবার একই ফল দেয় এমন হিসাব লুপের বাইরে সরানো), আর কিছু ক্ষেত্রে auto-vectorization — CPU-র SIMD নির্দেশ দিয়ে একসাথে কয়েকটি সংখ্যা যোগ।
আর main-এর ভেতরের একটি লম্বা লুপ, যে একবারই ডাকা হয় কিন্তু কোটি বার ঘোরে? HotSpot এর জন্য on-stack replacement (OSR) ব্যবহার করে: লুপ চলার মাঝখানেই compile করা সংস্করণ তৈরি করে, আর চলমান অবস্থা নিয়ে সেখানে সরে যায়। পর্ব ০৫-এর warm-up-এর কথা মনে রাখুন — তাই লুপের গতি মাপতে চাইলে শুধু একবার চালিয়ে মাপা ফল বিশ্বাস করবেন না; পেশাদারেরা JMH-এর মতো benchmark টুল ব্যবহার করেন।
মূল উপলব্ধি: নিরাপত্তা আর গতি সবসময় বিপরীত নয়। Java ভাষার স্তরে নিরাপত্তা দেয় (প্রতিটি যাচাই), আর runtime-এর স্তরে বুদ্ধি দিয়ে যেখানে নিরাপদ সেখানে খরচ সরিয়ে ফেলে। সরল, পরিষ্কার লুপ মানুষের জন্যও ভালো, compiler-এর জন্যও।
প্র ০৫for-each লুপ না Stream — আধুনিক Java-তে কোনটি লিখবেন? এই বিতর্কের দুই দিক কী?
২০১৪ সালে Java 8 একসাথে আনে lambda আর Stream API. হঠাৎ করে একই কাজের দুটি রীতি তৈরি হলো। ধরুন ৩৩-এর কম নম্বর কয়টি গুনতে হবে। লুপে: int c = 0; for (int m : marks) if (m < 33) c++;. Stream-এ: long c = Arrays.stream(marks).filter(m -> m < 33).count();. কোনটি “ঠিক”? Java সমাজে এ নিয়ে আজও আলোচনা চলে।
Stream-এর পক্ষে: কোড বলে কী চাই, কীভাবে নয় — “ফিল্টার করো, তারপর গোনো” সরাসরি পড়া যায়। ভ্যারিয়েবল বদলানোর ঝামেলা নেই, তাই accumulator-এর শুরুর মান ভুল করার সুযোগ কম। কয়েকটি ধাপ (filter → map → sort → collect) পাইপের মতো জোড়া যায়। আর কিছু ক্ষেত্রে .parallel() যোগ করেই কাজ কয়েকটি CPU core-এ ভাগ করা যায় — যদিও সেটা সবসময় দ্রুত হয় না, আর ভুল জায়গায় ব্যবহার করলে ধীরও হতে পারে।
লুপের পক্ষে: মাঝপথে থামা সহজ — break আর return. Stream-এ এর সমতুল্য আছে (findFirst, anyMatch, Java 9 থেকে takeWhile), কিন্তু সবসময় সমান সহজ নয়। লুপের ভেতরে বাইরের ভ্যারিয়েবল বদলানো যায়; lambda-র ভেতরে বাইরের local ভ্যারিয়েবল হতে হয় effectively final. checked exception ছোড়ে এমন কাজ (যেমন ফাইল পড়া) lambda-তে ঝামেলার। debugger-এ লুপ এক লাইন এক লাইন করে দেখা সহজ, Stream-এর stack trace লম্বা আর জটিল। ছোট তালিকায় Stream-এর নিজের কিছু খরচ আছে, যদিও JIT তার বড় অংশ সরিয়ে ফেলে।
বাস্তবে অভিজ্ঞ Java ডেভেলপাররা সাধারণত মাঝামাঝি পথ নেন। ডেটা রূপান্তর — বাছাই, বদল, দলে ভাগ, তালিকা বানানো — Stream-এ পরিষ্কার। জটিল নিয়ন্ত্রণ — একাধিক জায়গা থেকে থামা, একসাথে কয়েকটি অবস্থা বদলানো, exception সামলানো — লুপে পরিষ্কার। একটি ভালো পরীক্ষা: কোডটি জোরে পড়ুন; যেটি শুনতে সমস্যার বর্ণনার কাছাকাছি, সেটি বেছে নিন। আর দলের রীতি মানুন — একই ধরনের কাজ পুরো প্রজেক্টে একই রীতিতে লেখা থাকলে সবার পড়তে সুবিধা।
মূল উপলব্ধি: লুপ আর Stream প্রতিদ্বন্দ্বী নয়, দুটি যন্ত্র। লুপ আগে শিখুন, কারণ Stream-এর ভেতরে শেষ পর্যন্ত লুপই চলে, আর তা বুঝলে Stream-এর আচরণও বুঝবেন। তারপর পর্ব ৩৫-এ Stream শিখে প্রতিটি কাজে নিজেকে জিজ্ঞেস করুন — কোনটিতে উদ্দেশ্য বেশি স্পষ্ট?
অনুশীলন
এগুলোর বেশিরভাগে কোড লিখতে হবে না — হাতে-কলমে দেখা আর ভাবাই আসল কাজ।
-
হাতে trace করুন: কাগজে টেবিল বানিয়ে বলুন আউটপুট কী আসবে, আর
System.out.printকতবার চলবে:for (int i = 1; i <= 3; i++) { for (int j = i; j <= 3; j++) { System.out.print(i * j + " "); } System.out.println(); }ভেতরের লুপ শুরু হয়
j = iথেকে, তাই প্রতিটি সারি আগেরটির চেয়ে ছোট। i = 1: j = 1, 2, 3 →1 2 3. i = 2: j = 2, 3 →4 6. i = 3: j = 3 →9. আউটপুট তিন লাইনে:1 2 3,4 6,9.printচলে ৩ + ২ + ১ = ৬ বার। এটি নামতার ছকের “উপরের ত্রিভুজ” — গুণ বিনিময়যোগ্য বলে বাকি অর্ধেক বাদ দেওয়া যায়। -
বাগ খুঁজুন: এই কোড ১ থেকে ১০-এর যোগফল ছাপার কথা, কিন্তু প্রোগ্রাম কখনো শেষ হয় না:
int i = 1, sum = 0; while (i <= 10) { sum += i; } System.out.println(sum);কারণ কী, কোথায় কী যোগ করবেন? ঠিক করার পরi < 10লিখলে ফল কত হবে?body-তে
iকখনো বদলায় না — চিরকাল 1, তাই1 <= 10চিরকাল সত্য।sum += i;-এর পরেi++;যোগ করুন; তখন ফল 55. শর্তi < 10করলে 10 বাদ পড়ে, ফল আসে 45 — একটি off-by-one. এজন্যই এমন গোনা কাজেfor (int i = 1; i <= 10; i++)ভালো: তিনটি অংশ এক লাইনে থাকায় update ভুলে যাওয়া কঠিন। -
কোন লুপ বাছবেন: প্রতিটির জন্য
for,while,do-whileনা for-each — এক লাইনে কারণসহ বলুন। (ক) ৩০ জন ছাত্রের নামের তালিকা ছাপা; (খ) সঠিক PIN না দেওয়া পর্যন্ত বারবার PIN চাওয়া; (গ) ১০০ থেকে ১ পর্যন্ত উল্টো গোনা; (ঘ) একটি sensor থেকে তাপমাত্রা পড়তে থাকা, যতক্ষণ না তা ৫০ ডিগ্রি ছাড়ায়।(ক) for-each — প্রতিটি জিনিস, index লাগে না। (খ) do-while — PIN অন্তত একবার চাইতেই হবে, তারপর যাচাই। (গ)
for (int i = 100; i >= 1; i--)— কতবার জানা, আর উল্টো দিকে চলতে index লাগে। (ঘ) while — কতবার পড়তে হবে জানা নেই, একটি ঘটনা (৫০ ছাড়ানো) পর্যন্ত চলবে। খেয়াল করুন, চারটিই যেকোনো লুপ দিয়ে লেখা সম্ভব; প্রশ্নটা হলো কোনটিতে উদ্দেশ্য সবচেয়ে স্পষ্ট। -
নিজে লিখুন — উল্টো তারার প্যাটার্ন: nested loop দিয়ে এমন প্যাটার্ন ছাপুন: প্রথম লাইনে ৪টি তারা, তারপর ৩টি, ২টি, ১টি। উপরের যেকোনো রানার-ব্লকে
main-এর ভেতরটা বদলে চালিয়ে দেখতে পারেন।একটি সমাধান:
for (int row = 4; row >= 1; row--) { for (int star = 1; star <= row; star++) { System.out.print("*"); } System.out.println(); }. বাইরের লুপ ৪ থেকে নামছে, আর ভেতরের লুপ প্রতি সারিতেrow-সংখ্যক তারা ছাপছে। আরেকটি উপায়: বাইরের লুপrow = 1থেকে ৪, আর ভেতরেstar <= 5 - row. দুটোই সঠিক; প্রথমটি পড়তে সহজ, কারণ “সারিতে কয়টি তারা” সরাসরিrow-এর মান।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমএকটি for লুপ যে ক্রমে চলে, সেভাবে সাজান (▲▼ চেপে সরান)
- 1️⃣ init চলে — একবারই (যেমন
int i = 0) - ❓ শর্ত যাচাই — সত্য তাই ভেতরে যাও
- ⚙️ body চলে
- ➕ update চলে (যেমন
i++) - 🛑 শর্ত আবার যাচাই — মিথ্যা হলে লুপের পরের লাইনে
Java-তে for (int i = 0; i < 5; i += 2) System.out.print(i); কী ছাপে?
- 01234
- 246
- 024
- 0245
Java-র কোন লুপের body শর্ত শুরুতেই মিথ্যা হলেও অন্তত একবার চলে?
- while
- do-while
- for
- for-each
int[] a = {5, 6, 7}; থাকলে for (int i = 0; i <= a.length; i++) System.out.println(a[i]); চালালে কী ঘটে?
- Compile error হয়
- 5, 6, 7 ছাপা হয়, কোনো সমস্যা নেই
- 5, 6, 7 আর একটি এলোমেলো মান ছাপা হয়
- 5, 6, 7 ছাপার পর ArrayIndexOutOfBoundsException
দুটি nested লুপের বাইরেরটির নাম outer. ভেতরের লুপে break outer; চললে কী হয়?
- দুটি লুপই শেষ হয়, প্রোগ্রাম বাইরের লুপের পরের লাইনে যায়
- শুধু ভেতরের লুপ শেষ হয়
- বাইরের লুপের পরের ঘোরা শুরু হয়
- Compile error, কারণ Java-তে label নেই
একটি ArrayList-এর ওপর for-each লুপ চলার সময় ভেতরে list.remove(x) ডাকলে সাধারণত কী হয়?
- জিনিসটি মুছে যায়, লুপ ঠিকঠাক চলতে থাকে
- Compile error
- ConcurrentModificationException
- NullPointerException
নিচের কোনটি Java-তে একটি বৈধ, compile হওয়া অসীম লুপের শুরু?
while (1) { … }for (;;) { … }loop { … }do { … } until (false);
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- লুপ = শর্ত যতক্ষণ সত্য, ততক্ষণ পুনরাবৃত্তি। প্রতিটি লুপে থাকতে হবে শুরু, থামার শর্ত, আর থামার দিকে এগোনোর পরিবর্তন.
- কতবার জানা — for; ঘটনা পর্যন্ত — while (sentinel); অন্তত একবার — do-while; প্রতিটি জিনিস — for-each.
- for-এর ক্রম: init একবার → শর্ত → body → update → শর্ত…; loop variable লুপের বাইরে থাকে না।
- break লুপ শেষ করে, continue একটি ঘোরা বাদ দেয়; nested loop থেকে একবারে বেরোতে labeled break — তবে আলাদা method আর
returnপ্রায়ই বেশি পরিষ্কার। - ফাঁদ: অসীম লুপ,
<=দিয়ে off-by-one, for-each-এর মাঝে তালিকা বদলানো (removeIfব্যবহার করুন)।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১২ · method — overloading, varargs আর pass-by-valueপরবর্তী পর্বলুপকে নাম দেওয়া method-এ রাখা, আর return দিয়ে খোঁজা শেষ করা।
- Java প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Python-এর লুপ — for, while, range আর enumerateতুলনা করুনযে ভাষায় for মানেই for-each, আর লুপের সাথে else আছে।
- C-র লুপ — একই কাজ বারবারপাশের ট্র্যাকJava-র for/while-এর উৎস, আর সীমা যাচাই ছাড়া array-র ঝুঁকি।