সরাসরি পর্বে যান
পর্ব ০৯ · ৫৯-এর মধ্যে · মডিউল ২

ইনপুট-আউটপুট — Scanner, printf আর text block

Input and Output in Java — Streams, Scanner, printf, Locale and Text Blocks

Taught in Bangla, this lesson handles console input and output: the System.in, out and err streams, reading typed values with Scanner, the classic nextInt then nextLine bug, validating with hasNextInt, faster input with BufferedReader, printf format specifiers and Locale, aligned table reports, and multi-line text blocks.

এই পর্বে যা শিখবেন

  • stream কী, আর System.in, System.out, System.err কেন তিনটি আলাদা পথ
  • Scanner কীভাবে ইনপুটকে token-এ ভাঙে — আর nextInt/nextLine বাগটি কেন ঘটে ও কীভাবে সারাবেন
  • ভুল ইনপুট যাচাই (hasNextInt), Scanner বন্ধ করার নিয়ম, আর দ্রুত বিকল্প BufferedReader
  • printf-এর ছাঁচের পুরো ভাষা, Locale-এর প্রভাব, আর সারি-কলাম মিলিয়ে টেবিল ছাপা
  • text block দিয়ে SMS টেমপ্লেট আর JSON লেখা — indentation কীভাবে কাটা হয়

১ · প্রোগ্রাম কথা বলে তিনটি নল দিয়ে

এখন পর্যন্ত আপনার প্রোগ্রামগুলো ছিল একতরফা বক্তা — যা লেখা আছে, তা-ই ছাপে। পর্ব ০৬-এ variable শিখেছেন, পর্ব ০৮-এ String. কিন্তু মানগুলো ছিল কোডের ভেতরেই বসানো। প্রতিবার নতুন ওজন দিয়ে BMI দেখতে চাইলে কোড বদলে আবার compile করতে হবে — এটা কোনো কাজের প্রোগ্রাম নয়। আসল প্রোগ্রাম শোনে, তারপর বলে।

একটি প্রোগ্রাম চালু হলেই অপারেটিং সিস্টেম তাকে তিনটি তৈরি সংযোগ দিয়ে দেয়। প্রতিটিকে বলে stream — একদিক দিয়ে বাইট ঢোকে, অন্যদিক দিয়ে বের হয়, ঠিক পানির নলের মতো। Java এই তিনটিকে রেখেছে System class-এর ভেতরে:

📥 System.in

standard input — ইনপুট ঢোকার নল। সাধারণত এর মাথায় থাকে কীবোর্ড, তবে ফাইলও জোড়া যায়।

দোকানের অর্ডার-স্লিপ

📤 System.out

standard output — ফল বেরোনোর নল। println আর printf এখানেই লেখে।

কাউন্টারে তৈরি খাবার

⚠️ System.err

standard error — error আর সতর্কবার্তার আলাদা নল, যাতে আসল ফলের সাথে মিশে না যায়।

অভিযোগের বাক্স

একটি ভাতের হোটেলের কথা ভাবুন। ওয়েটার স্লিপে অর্ডার লিখে রান্নাঘরে পাঠান — “২ প্লেট ভাত, ১ বাটি ডাল”। রান্নাঘর স্লিপটি পড়ে, প্রতিটি শব্দ আলাদা করে বোঝে (“২” একটি সংখ্যা, “প্লেট” একটি একক), তারপর রান্না করে। শেষে খাবার সুন্দর করে থালায় সাজিয়ে কাউন্টারে দেয়। এখানে অর্ডার-স্লিপ হলো System.in, স্লিপ পড়ে বোঝার কাজটি করে Scanner, আর থালায় সাজানোর কাজ printf-এর। কোনো উপকরণ না থাকলে সেই খবর যায় ম্যানেজারের কাছে আলাদা পথে — খদ্দেরের থালায় নয়। সেটাই System.err.

মূল ধারণা

নলের ভেতর দিয়ে যা চলে, তা শুধু অক্ষরের সারি — কোনো টাইপ নেই। কীবোর্ডে 70 টাইপ করলে প্রোগ্রাম পায় দুটি অক্ষর '7' আর '0', তারপর একটি Enter-এর অক্ষর। সংখ্যা ৭০ নয়। তাই দুটি অনুবাদক লাগে: Scanner লেখাকে টাইপ করা মানে বদলায় ("70" → int 70), আর printf মানকে সাজানো লেখায় বদলায় (22.857142… → "22.9"). এই পর্বের পুরোটাই এই দুই অনুবাদের গল্প।

এই কোর্সের কোড-বক্সে ইনপুট কোথা থেকে আসে?

নিজের কম্পিউটারে চালালে প্রোগ্রাম থেমে আপনার টাইপ করার অপেক্ষা করবে। কিন্তু এই পাতার “চালান” বোতাম কোড পাঠায় একটি অনলাইন সার্ভারে (Wandbox, OpenJDK 21) — সেখানে কেউ কীবোর্ডে বসে নেই। তাই ইনপুট নেয় এমন প্রতিটি বক্সে ইনপুট আগে থেকে লিখে দেওয়া থাকে, আর প্রোগ্রাম চলার সময় সেটি System.in-এ ঢেলে দেওয়া হয় — যেন কেউ খুব দ্রুত টাইপ করে দিল। প্রতিটি বক্সের নিচের ব্যাখ্যায় লেখা থাকবে কোন ইনপুট দেওয়া হয়েছে।

এর একটি মজার ফল আছে: আউটপুটে আপনার “উত্তর” দেখা যাবে না। কীবোর্ডে টাইপ করলে যে লেখা পর্দায় ভেসে ওঠে, তা আসলে terminal দেখায়, প্রোগ্রাম নয়। ইনপুট আগে থেকে দেওয়া হলে কেউ টাইপ করে না, তাই প্রশ্নগুলো (Name: Weight (kg): …) এক লাইনে পাশাপাশি চলে আসে। এটা ভুল নয় — নলের স্বভাব।

অবাক করার মতো তথ্য: Java-র প্রথম প্রায় নয় বছর কীবোর্ড থেকে একটি সংখ্যা পড়ার কোনো সহজ উপায় ছিল না। ১৯৯৬ সালের Java 1.0 থেকে System.in ছিল, কিন্তু সেটি শুধু কাঁচা বাইট দিত। একটি সংখ্যা পড়তে লিখতে হতো new BufferedReader(new InputStreamReader(System.in)), তারপর readLine(), তারপর Integer.parseInt(…) — আর সাথে exception সামলানো। সহজ Scanner আর printf — দুটোই এলো ২০০৪ সালের Java 5-এ। ভাবনার প্রশ্ন ১-এ এর কারণ খুঁজব।

২ · Scanner — লেখার স্রোত থেকে টাইপ করা মান

Scanner থাকে java.util package-এ। System বা String-এর মতো এটি আপনাআপনি হাতের কাছে থাকে না, তাই ফাইলের শুরুতে একটি import লাইন লাগে। তারপর একটি Scanner বানিয়ে তাকে বলে দিতে হয় কোন নল থেকে পড়বে:

import java.util.Scanner;                 // Scanner কোথায় আছে, compiler-কে জানানো

Scanner sc = new Scanner(System.in);      // System.in থেকে পড়বে এমন একটি Scanner

new দিয়ে একটি object বানানো হলো — পর্ব ১৫-এ এর পুরো মানে শিখবেন। আপাতত sc-কে ভাবুন একজন পাঠক হিসেবে, যে নল থেকে লেখা পড়ে আপনার চাহিদামতো টুকরো এনে দেয়। Scanner লেখাকে ভাঙে whitespace (space, tab, Enter) দেখে। প্রতিটি টুকরোকে বলে token, আর যা দিয়ে ভাঙা হয় তাকে বলে delimiter. প্রতিদিন যে method-গুলো লাগবে:

Methodকী ফেরত দেয়কীভাবে পড়ে
next()Stringসামনের whitespace বাদ দিয়ে একটি token (একটি শব্দ)
nextInt()intএকটি token পড়ে পূর্ণসংখ্যায় বদলায়
nextDouble()doubleএকটি token পড়ে দশমিক সংখ্যায় বদলায়
nextBoolean()booleantrue/false token (বড়-ছোট হাতের অক্ষর যেকোনো)
nextLine()Stringবর্তমান লাইনের বাকি সবটুকু, পরের Enter পর্যন্ত (Enter নিজে বাদ)
hasNextInt()booleanপরের token কি পূর্ণসংখ্যা? শুধু উঁকি দেয়, কিছু পড়ে না

Scanner ভেতরে একটি input buffer রাখে — নল থেকে আসা লেখা যেখানে জমা থাকে, আর প্রতিটি next…() ডাক সেখান থেকে সামনের অংশ কেটে নেয়। নিচের ডায়াগ্রামে ইনপুট তিন লাইনে: রফিক, 70, 1.75. প্রতিটি ধাপে দেখুন বাফারে কী বাকি থাকে (⏎ মানে Enter-এর অক্ষর \n):

1Scanner sc = new Scanner(System.in);2String name = sc.next();3double weight = sc.nextDouble();4double height = sc.nextDouble();
এই মুহূর্তে
বাফারে বাকি
name
weight
height
  1. Scanner তৈরি, ইনপুট এসে জমা। ব্যবহারকারী তিনটি লাইন টাইপ করে প্রতিবার Enter চেপেছেন। নলের ভেতর এখন একটানা অক্ষরের সারি: রফিক⏎70⏎1.75⏎. লক্ষ করুন, এখানে “লাইন” বলে আলাদা কিছু নেই — শুধু অক্ষর, মাঝে মাঝে একটি ⏎. কোনো variable-এ এখনো কিছু আসেনি।
  2. next() — একটি শব্দ। Scanner সামনের whitespace বাদ দেয় (এখানে নেই), তারপর পরের whitespace পর্যন্ত পড়ে: রফিক. থামার কারণ হয়ে-থাকা ⏎-টি কিন্তু বাফারেই রয়ে গেল — next() delimiter খায় না। নাম “রফিক ইসলাম” হলে এখানে শুধু “রফিক” আসত, “ইসলাম” পড়ে থাকত পরের ডাকের জন্য।
  3. nextDouble() — লেখা থেকে সংখ্যা। প্রথমে সামনের ⏎-টি whitespace হিসেবে বাদ গেল। তারপর token 70 পড়ে Scanner যাচাই করল এটি সংখ্যা কি না — হ্যাঁ — আর বদলে দিল double মান 70.0-এ। এই যাচাই আর বদলকে বলে parse করা। এখান থেকে weight একটি আসল সংখ্যা, যা দিয়ে গুণ-ভাগ করা যায়।
  4. আবার nextDouble(). একই নিয়ম: ⏎ বাদ, token 1.75, বদলে 1.75. তিনটি ডাক, তিনটি token — Scanner লাইন গোনেনি, token গুনেছে। ইনপুট এক লাইনে রফিক 70 1.75 দিলেও ফল হুবহু এক হতো।
  5. শেষে একটি ⏎ পড়ে রইল। শেষ সংখ্যার পরের Enter কেউ খায়নি। এখানে থামলে সমস্যা নেই। কিন্তু এর পরে কেউ যদি nextLine() ডাকে, সে এই পড়ে থাকা ⏎-টিকেই “লাইনের শেষ” ধরে খালি লেখা ফেরত দেবে। এটাই পরের অংশের বিখ্যাত বাগের বীজ।

এবার পুরো প্রোগ্রাম — নাম, ওজন আর উচ্চতা পড়ে BMI (Body Mass Index = ওজন ÷ উচ্চতা²) হিসাব। শেষ তিন লাইনে একই সংখ্যা তিনভাবে ছাপা হয়েছে, যাতে println আর printf-এর পার্থক্য চোখে পড়ে:

import java.util.Locale;
import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        System.out.print("Name: ");
        String name = sc.next();            // একটি শব্দ (token)
        System.out.print("Weight (kg): ");
        double weight = sc.nextDouble();    // লেখা "70" থেকে double 70.0
        System.out.print("Height (m): ");
        double height = sc.nextDouble();    // লেখা "1.75" থেকে 1.75

        double bmi = weight / (height * height);
        System.out.println();
        System.out.println("Hello, " + name + "!");
        System.out.println("println : " + bmi);
        System.out.printf(Locale.US, "printf  : %.1f%n", bmi);
        System.out.printf(Locale.US, "[%8.2f] [%-8.2f] [%08.2f]%n", bmi, bmi, bmi);
    }
}

এই বক্সের ইনপুট আগে থেকে দেওয়া: তিন লাইনে রফিক, 70 আর 1.75. তাই প্রথম আউটপুট লাইনে তিনটি প্রশ্ন পাশাপাশি — Name: Weight (kg): Height (m): — কেউ টাইপ করেনি বলে উত্তরগুলো পর্দায় ভাসেনি। ফাঁকা println() এই লাইনটি শেষ করে। তারপর Hello, রফিক! — বাংলা নামও ঠিকঠাক পড়া গেছে।

হিসাব: ৭০ ÷ (১.৭৫ × ১.৭৫) = ৭০ ÷ ৩.০৬২৫. println ছাপল double-এর পুরো রূপ 22.857142857142858 — সঠিক, কিন্তু মানুষের পড়ার উপযোগী নয়। printf-এর %.1f একে এক দশমিকে গোল করে দিল 22.9. শেষ লাইনে তিনটি ছাঁচ: [ 22.86] (৮ ঘর, ডানে ঘেঁষা), [22.86 ] (- দিলে বাঁয়ে), [00022.86] (0 দিলে ফাঁকার জায়গায় শূন্য)। Locale.US কেন লেখা হলো, তা অংশ ৬-এ। আর মনে রাখুন, প্রতিটি টুকরো আলাদা token, তাই একই ইনপুট এক লাইনে রফিক 70 1.75 দিলেও ফল একই আসবে।

৩ · next বনাম nextLine — আর বিখ্যাত “হারানো লাইন” বাগ

নাম যদি দুই শব্দের হয় — “রফিক ইসলাম”? next() শুধু প্রথম শব্দটি দেবে। পুরো লাইন চাইলে লাগবে nextLine(). দুটোর পার্থক্য একটি উদাহরণে পরিষ্কার। ইনপুট লাইন যদি হয় Dhaka Mirpur 10 (Dhaka-র পরে দুটি space), তাহলে প্রথমে next() আর তারপর nextLine() ডাকলে অনলাইন runner-এ পাওয়া গেল:

ডাকফলকেন
sc.next()"Dhaka"প্রথম whitespace-এ থামে
sc.nextLine()" Mirpur 10"যেখানে থেমেছিল সেখান থেকে লাইনের শেষ পর্যন্ত — শুরুর দুটি space-সহ

লক্ষ করুন, nextLine() সামনের whitespace বাদ দেয় না, আর লাইনের শেষের ⏎-টি খেয়ে ফেলে কিন্তু ফেরত দেয় না। এই দুটি স্বভাব থেকেই জন্ম নেয় Java শিক্ষার্থীদের সবচেয়ে পরিচিত বাগ। প্রোগ্রাম রোল নম্বর (সংখ্যা) চায়, তারপর নাম (পুরো লাইন)। ব্যবহারকারী টাইপ করেন 12, Enter, Rafiq Islam, Enter. অথচ নাম আসে খালি — প্রোগ্রাম নাম চাওয়ার জায়গাটি যেন লাফিয়ে পার হয়ে যায়। ধাপে ধাপে দেখুন:

1int roll = sc.nextInt();2String name = sc.nextLine();   // ভুল3// ---- সমাধান ----4int roll = sc.nextInt();5sc.nextLine();                 // ⏎ ফেলে দাও6String name = sc.nextLine();
ইনপুট: 12⏎Rafiq Islam⏎
বাফারে বাকি
roll
name
  1. nextInt() সংখ্যাটুকুই নেয়। token 12 পড়ে int বানাল। কিন্তু ব্যবহারকারী 12-এর পরে যে Enter চেপেছিলেন, সেই ⏎ কোনো সংখ্যার অংশ নয় — তাই nextInt সেটি ছোঁয়নি। বাফারের একদম সামনে এখন ⏎.
  2. nextLine() খালি হাতে ফিরল। nextLine-এর কাজ “পরের ⏎ পর্যন্ত যা আছে দাও”. পরের ⏎ একদম সামনেই! তাই মাঝখানে কিছু নেই — ফল "", খালি String. ব্যবহারকারী নাম টাইপ করার সুযোগই পাননি (নিজের কম্পিউটারে প্রোগ্রামটি অপেক্ষা না করেই এগিয়ে যায়)। কোনো error হয়নি — বাগটি নিঃশব্দ, আর তাই বিপজ্জনক।
  3. নামটি এখনো বাফারে বসে আছে। Rafiq Islam⏎ পড়ে রইল পরের প্রশ্নের জন্য। পরের ডাক যদি nextInt() হয় (যেমন বয়স), সে পাবে token Rafiq — আর প্রোগ্রাম InputMismatchException দিয়ে থেমে যাবে। অর্থাৎ একটি ভুলের ফল দেখা দেয় দুই প্রশ্ন পরে, অন্য জায়গায়। তাই ভুলটি খুঁজে পাওয়া কঠিন।
  4. সমাধান — একই ইনপুট দিয়ে আবার শুরু। nextInt() আগের মতোই 12 নিল, ⏎ রেখে দিল। এ পর্যন্ত কোনো পার্থক্য নেই।
  5. বাড়তি একটি nextLine(). এর ফল আমরা কোনো variable-এ রাখছি না — শুধু লাইনের বাকি অংশ (এখানে শুধু ⏎) খেয়ে ফেলাই উদ্দেশ্য। বাফারের সামনে এখন Rafiq Islam⏎.
  6. এবার nextLine পুরো নাম পেল। "Rafiq Islam" — মাঝের space-সহ। নিয়মটি মনে রাখুন: token পড়ার method (nextInt, nextDouble, next) আর nextLine পাশাপাশি ব্যবহার করলে, token-এর পরে nextLine-এর আগে একটি বাড়তি nextLine() দিন।

দ্বিতীয় আরেকটি সমাধান আছে, যা অনেক পেশাদার পছন্দ করেন: সব ইনপুট লাইন ধরে পড়ুন, আর সংখ্যা দরকার হলে নিজে বদলে নিন — Integer.parseInt(sc.nextLine().trim()). তখন token আর লাইনের মিশ্রণই থাকে না। নিচের প্রোগ্রামে বাগ আর দুটি সমাধান একসাথে চলছে:

import java.util.Scanner;

class Main {
    public static void main(String[] args) {
        Scanner sc = new Scanner(System.in);

        // ভুল: nextInt-এর ঠিক পরে nextLine
        int roll = sc.nextInt();
        String name = sc.nextLine();
        System.out.println("Roll " + roll + ", name = [" + name + "]");
        System.out.println("Still waiting: [" + sc.nextLine() + "]");

        // সমাধান ১: সংখ্যার পরে লাইনের বাকিটুকু ফেলে দিন
        int roll2 = sc.nextInt();
        sc.nextLine();
        String name2 = sc.nextLine();
        System.out.println("Roll " + roll2 + ", name = [" + name2 + "]");

        // সমাধান ২: সব লাইন nextLine দিয়ে পড়ে নিজে সংখ্যায় বদলান
        int roll3 = Integer.parseInt(sc.nextLine().trim());
        String name3 = sc.nextLine();
        System.out.println("Roll " + roll3 + ", name = [" + name3 + "]");
    }
}

এই বক্সের ইনপুট আগে থেকে দেওয়া: ছয় লাইনে তিনজন শিক্ষার্থী — 12, Rafiq Islam, 13, Nusrat Jahan, 14, Tanvir Ahmed. নামের চারপাশে [ ] রাখা হয়েছে যাতে খালি String চোখে পড়ে।

প্রথম লাইন Roll 12, name = [] — বাগটি হাতেনাতে ধরা পড়ল। দ্বিতীয় লাইন Still waiting: [Rafiq Islam] প্রমাণ করে নামটি হারায়নি, বাফারে অপেক্ষা করছিল। এরপর সমাধান ১ দিল Roll 13, name = [Nusrat Jahan], আর সমাধান ২ দিল Roll 14, name = [Tanvir Ahmed]. সমাধান ২-এ trim() (পর্ব ০৮) লাইনের আগে-পিছের বাড়তি space কেটে দেয়, আর Integer.parseInt লেখাকে int-এ বদলায়। লেখাটি সংখ্যা না হলে এটি NumberFormatException দেয় — Scanner-এর exception-এর আত্মীয়, পরের অংশে দেখবেন।

৪ · ভুল ইনপুট, Scanner বন্ধ করা, আর দ্রুত বিকল্প

ব্যবহারকারী সংখ্যার জায়গায় লেখা দিলে

প্রোগ্রাম বয়স চাইল, ব্যবহারকারী লিখলেন bish (বিশ)। nextInt() token-টি পড়ে দেখল এটি পূর্ণসংখ্যা নয়, তখন সে ছোড়ে InputMismatchException. অনলাইন runner-এ আসল আউটপুট:

Age: Exception in thread "main" java.util.InputMismatchException
	at java.base/java.util.Scanner.throwFor(Scanner.java:947)
	at java.base/java.util.Scanner.next(Scanner.java:1602)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2267)
	at java.base/java.util.Scanner.nextInt(Scanner.java:2221)
	at Main.main(prog.java:7)

পর্ব ০৪-এ শেখা নিয়মে stack trace পড়ুন: exception-এর নাম java.util.InputMismatchException, কোনো বিস্তারিত বার্তা নেই। নিচের চারটি java.base/…Scanner লাইন হলো Java-র নিজের কোডের ভেতরের পথ — সেগুলো আপনার দোষ নয়। আপনার কোডের প্রথম লাইনটি খুঁজুন: at Main.main(prog.java:7) — লাইন ৭-এ nextInt(). এটি একটি unchecked exception, তাই compiler আগে থেকে সতর্ক করেনি।

প্রোগ্রাম থেমে যাওয়ার আগেই উঁকি দিয়ে দেখা যায়: hasNextInt() শুধু জানায় পরের token পূর্ণসংখ্যা কি না, কিছু পড়ে না। if দিয়ে সিদ্ধান্ত নেওয়া পরের পর্বে বিস্তারিত শিখবেন, তবে ধারণাটি এখনই সহজ:

System.out.print("Age: ");
if (sc.hasNextInt()) {              // পরের token কি পূর্ণসংখ্যা?
    int age = sc.nextInt();
    System.out.println("Age = " + age);
} else {
    String bad = sc.next();         // ভুল token-টি সরিয়ে ফেলা জরুরি
    System.out.println("Please type a number, not: " + bad);
}

ইনপুট bish হলে ছাপা হবে Age: Please type a number, not: bish. else-এর ভেতরে sc.next() দিয়ে ভুল token-টি সরানো জরুরি — নইলে সেটি বাফারের সামনেই থেকে যায়, আর বারবার জিজ্ঞেস করলে প্রতিবার একই ভুল token দেখা যাবে। পর্ব ১১-এ loop শিখলে “সঠিক সংখ্যা না দেওয়া পর্যন্ত আবার জিজ্ঞেস করো” লিখতে পারবেন, আর পর্ব ২৯-এ শিখবেন try-catch দিয়ে exception ধরা।

দুটি অপ্রত্যাশিত স্বভাব — runner-এ যাচাই করা। (১) ডিফল্ট US locale-এ nextInt() লেখা 1,234-কে গ্রহণ করে 1234 বানায় — কমাকে হাজারের বিভাজক ধরে। (২) Scanner বাংলা অঙ্কও চেনে: ১২৩ পড়ে দেয় 123. প্রথমটি কখনো কখনো ভুল ইনপুটকে চুপচাপ পার হতে দেয়, তাই টাকার অঙ্ক পড়ার সময় খেয়াল রাখুন।

Scanner কি বন্ধ করতে হয়?

IDE প্রায়ই সতর্ক করে: “Resource leak: sc is never closed”. ফাইল থেকে পড়া Scanner কাজ শেষে অবশ্যই বন্ধ করবেন (sc.close(), বা পর্ব ২৯-এর try-with-resources). কিন্তু System.in-এর Scanner আলাদা। Scanner বন্ধ করলে তার নিচের নলটিও বন্ধ হয় — আর System.in একবার বন্ধ হলে প্রোগ্রামের বাকি জীবনে আর খোলা যায় না। Runner-এ যাচাই করা: sc.close()-এর পরে নতুন new Scanner(System.in).nextLine() ডাকলে পাওয়া যায় java.util.NoSuchElementException: No line found.

নিয়মটি তাই সরল: পুরো প্রোগ্রামে System.in-এর জন্য একটিই Scanner বানান, শুরুতে; আর হয় একেবারে শেষে বন্ধ করুন, নয়তো বন্ধই করবেন না — প্রোগ্রাম শেষ হলে অপারেটিং সিস্টেম নিজেই নল বন্ধ করে দেয়। প্রতিটি method-এ নতুন Scanner বানিয়ে বন্ধ করা একটি সাধারণ ভুল, যা দ্বিতীয়বার ইনপুট চাইলেই ধরা পড়ে।

BufferedReader — বেশি ইনপুটের জন্য দ্রুত পথ

Scanner আরামদায়ক, কিন্তু ধীর: প্রতিটি token চিনতে সে regular expression ব্যবহার করে, আর সংখ্যা যাচাইয়ে অনেক নিয়ম মানে (locale, কমা, বাংলা অঙ্ক…)। দশ লাখ সংখ্যা পড়তে হলে এই খরচ চোখে পড়ে। প্রোগ্রামিং কনটেস্টে তাই প্রায় সবাই ব্যবহার করেন BufferedReader — যে শুধু পুরো লাইন পড়ে, আর বাকি কাজ আপনি নিজে করেন:

import java.io.*;
import java.util.StringTokenizer;

class Main {
    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        int n = Integer.parseInt(br.readLine().trim());       // লাইন ১: "3"
        StringTokenizer st = new StringTokenizer(br.readLine()); // লাইন ২: "40 2"
        long a = Long.parseLong(st.nextToken());
        long b = Long.parseLong(st.nextToken());
        System.out.println(n + " " + (a + b));                  // 3 42
    }
}

এখানে System.in (কাঁচা বাইট) মোড়ানো হয়েছে InputStreamReader-এ (বাইট → অক্ষর), সেটি আবার মোড়ানো BufferedReader-এ (একবারে বড় টুকরো পড়ে জমিয়ে রাখা, লাইন ধরে দেওয়া)। পেঁয়াজের খোসার মতো স্তরে স্তরে মোড়ানো — ভাবনার প্রশ্ন ৫-এ দেখবেন কেন Java-র I/O এভাবে নকশা করা। throws IOException লাগে কারণ readLine() একটি checked exception ঘোষণা করে (পর্ব ২৯)। কনটেস্টের আরও কৌশল — দ্রুত আউটপুটের জন্য PrintWriter/StringBuilder — পর্ব ৫৪-এ।

C/C++/Python জানলে

Scanner-এর nextInt() অনেকটা C-র scanf("%d")-এর মতো whitespace বাদ দিয়ে token পড়ে, আর nextInt/nextLine বাগটি C-র scanf-এর পরে fgets-এর পুরোনো সমস্যারই যমজ। পার্থক্য: ভুল ইনপুটে C নিঃশব্দে ব্যর্থ হয় (return value দেখতে হয়), Java exception ছোড়ে। Python-এর input() সবসময় পুরো লাইন দেয় — Java-র “সমাধান ২”-এর মতো। printf-এর ছাঁচ C থেকেই নেওয়া, তবে Java-তে %n, %,d, %b আছে, আর টাইপ না মিললে compile হলেও চলার সময় exception আসে। বিস্তারিত তুলনা: C-র printf আর scanf, Python-এর ইনপুট-আউটপুট।

৫ · printf-এর ছাঁচ — format specifier-এর ভাষা

এবার উল্টো দিক: মানকে সুন্দর লেখায় সাজানো। printf-এর প্রথম argument একটি format string — সাধারণ লেখার মাঝে % দিয়ে শুরু হওয়া কিছু “ফাঁকা ঘর”, যাদের বলে format specifier. বাকি argument-গুলো ক্রমানুসারে সেই ঘরগুলোতে বসে। একই ছাঁচের ভাষা তিন জায়গায় চলে:

System.out.printf("%s is %d%n", "Age", 20);          // সরাসরি ছাপে
String a = String.format("%s is %d", "Age", 20);      // ছাপে না, String বানায়
String b = "%s is %d".formatted("Age", 20);           // একই কাজ, Java 15 থেকে

তিনটিতেই ফল Age is 20. String.format কাজে লাগে যখন সাজানো লেখাটি ছাপার বদলে কোথাও রাখতে চান — ফাইলে, SMS-এ, ডেটাবেসে। formatted() একই জিনিস, শুধু পড়তে একটু স্বাভাবিক; text block-এর সাথে এটি খুব মানায় (অংশ ৮)। নিচের সব ফল অনলাইন runner-এ যাচাই করা ([ ] দেওয়া হয়েছে যাতে ফাঁকা জায়গা দেখা যায়):

ছাঁচমানফলমানে
%d42[42]পূর্ণসংখ্যা (int, long)
%s"Dhaka"[Dhaka]লেখা — যেকোনো মান
%f3.14159[3.141590]দশমিক, ডিফল্ট ৬ ঘর
%.2f3.14159[3.14]দশমিকের পরে ২ ঘর, গোল করে
%,d1234567[1,234,567]হাজারের কমা
%10s"Rafiq"[     Rafiq]অন্তত ১০ ঘর, ডানে ঘেঁষা
%-10s"Rafiq"[Rafiq     ]অন্তত ১০ ঘর, বাঁয়ে ঘেঁষা
%05d42[00042]৫ ঘর, ফাঁকায় শূন্য
%btrue[true]boolean
%c'A'[A]একটি char
%x255[ff]hexadecimal (%X দিলে FF)
%%—[%]একটি আসল % চিহ্ন
%n—নতুন লাইনঅপারেটিং সিস্টেমের নিজস্ব line separator

%n বনাম \n: \n সবসময় একটি অক্ষর (LF). কিন্তু Windows-এ লাইন শেষ হয় দুটি অক্ষরে (CR LF), Linux আর Mac-এ একটিতে। %n বসায় চলতি অপারেটিং সিস্টেমের line separator — Windows-এ \r\n, runner-এর Linux-এ \n. তাই printf-এ %n লেখাই প্রথা। আর মনে রাখুন, printf নিজে থেকে লাইন ভাঙে না — শেষে %n না দিলে পরের লেখা একই লাইনে জুড়ে যাবে।

একটি specifier-এ কয়েকটি অংশ থাকতে পারে, আর তাদের ক্রম নির্দিষ্ট: %, তারপর flag (-, 0, ,), তারপর width (ন্যূনতম ঘর), তারপর . আর precision, শেষে conversion অক্ষর। টাকার অঙ্ক ছাপার সবচেয়ে কাজের ছাঁচ %,10.2f — টুকরো ধরে ধরে গড়ে তুলুন, মান 12345.678:

% , 10 .2 f
  1. % — ছাঁচের শুরু। format string-এ % দেখলেই Formatter বোঝে, “এখান থেকে একটি ফাঁকা ঘরের বর্ণনা শুরু”. এর পরের অক্ষরগুলো একসাথে মিলে একটি specifier. একা % অসম্পূর্ণ — আসল % ছাপতে চাইলে লিখতে হয় %%.
  2. f — conversion: কী ধরনের মান। সবসময় শেষে থাকে, আর এটিই একমাত্র বাধ্যতামূলক অংশ। f মানে দশমিক সংখ্যা (float/double), ডিফল্টে দশমিকের পরে ৬ ঘর: 12345.678000. মানের টাইপ না মিললে — যেমন %d-এ double দিলে — compile ঠিকই হয়, কিন্তু চলার সময় IllegalFormatConversionException: d != java.lang.Double.
  3. , — flag: হাজারের বিভাজক। 12,345.678000. বড় অঙ্ক এক নজরে পড়া যায়। কমা না বিন্দু, আর কত ঘর পরপর — তা ঠিক করে Locale (শেষ ধাপে দেখবেন)। আরও flag: - (বাঁয়ে ঘেঁষা), 0 (শূন্য দিয়ে ভরাট), + (ধনাত্মকেও চিহ্ন)।
  4. 10 — width: অন্তত কত ঘর। ফল বদলাল না! কারণ 12,345.678000-এ ১৩টি অক্ষর, যা ১০-এর বেশি। width হলো ন্যূনতম জায়গা — লেখা বড় হলে Java কেটে ফেলে না, পুরোটাই ছাপে (তাতে টেবিলের কলাম সরে যেতে পারে)। লেখা ছোট হলে বাঁ দিকে space বসে।
  5. .2 — precision: দশমিকের পরে ২ ঘর। 12,345.68 — ৯টি অক্ষর, তাই width ১০ পূরণে সামনে একটি space: [ 12,345.68]. .678 গোল হয়ে .68 হলো। Java-র Formatter গোল করে “half up” নিয়মে: runner-এ %.2f দিয়ে 2.675 হলো 2.68, %.0f দিয়ে 2.5 হলো 3.
  6. একই ছাঁচ, অন্য দেশ। printf(Locale.GERMANY, …) দিলে একই ছাঁচ দেয় [ 12.345,68] — জার্মানিতে হাজারের বিভাজক বিন্দু, দশমিক চিহ্ন কমা! ছাঁচ বলে “কী দেখাতে হবে”, Locale বলে “কোন দেশের রীতিতে”. পরের অংশ এটি নিয়েই।

বাস্তব উদাহরণ — বিদ্যুৎ বিলের হিসাব

ইউনিট পড়ে বিদ্যুৎ বিলের একটি আনুমানিক হিসাব (দরগুলো কাল্পনিক — আসল বিল হয় ধাপে ধাপে বাড়া দরে, যা পর্ব ১০-এর if শেখার পরে লিখতে পারবেন):

int units = sc.nextInt();               // ইনপুট: 175
double energy = units * 7.50;           // কাল্পনিক দর: ইউনিটপ্রতি ৭.৫০ টাকা
double demand = 42.0;                   // কাল্পনিক ডিমান্ড চার্জ
double vat = (energy + demand) * 0.05;  // ৫% VAT
double total = energy + demand + vat;

System.out.printf(Locale.US, "%-14s%,10.2f%n", "Energy", energy);
System.out.printf(Locale.US, "%-14s%,10.2f%n", "Demand charge", demand);
System.out.printf(Locale.US, "%-14s%,10.2f%n", "VAT (5%)", vat);
System.out.printf(Locale.US, "%-14s%,10.2f%n", "Total", total);
Energy          1,312.50
Demand charge      42.00
VAT (5%)           67.73
Total           1,422.23

প্রতিটি লাইনে একই ছাঁচ: নাম ১৪ ঘরে বাঁয়ে, টাকা ১০ ঘরে ডানে, কমা আর দুই দশমিকসহ। ফলে সব দশমিক বিন্দু একই কলামে — হিসাব মেলানো সহজ। লক্ষ করুন, "VAT (5%)"-এর ভেতরের % সমস্যা করেনি, কারণ সেটি argument, format string নয়। format string-এর ভেতরে লিখলে %% লাগত।

৬ · Locale — একই সংখ্যা, দেশে দেশে আলাদা চেহারা

১২ লক্ষ ৩৪ হাজার ৫৬৭ টাকা ৮৯ পয়সা — বিভিন্ন দেশে একই সংখ্যা লেখা হয় বিভিন্নভাবে। Java এই রীতিগুলো জানে একটি Locale object-এর মাধ্যমে (ভাষা + দেশ)। %,.2f ছাঁচে 1234567.891 দিয়ে runner-এ পাওয়া গেল:

Localeফললক্ষণীয়
Locale.US1,234,567.89কমা দিয়ে হাজার, বিন্দু দিয়ে দশমিক
Locale.GERMANY1.234.567,89ঠিক উল্টো!
Locale.FRANCE1 234 567,89হাজারের বিভাজক একটি বিশেষ ফাঁকা অক্ষর
bn-BD১,২৩৪,৫৬৭.৮৯বাংলা অঙ্ক — কিন্তু তিন অঙ্ক পরপর কমা
en-IN1,234,567.89লাখের রীতি নয়, তিন অঙ্ক পরপর

Locale না লিখলে printf ব্যবহার করে ডিফল্ট locale, যা আসে চলতি কম্পিউটারের অপারেটিং সিস্টেমের সেটিং থেকে। এই কোর্সের runner-এ সেটি en_US (যাচাই করা)। কিন্তু একই প্রোগ্রাম জার্মান সেটিংয়ের কোনো সার্ভারে চললে আপনার %.2f হঠাৎ 22,86 দেখাবে — আর সেই আউটপুট যদি অন্য প্রোগ্রাম পড়ে (CSV ফাইল, JSON), সব ভেঙে পড়বে। তাই নিয়ম:

  • মানুষের জন্য আউটপুট (পর্দা, রিপোর্ট): ডিফল্ট locale চলতে পারে, তবে রিপোর্ট সবার কাছে একই রকম দেখাতে চাইলে locale লিখে দিন।
  • যন্ত্রের জন্য আউটপুট (ফাইল, API, লগ, টেস্ট): সবসময় নির্দিষ্ট locale — printf(Locale.US, …) বা String.format(Locale.ROOT, …). নইলে আউটপুট কম্পিউটারভেদে বদলাবে।
  • ইনপুটেও একই কথা: Scanner-ও locale মানে। Runner-এ যাচাই করা: useLocale(Locale.GERMANY) দেওয়া Scanner লেখা 1.75-কে দশমিক হিসেবে নেয় না (InputMismatchException), কিন্তু 1,75 নেয়। নির্দিষ্ট ফরম্যাটের ইনপুট পড়লে sc.useLocale(Locale.US) লিখে দিন।

লাখ-কোটির কমা — Java-তে তৈরি নেই

বাংলাদেশে আমরা লিখি 12,34,567 — শেষে তিন অঙ্ক, তারপর দুই অঙ্ক পরপর কমা। ওপরের টেবিলে দেখলেন, bn-BD বা en-IN দিয়েও printf এই রীতি দেয় না; NumberFormat-ও (runner-এ যাচাই করা) দেয় 12,345,678. JDK-র সংখ্যা-সাজানোর ব্যবস্থা শুধু এক মাপের দল চেনে। দরকার হলে ছোট্ট একটি নিজস্ব method লিখে নেওয়া যায় — method লেখা পর্ব ১২-এ শিখবেন, আজ শুধু ব্যবহার দেখুন:

// শেষের ৩ অঙ্ক আলাদা, বাকিটায় প্রতি ২ অঙ্কে কমা (শুধু অঋণাত্মক সংখ্যার জন্য)
static String lakh(long n) {
    String s = Long.toString(n);
    if (s.length() <= 3) return s;
    String last3 = s.substring(s.length() - 3);
    String rest  = s.substring(0, s.length() - 3);
    return rest.replaceAll("\\B(?=(\\d{2})+$)", ",") + "," + last3;
}
// lakh(123456789) → "12,34,56,789"   lakh(1500000) → "15,00,000"

ভাগ দুটি: শেষের তিন অঙ্ক কেটে রাখা, আর বাকি অংশে একটি regular expression দিয়ে ডান দিক থেকে প্রতি দুই অঙ্কে কমা বসানো। দুটি ফলই runner-এ যাচাই করা: ১২ কোটি ৩৪ লাখ ৫৬ হাজার ৭৮৯, আর ১৫ লাখ। ঋণাত্মক সংখ্যা বা দশমিক এটি সামলায় না — বাস্তব প্রজেক্টে সাধারণত ICU4J-এর মতো লাইব্রেরি ব্যবহার করা হয়, যা লাখের রীতি জানে।

৭ · টেবিল সাজানো — মাসিক খরচের রিপোর্ট, আর System.err

একটি পরিবারের মাসিক খরচের রিপোর্ট। প্রথমে নতুনরা সাধারণত যেভাবে লেখেন — + দিয়ে জুড়ে:

System.out.println("House rent " + 15000.0);
System.out.println("Bazar " + 6500.0);
House rent 15000.0
Bazar 6500.0

কলাম মেলে না, কমা নেই, দশমিক এক ঘরের, আর নাম যত লম্বা, সংখ্যা তত ডানে সরে। printf দিয়ে প্রতিটি কলামের মাপ একবার ঠিক করে দিলে সমস্যা মিটে যায়:

System.out.printf("%-12s%12s%8s%n", "Khat", "Taka", "Share");
System.out.println("-".repeat(32));
System.out.printf("%-12s%,12.2f%7.1f%%%n", "House rent", 15000.0, 15000 * 100.0 / 24500);
System.out.printf("%-12s%,12.2f%7.1f%%%n", "Bazar",       6500.0,  6500 * 100.0 / 24500);
System.out.printf("%-12s%,12.2f%7.1f%%%n", "Transport",   1800.0,  1800 * 100.0 / 24500);
System.out.printf("%-12s%,12.2f%7.1f%%%n", "Net+Mobile",  1200.0,  1200 * 100.0 / 24500);
System.out.println("-".repeat(32));
System.out.printf("%-12s%,12.2f%n", "Total", 24500.0);
Khat                Taka   Share
--------------------------------
House rent     15,000.00   61.2%
Bazar           6,500.00   26.5%
Transport       1,800.00    7.3%
Net+Mobile      1,200.00    4.9%
--------------------------------
Total          24,500.00

কৌশলটি হলো শিরোনাম আর প্রতিটি সারির জন্য একই width: নাম ১২ ঘর বাঁয়ে (%-12s), টাকা ১২ ঘর ডানে (শিরোনামে %12s, সংখ্যায় %,12.2f), শতাংশ ৭ ঘর আর তারপর একটি আসল % চিহ্ন (%7.1f%%) — মোট ৮ ঘর, শিরোনামের %8s-এর সমান। লেখা বাঁয়ে, সংখ্যা ডানে — এটাই হিসাবের টেবিলের সর্বজনীন রীতি, কারণ ডানে ঘেঁষলে একক, দশক, শতক একই কলামে দাঁড়ায়। "-".repeat(32) (পর্ব ০৮) দাগটি টেবিলের মোট প্রস্থের সমান রাখে: ১২ + ১২ + ৮ = ৩২. একই কাঠামোর চারটি লাইন বারবার লেখা দৃষ্টিকটু লাগছে? পর্ব ১১-এ loop আর পর্ব ১৩-এ array দিয়ে এটি দুই লাইনে নামবে।

বাংলা লেখায় সারি মেলে না কেন

একই টেবিলে খাতের নাম বাংলায় লিখলে কলাম আঁকাবাঁকা হয়ে যেতে পারে। কারণ printf ঘর গোনে char দিয়ে, পর্দার চওড়া দিয়ে নয়। "রফিক".length() হলো 4 (র, ফ, ি, ক — চারটি char), অথচ পর্দায় দেখায় প্রায় তিনটি অক্ষরের জায়গায়, কারণ ি-কার ফ-এর সাথে মিশে যায়। Runner-এ [%-8s] দিয়ে Rafiq হলো [Rafiq   ] (৩টি space), আর রফিক হলো [রফিক    ] (৪টি space). তার ওপর বাংলা font-এর অক্ষরগুলো সাধারণত monospace নয়। ব্যবহারিক সমাধান: সারি মেলানো দরকার এমন কলামে ইংরেজি বা সংখ্যা রাখুন, বাংলা লেখা রাখুন শেষ কলামে, যেখানে পরে আর কিছু মেলাতে হয় না।

System.err — error-এর আলাদা পথ

System.err.println("Error: file not found"); দেখতে println-এর মতোই, পর্দাতেও সাধারণত একসাথে দেখায়। তাহলে আলাদা নল কেন? কারণ terminal-এ আউটপুট ফাইলে পাঠানো যায়: java Main > report.txt লিখলে System.out-এর সব লেখা ফাইলে যায়, কিন্তু System.err-এর লেখা তবু পর্দায় আসে — ফলে রিপোর্ট ফাইলে error মেশে না, আর ব্যবহারকারী সমস্যার খবর দেখতে পান। Error আলাদা ফাইলে রাখতে 2> errors.txt. IDE-গুলো System.err-এর লেখা সাধারণত লাল রঙে দেখায়। এই কোর্সের চালানো যায় এমন কোড কখনো System.err-এ লেখে না, কারণ runner সেখানে কিছু পেলেই ধরে নেয় প্রোগ্রাম ব্যর্থ হয়েছে।

পরীক্ষা ও ইন্টারভিউতে
  • nextInt()-এর পরে nextLine() খালি String দেয় কেন? — nextInt সংখ্যার পরের \n বাফারে রেখে দেয়; nextLine সেই \n পর্যন্ত পড়ে খালি ফেরত দেয়। মাঝে একটি বাড়তি nextLine() দিন।
  • next() আর nextLine()-এর পার্থক্য? — next() whitespace পর্যন্ত একটি শব্দ পড়ে; nextLine() লাইনের বাকি সবটুকু (space-সহ) পড়ে আর শেষের newline খেয়ে ফেলে।
  • Scanner আর BufferedReader-এর মধ্যে কোনটি দ্রুত, কেন? — BufferedReader; সে শুধু লাইন পড়ে, আর Scanner প্রতিটি token regex দিয়ে যাচাই ও parse করে।
  • printf-এ %n আর \n-এর পার্থক্য? — \n সবসময় LF; %n অপারেটিং সিস্টেমের line separator (Windows-এ CRLF).
  • String.format("%05.1f", 3.14159)-এর ফল? — 003.1: মোট ৫ ঘর, এক দশমিক, ফাঁকায় শূন্য।

৮ · text block — বহু লাইনের লেখা এক টুকরোয়

বিল তৈরি হলে গ্রাহককে একটি SMS পাঠাতে হবে, চার লাইনের। পুরোনো Java-য় লিখতে হতো "প্রিয় " + name + ",\n" + "এ মাসে…\n" + … — কোট, \n আর +-এর জঙ্গল। JSON বা HTML লিখতে গেলে ভেতরের প্রতিটি "-এর আগে \ দিতে হতো। Java 15 থেকে স্থায়ী text block এই সমস্যার সমাধান: তিনটি ডাবল কোট """ দিয়ে শুরু, তারপর নতুন লাইন, তারপর লেখা যেমন দেখতে চান ঠিক তেমন:

String sms = """
        প্রিয় %s,
        এ মাসে ব্যবহার: %d ইউনিট
        মোট বিল: %,.2f টাকা (VAT সহ)
        ধন্যবাদ।
        """.formatted(name, units, total);
System.out.print(sms);

নাম Nusrat Jahan, ১৭৫ ইউনিট আর আগের অংশের মোট দিয়ে runner-এ আউটপুট:

প্রিয় Nusrat Jahan,
এ মাসে ব্যবহার: 175 ইউনিট
মোট বিল: 1,422.23 টাকা (VAT সহ)
ধন্যবাদ।

text block আসলে একটি সাধারণ String — compile-এর সময়ই তৈরি হয়ে যায়, তাই formatted(), length(), replace() সবই চলে। টেমপ্লেট (ছাঁচ) আর তথ্য আলাদা থাকল: SMS-এর ভাষা বদলাতে কোডের হিসাব ছুঁতে হয় না। একইভাবে JSON লেখা যায়, ভেতরের কোট সরাসরি লিখে:

String json = """
    {
      "name": "%s",
      "units": %d
    }""".formatted(name, units);

কিন্তু একটি প্রশ্ন: কোডে লেখাগুলো ৮ ঘর ভেতরে, অথচ আউটপুটে লাইনের শুরুতে কোনো space নেই। কেন? Java নিজে “অপ্রাসঙ্গিক” indentation কেটে ফেলে। কতটা কাটে, তা ঠিক হয় একটি সরল নিয়মে (· মানে একটি space):

কোডে যেমন লেখাধাপ ১
String msg = """
········Priyo grahok,
··········Bill: 1,422 tk
········Dhonnobad
········""";
সবচেয়ে কম indentation খোঁজা৮
String msg = """
········Priyo grahok,
··········Bill: 1,422 tk
········Dhonnobad
········""";
ফলাফলের String৮ ঘর কাটা হলো
Priyo grahok,⏎
··Bill: 1,422 tk⏎
Dhonnobad⏎
শেষের """ ৪ ঘর বাঁয়ে সরালেসবচেয়ে কম এখন ৪
String msg = """
········Priyo grahok,
··········Bill: 1,422 tk
········Dhonnobad
····""";

····Priyo grahok,⏎
······Bill: 1,422 tk⏎
····Dhonnobad⏎
""" শেষ লাইনের সাথে লাগানোশেষে ⏎ নেই
String msg = """
········Priyo grahok,
··········Bill: 1,422 tk
········Dhonnobad""";

Priyo grahok,⏎
··Bill: 1,422 tk⏎
Dhonnobad   ← এখানেই শেষ
  1. কোডে যেমন লেখা। খোলার """-এর পরে সেই লাইনে আর কিছু লেখা যায় না — লেখা শুরু হয় পরের লাইন থেকে। প্রতিটি লাইন ৮ ঘর ভেতরে, শুধু Bill লাইনটি ১০ ঘর (ইচ্ছা করে একটু ভেতরে রাখা)। শেষের """-ও নিজের লাইনে, ৮ ঘর ভেতরে।
  2. সবচেয়ে কম indentation খোঁজা। compiler প্রতিটি লেখাওয়ালা লাইন এবং শেষের """-এর লাইনের শুরুর space গোনে, আর সবচেয়ে ছোটটি নেয় — এখানে ৮. এই ৮ ঘরকে বলে incidental (অপ্রাসঙ্গিক) whitespace — শুধু কোড সুন্দর দেখানোর জন্য, লেখার অংশ নয়। খালি লাইন এই গোনায় ধরা হয় না।
  3. প্রতিটি লাইন থেকে ৮ ঘর কাটা। ফল: Priyo grahok, লাইনের শুরুতে কোনো space নেই, কিন্তু Bill লাইনে বাড়তি ২টি space রয়ে গেল — আপেক্ষিক indentation অক্ষত থাকে। প্রতিটি লাইনের শেষের অদৃশ্য space-ও কেটে ফেলা হয়। আর শেষের """ নিজের লাইনে থাকায় শেষ লাইনের পরে একটি ⏎ আছে।
  4. শেষের """ দিয়ে নিয়ন্ত্রণ। শেষের """-কে ৪ ঘরে নামালে সবচেয়ে কম indentation হয় ৪. তখন প্রতিটি লাইনে ৪টি space থেকে যায়। অর্থাৎ পুরো লেখাকে একটু ভেতরে সরাতে চাইলে শুধু শেষের """ বাঁয়ে সরান। (runner-এ যাচাই করা।)
  5. """ শেষ লাইনের সাথে লাগালে। তখন শেষে কোনো ⏎ থাকে না — String শেষ হয় Dhonnobad-এ। println দিয়ে ছাপতে চাইলে এটাই সুবিধাজনক; print দিয়ে ছাপলে নিজের লাইনে রাখা """-ই ভালো। দুটি বিশেষ escape-ও আছে: লাইনের শেষে \ দিলে পরের লাইন জুড়ে যায় (লম্বা বাক্য ভাঙতে), আর \s একটি space যা কাটা হয় না।

৯ · AI-এর যুগে — ইনপুট-আউটপুট কোডে AI কোথায় হোঁচট খায়

“একটি Java প্রোগ্রাম লেখো যা নাম আর বয়স নিয়ে…” — এমন অনুরোধে AI কয়েক সেকেন্ডে কোড দেয়, আর সেটি সাধারণত compile-ও হয়। কিন্তু ইনপুট-আউটপুটের ভুলগুলো প্রায়ই compile-এর সময় ধরা পড়ে না। সেগুলো দেখা দেয় আসল ব্যবহারকারী আসল ইনপুট দিলে, বা অন্য দেশের সার্ভারে চালালে।

যে কোড শুধু “সুন্দর ইনপুটে” চলে, তা অর্ধেক লেখা কোড।
ভুল ইনপুট আর অন্য Locale দিয়ে যাচাই না করা পর্যন্ত কাজ শেষ নয়।

AI সাধারণত যে ভুলগুলো করে

AI যা করেকী সমস্যা হয়আপনি যা করবেন
nextInt()-এর পরে সরাসরি nextLine()নাম বা ঠিকানা খালি আসে, পরের সংখ্যায় exceptionবাড়তি nextLine(), অথবা সব লাইন ধরে পড়ে parse
টেবিল বানায় + আর "\t" জুড়েনাম লম্বা হলেই কলাম সরে যায়printf-এ নির্দিষ্ট width: %-12s%,10.2f
Locale ছাড়া String.format("%.2f", …) দিয়ে CSV/JSON লেখেজার্মান বা ফরাসি সেটিংয়ে 3,14 — ফাইল ভেঙে যায়যন্ত্রের জন্য আউটপুটে Locale.US/Locale.ROOT
প্রতিটি method-এ new Scanner(System.in) বানিয়ে close() করেদ্বিতীয়বার ইনপুট চাইলে NoSuchElementExceptionপুরো প্রোগ্রামে একটি Scanner, System.in বন্ধ নয়
ভুল ইনপুট নিয়ে কিছুই ভাবে না“বিশ” লিখলেই প্রোগ্রাম থেমে যায়hasNextInt() দিয়ে যাচাই, ভুল token সরানো

AI-কে ইনপুট-আউটপুটের কোড চাওয়ার সময় শর্তগুলো আগেই বলে দিন — তাহলে ওপরের ভুলগুলোর বেশিরভাগ শুরুতেই এড়ানো যায়:

Java 21-এ একটি console প্রোগ্রাম লিখে দাও। প্রোগ্রামটি কিছু তথ্য পড়বে (যেমন নাম, ওজন, উচ্চতা) আর একটি সাজানো রিপোর্ট ছাপবে। শর্ত: ১. System.in-এর জন্য পুরো প্রোগ্রামে একটিই Scanner, আর সেটি close() করবে না। ২. পুরো লাইন (নাম) আর সংখ্যা মেশালে nextInt/nextLine-এর বাড়তি newline সমস্যা এড়াবে — সম্ভব হলে সব ইনপুট nextLine() দিয়ে পড়ে Integer.parseInt/Double.parseDouble করবে। ৩. সংখ্যার জায়গায় লেখা দিলে প্রোগ্রাম থেমে যাবে না — hasNextInt() বা exception ধরে আবার জিজ্ঞেস করবে। ৪. টেবিল printf দিয়ে নির্দিষ্ট width-এ (লেখা বাঁয়ে, সংখ্যা ডানে, %,.2f); + দিয়ে জোড়া নয়। ৫. printf/String.format-এ Locale.US স্পষ্ট লিখবে, আর নতুন লাইনের জন্য %n. ৬. কোডের পরে তিনটি পরীক্ষার ইনপুট দাও — একটি স্বাভাবিক, একটি দুই শব্দের নামসহ, একটি ভুল (সংখ্যার জায়গায় লেখা) — আর প্রতিটির প্রত্যাশিত আউটপুট।

AI-এর দেওয়া ইনপুট-আউটপুট কোড যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।

প্র ০১Java ১৯৯৬ সালে এলেও কীবোর্ড থেকে সহজে একটি সংখ্যা পড়ার উপায় (Scanner) এলো ২০০৪ সালে। এত দেরি কেন?

কারণটি বুঝতে Java-র জন্মের সময়ের দিকে তাকাতে হবে। ১৯৯০-এর দশকের মাঝামাঝি Java-র বড় প্রচার ছিল ওয়েব ব্রাউজারের ভেতরে চলা applet আর নেটওয়ার্কে যুক্ত যন্ত্র নিয়ে। ব্যবহারকারীর সাথে কথা বলার পথ ভাবা হয়েছিল গ্রাফিক্যাল — বোতাম, টেক্সট বক্স (AWT, পরে Swing). কালো console-এ প্রশ্ন করে উত্তর পড়া তখন Java-র নকশাকারদের অগ্রাধিকারে ছিল না। পরে Java-র বড় ক্ষেত্র হলো সার্ভার, যেখানে ইনপুট আসে নেটওয়ার্ক আর ফাইল থেকে, কীবোর্ড থেকে নয়।

তাই Java 1.0-তে System.in ছিল শুধু একটি InputStream — কাঁচা বাইটের নল। Java 1.1-এ (১৯৯৭) অক্ষরভিত্তিক Reader আর BufferedReader এলো, ফলে লাইন পড়া গেল। কিন্তু একটি সংখ্যা পড়তে তখনো তিন স্তরের মোড়ক, readLine(), Integer.parseInt() আর বাধ্যতামূলক IOException সামলানো লাগত। নতুনদের জন্য এটি ছিল বিরক্তিকর দেয়াল — অনেক বিশ্ববিদ্যালয় আর বই নিজস্ব helper class বানিয়ে শিক্ষার্থীদের দিত।

২০০৪ সালের Java 5 (কোড-নাম Tiger) ছিল ভাষার ইতিহাসের সবচেয়ে বড় হালনাগাদগুলোর একটি: generics, enum, enhanced for, autoboxing, varargs, annotation — আর সাথে java.util.Scanner. লক্ষ করুন autoboxing আর varargs-ও একই সংস্করণে, যেগুলো সহজ console কোডের জন্য জরুরি ছিল। ততদিনে Python-এর মতো ভাষা শিক্ষায় জনপ্রিয় হচ্ছিল, আর Java-কে “শুরুর ভাষা” হিসেবে টিকে থাকতে সহজ হতে হচ্ছিল।

Scanner-এর নকশাতেও একটি আপস আছে: সে খুব নমনীয় (regex, locale, যেকোনো উৎস — ফাইল, String, নল), কিন্তু সেজন্যই ধীর। তাই আজও দুটি পথ পাশাপাশি টিকে আছে — শেখা আর ছোট প্রোগ্রামে Scanner, বড় ইনপুটে BufferedReader.

মূল উপলব্ধি: একটি ভাষার standard library দেখায় তার নকশাকারেরা কোন ব্যবহারকারীর কথা ভেবেছিলেন। ব্যবহারকারী বদলালে library-ও বদলায় — কখনো কখনো দেরিতে।

প্র ০২printf-ও Java 5-এ এলো, C-র চেহারা নিয়ে। একটি object-oriented ভাষা কেন ১৯৭০-এর দশকের C-র ছাঁচের ভাষা ধার করল?

Java 5-এর আগে সংখ্যা সাজিয়ে ছাপার উপায় ছিল java.text.DecimalFormat আর NumberFormat — শক্তিশালী, locale বোঝে, কিন্তু প্রতিটি সংখ্যার জন্য আলাদা object আর প্যাটার্ন ("#,##0.00"). একটি টেবিল সারির জন্য কয়েক লাইন কোড লাগত, আর width মেলানোর সরল কোনো উপায় ছিল না। অথচ C-র printf("%-12s%10.2f\n", …) এক লাইনে এটা করে আসছিল প্রায় তিন দশক ধরে।

printf যোগ করার পথে একটি প্রযুক্তিগত বাধা ছিল: method-টি যেকোনো সংখ্যক argument নিতে হবে। Java 5-এর আগে তা সম্ভব ছিল না। Java 5-এর varargs (Object... args) এই দরজা খুলল — ভেতরে argument-গুলো একটি array হয়ে যায়। autoboxing-এর ফলে int-এর মতো primitive-ও সরাসরি সেই Object array-তে ঢুকতে পারল। তাই printf, varargs আর autoboxing একসাথে আসা কাকতালীয় নয়।

C-র ছাঁচের ভাষা নেওয়ার কারণ একই, যে কারণে Java-র চেহারা C-র মতো: লাখো প্রোগ্রামার আগেই %d, %s, %.2f জানতেন। তবে Java এটিকে নিরাপদ করেছে। C-তে %d-এ double দিলে undefined behavior — আবর্জনা ছাপা বা ক্র্যাশ। Java-তে প্রতিটি argument একটি object, তাই Formatter চলার সময় টাইপ যাচাই করে স্পষ্ট exception দেয়। Java যোগ করেছে %n (প্ল্যাটফর্ম-নিরপেক্ষ নতুন লাইন), %,d (locale-অনুযায়ী বিভাজক), %b, আর তারিখের জন্য %t.

দুর্বলতাও আছে: ছাঁচ আর argument-এর মিল compiler যাচাই করে না, ভুল ধরা পড়ে চলার সময়। IntelliJ-এর মতো IDE অবশ্য অনেক ভুল আগেই দেখিয়ে দেয়।

মূল উপলব্ধি: ভালো ধারণা যেখান থেকেই আসুক, নিয়ে নেওয়া বুদ্ধিমানের কাজ — তবে নিজের ভাষার নিরাপত্তার নিয়মে মানিয়ে নিয়ে।

প্র ০৩text block এলো Java 15-এ (JEP 378). কিন্তু এর আগে “raw string literal” (JEP 326) নামে একটি প্রস্তাব বাতিল হয়েছিল। কী হয়েছিল, আর এখান থেকে কী শেখার আছে?

বহু লাইনের String-এর অভাব নিয়ে Java প্রোগ্রামারদের অভিযোগ ছিল বহু বছরের। SQL query, HTML, JSON, regular expression — সবকিছুতে "…\n" + আর \"-এর জঙ্গল। ২০১৮ সালে JEP 326 প্রস্তাব করে “raw string literal”: backtick (`) দিয়ে ঘেরা লেখা, যার ভেতরে কোনো escape কাজ করবে না, আর বেশি backtick দিয়ে ঘিরে ভেতরে backtick রাখা যাবে। পরিকল্পনা ছিল JDK 12-এ preview হিসেবে আনা।

কিন্তু JDK 12 প্রকাশের আগেই প্রস্তাবটি প্রত্যাহার করা হয়। আলোচনায় কয়েকটি সমস্যা উঠে আসে: কোডে লেখাটি যেভাবে ভেতরে সরানো থাকে, তার indentation কী হবে, তার কোনো ভালো উত্তর ছিল না; “কোনো escape নয়” নীতি সব ক্ষেত্রে সুবিধাজনক নয়; আর নতুন একটি delimiter চিহ্ন ভাষায় স্থায়ীভাবে যোগ করার আগে আরও ভাবা দরকার। Java-র স্থপতিরা প্রকাশ্যে বললেন, ভুল নকশা স্থায়ী করার চেয়ে পিছিয়ে আসা ভালো।

এরপর নতুন করে ভাবা হলো। JEP 355 (JDK 13, প্রথম preview) আনল """ — Python আর Kotlin-এর পরিচিত চিহ্ন — আর সবচেয়ে বড় উদ্ভাবন: incidental whitespace কাটার নিয়ম, যা এই পর্বের stepper-এ দেখেছেন। JEP 368 (JDK 14, দ্বিতীয় preview) যোগ করল \ (লাইন জোড়া) আর \s escape. শেষে JEP 378 Java 15-এ বৈশিষ্ট্যটিকে স্থায়ী করে। সাথে String-এ এলো formatted(), stripIndent() আর translateEscapes().

লক্ষ করুন, চূড়ান্ত নকশা “raw” নয় — escape কাজ করে। কারণ বাস্তবে বেশিরভাগ ব্যবহার (SQL, JSON, HTML) চায় পরিষ্কার বহু লাইন আর সুন্দর indentation, escape বন্ধ করা নয়।

মূল উপলব্ধি: লাখো প্রজেক্টের ভাষায় কোনো বৈশিষ্ট্য একবার স্থায়ী হলে আর ফেরানো যায় না। তাই “না” বলা, পিছিয়ে আসা আর preview দিয়ে পরীক্ষা করা দুর্বলতা নয় — দায়িত্বশীল প্রকৌশল।

প্র ০৪Scanner দিয়ে পাসওয়ার্ড পড়লে টাইপ করা অক্ষর পর্দায় দেখা যায়। System.console() কীভাবে সাহায্য করে, আর পাসওয়ার্ড কেন String-এ না রেখে char[]-এ রাখার পরামর্শ দেওয়া হয়?

অংশ ১-এ দেখেছেন, টাইপ করা লেখা পর্দায় দেখায় terminal, প্রোগ্রাম নয়। Scanner শুধু নল থেকে পড়ে — terminal-কে “এবার অক্ষর দেখিও না” বলার ক্ষমতা তার নেই। পাশে বসা কেউ পাসওয়ার্ড দেখে ফেলতে পারে। Java 6 (২০০৬) এই সমস্যার জন্য আনে java.io.Console class, যা পাওয়া যায় System.console() দিয়ে। এর readPassword("Password: ") method terminal-এর echo বন্ধ রেখে পাসওয়ার্ড পড়ে — ATM-এ PIN দেওয়ার সময় যেমন পর্দায় শুধু তারকা বা কিছুই দেখায় না।

একটি সতর্কতা: সত্যিকারের interactive terminal না থাকলে — যেমন ইনপুট ফাইল বা পাইপ থেকে এলে, বা কিছু IDE-র ভেতরে — System.console() null দিতে পারে। এই কোর্সের runner-এ Java 21 সত্যিই null দিয়েছে (যাচাই করা)। পরের কিছু Java সংস্করণে এই আচরণে পরিবর্তন এসেছে, তাই null যাচাই করে বিকল্প পথ রাখাই নিরাপদ।

কেন char[]? readPassword ইচ্ছা করেই String নয়, char[] ফেরত দেয়। পর্ব ০৮-এ দেখেছেন String immutable — একবার তৈরি হলে বদলানো যায় না। তাই পাসওয়ার্ড String-এ রাখলে কাজ শেষে মুছে ফেলার উপায় নেই; garbage collector কবে মেমরি পরিষ্কার করবে তা অনিশ্চিত, আর তার আগে memory dump বা heap-এর কপিতে পাসওয়ার্ড পড়ে থাকতে পারে। char[] হলে কাজ শেষেই Arrays.fill(pw, ' ') দিয়ে ঘরগুলো মুছে ফেলা যায়। এটি নিখুঁত নিরাপত্তা নয়, তবে ঝুঁকির সময়টুকু ছোট করে। Java-র নিরাপত্তা নির্দেশিকা এই অভ্যাসের পরামর্শ দেয়।

বাস্তবে আজকের বেশিরভাগ Java অ্যাপে পাসওয়ার্ড আসে ওয়েব ফর্ম বা API দিয়ে (পর্ব ৫১), console-এ নয়। তবু নীতিটি একই: গোপন তথ্য যত কম সময় আর যত কম জায়গায় থাকে, তত ভালো — আর কখনো লগ বা System.out-এ ছাপবেন না।

মূল উপলব্ধি: ইনপুট শুধু “মান পড়া” নয়; কে দেখছে, কোথায় জমা থাকছে, কতক্ষণ থাকছে — এগুলোও নকশার অংশ।

প্র ০৫BufferedReader-এর ভেতরে InputStreamReader, তার ভেতরে System.in — Java-র I/O কেন এমন স্তরে স্তরে মোড়ানো? একটি Keyboard.readInt() থাকলেই তো হতো।

এটি একটি সচেতন নকশা, যার নাম decorator pattern. ধারণাটি সরল: একটি মৌলিক নল (বাইটের উৎস), আর তার চারপাশে একেকটি মোড়ক, যা একটি করে নতুন ক্ষমতা যোগ করে — অথচ বাইরে থেকে দেখতে সবাই একই ধরনের “নল”. InputStreamReader যোগ করে বাইট থেকে অক্ষরে অনুবাদ (UTF-8 ইত্যাদি encoding), BufferedReader যোগ করে জমিয়ে রাখা আর লাইন ধরে পড়া। চাইলে আরও মোড়ক — যেমন GZIPInputStream দিয়ে zip খোলা — যেকোনো ক্রমে জোড়া যায়।

বিকল্পটি ভাবুন। উৎস কয়েক রকম: কীবোর্ড, ফাইল, নেটওয়ার্ক, মেমরি। ক্ষমতাও কয়েক রকম: buffer, encoding, compression, encryption, লাইন গোনা। প্রতিটি মিশ্রণের জন্য আলাদা class বানালে (BufferedGzipFileReader, EncryptedNetworkLineReader…) class-এর সংখ্যা গুণফলে বাড়ত। মোড়ক থাকলে লাগে শুধু যোগফল — প্রতিটি উৎসের একটি, প্রতিটি ক্ষমতার একটি। এটাই এই নকশার শক্তি। ১৯৯৪ সালের বিখ্যাত বই Design Patterns (Gang of Four)-এ এই pattern-এর বর্ণনা আছে, আর java.io একে পাঠ্যবইয়ের উদাহরণ বানিয়েছে। পর্ব ৪৮-এ design pattern নিয়ে বিস্তারিত দেখবেন।

দামও আছে। নতুনদের জন্য প্রথম দিনেই তিন স্তর বোঝা কঠিন, আর সবচেয়ে সাধারণ কাজের জন্য কোনো শর্টকাট না থাকা ছিল প্রথম যুগের Java-র বড় অভিযোগ। Java এর জবাব দিয়েছে স্তর না ভেঙে, ওপরে সুবিধাজনক “ফটক” যোগ করে: Java 5-এ Scanner, Java 6-এ Console, আর পর্ব ৩৭-এ দেখবেন Java 7-এর java.nio.file.Files — যেখানে Files.readAllLines(path) এক লাইনে পুরো ফাইল পড়ে।

একই ধারণা System.out-এও: এটি একটি PrintStream, যা ভেতরের একটি বাইট-নলকে মুড়ে println আর printf-এর সুবিধা দেয়।

মূল উপলব্ধি: ছোট, একক-দায়িত্বের টুকরো জোড়া লাগিয়ে বড় ক্ষমতা বানানো নমনীয় নকশার মূলমন্ত্র। আর ভালো library নমনীয় ভিত্তির ওপরে সহজ ব্যবহারের পথও রাখে।

অনুশীলন

বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — ওপরের কোড বদলে দেখা আর ভাবাই আসল কাজ।

  1. বাফার অনুমান করুন: ইনপুট 25 Rafiq Islam⏎Dhaka⏎ (প্রথম লাইনে বয়স আর নাম একসাথে)। কোড: int age = sc.nextInt(); String a = sc.nextLine(); String b = sc.nextLine(); — a আর b-তে কী আসবে?

    age = 25. তারপর বাফারে Rafiq Islam⏎Dhaka⏎. প্রথম nextLine() লাইনের বাকিটুকু দেয়: a = " Rafiq Islam" — সামনে একটি space-সহ, কারণ nextLine whitespace বাদ দেয় না। দ্বিতীয়টি দেয় b = "Dhaka". এখানে বাগ হয়নি, কারণ সংখ্যার পরে একই লাইনে লেখা ছিল। কিন্তু সামনের space-টি নামের তুলনায় সমস্যা করবে (" Rafiq Islam".equals("Rafiq Islam") হলো false) — তাই trim() দেওয়া ভালো অভ্যাস।

  2. ছাঁচ পড়ুন: না চালিয়ে বলুন (US locale), কী ছাপা হবে — printf("[%6.1f]", 3.14159), printf("[%-6d]", 42), printf("[%,d]", 50000000), printf("[%5s]", "Bangladesh").

    প্রথম: [ 3.1] — এক দশমিকে 3.1 (৩টি অক্ষর), ৬ ঘর পূরণে সামনে ৩টি space. দ্বিতীয়: [42 ] — বাঁয়ে ঘেঁষা, পেছনে ৪টি space. তৃতীয়: [50,000,000] — পাঁচ কোটি, কিন্তু পশ্চিমা রীতিতে কমা (লাখের রীতি চাইলে অংশ ৬-এর method). চতুর্থ: [Bangladesh] — ১০টি অক্ষর, width ৫-এর চেয়ে বড়, তাই কিছুই কাটা হয়নি; width হলো ন্যূনতম, সর্বোচ্চ নয়। কেটে ছোট করতে চাইলে precision লাগে: %.5s দেয় Bangl.

  3. বাগ ঘটিয়ে দেখুন: অংশ ৩-এর চালানো যায় এমন কোডে “সমাধান ১”-এর sc.nextLine(); লাইনটি মুছে চালান। আউটপুট কী হলো, আর প্রোগ্রাম কোথায় থামল? কেন?

    প্রথম দুই লাইন আগের মতোই। তারপর roll2 = 13, কিন্তু name2-তে আসে খালি String, তাই ছাপা হয় Roll 13, name = []. এবার বাফারের সামনে Nusrat Jahan⏎14⏎Tanvir Ahmed. সমাধান ২-এর Integer.parseInt(sc.nextLine().trim()) পায় "Nusrat Jahan" — যা সংখ্যা নয় — তাই NumberFormatException: For input string: "Nusrat Jahan" দিয়ে প্রোগ্রাম থামে। একটি লাইন বাদ পড়ায় ভুল দেখা দিল পরের ধাপে, অন্য জায়গায় — ঠিক যেমন stepper-এর ধাপ ৩-এ দেখেছেন।

  4. রসিদ বানান: একটি মুদি দোকানের রসিদ printf দিয়ে লিখুন — তিনটি পণ্য (নাম, পরিমাণ, দাম), একটি দাগ আর মোট। শর্ত: নাম ১৪ ঘরে বাঁয়ে, পরিমাণ ৪ ঘরে, দাম ১০ ঘরে কমা আর দুই দশমিকসহ, আর মোটের সংখ্যাটি দামের কলামের ঠিক নিচে।

    একটি সম্ভাব্য রূপ (runner-এ যাচাই করা):

    System.out.printf("%-14s%4s%10s%n", "Item", "Qty", "Price");
    System.out.println("-".repeat(28));
    System.out.printf("%-14s%4d%,10.2f%n", "Rice (5 kg)", 1, 425.0);
    System.out.printf("%-14s%4d%,10.2f%n", "Soybean oil", 2, 380.0);
    System.out.printf("%-14s%4d%,10.2f%n", "Egg (dozen)", 1, 150.5);
    System.out.println("-".repeat(28));
    System.out.printf("%-18s%,10.2f%n", "Total", 1335.5);
    Item           Qty     Price
    ----------------------------
    Rice (5 kg)      1    425.00
    Soybean oil      2    380.00
    Egg (dozen)      1    150.50
    ----------------------------
    Total               1,335.50

    মোটের সারিতে পরিমাণের কলাম নেই, তাই নামের width ১৪ + ৪ = ১৮ করা হয়েছে — এতে মোটের সংখ্যা দামের কলামের নিচেই বসে। দাগের দৈর্ঘ্য ১৪ + ৪ + ১০ = ২৮. (২ × ৩৮০ = ৭৬০, তাই মোট ৪২৫ + ৭৬০ + ১৫০.৫০ = ১,৩৩৫.৫০)

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমব্যবহারকারীর টাইপ করা “70” থেকে পর্দায় সাজানো ফল পর্যন্ত ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. ⌨️ ব্যবহারকারী 7, 0 আর Enter চাপেন — অক্ষরগুলো System.in-এ জমা হয়
  2. 🔍 Scanner whitespace দেখে token “70” আলাদা করে
  3. 🔢 nextDouble() token-টি parse করে double 70.0 বানায়
  4. 🧮 প্রোগ্রাম মানটি দিয়ে হিসাব করে
  5. 🖨️ printf ছাঁচ মেনে ফলকে লেখায় সাজিয়ে System.out-এ পাঠায়

ইনপুট “12⏎Rafiq⏎”. কোড: int r = sc.nextInt(); String s = sc.nextLine(); — s-এর মান কী হবে?

  • "Rafiq"
  • "12"
  • "" (খালি String)
  • InputMismatchException

Java-তে System.out.printf("[%-8s]", "Java"); কী ছাপবে?

  • [ Java]
  • [Java ]
  • [Java]
  • [-8Java]

System.in-এর সাথে যুক্ত একটি Scanner-এ close() ডাকার পর নতুন Scanner(System.in) দিয়ে nextLine() ডাকলে কী হয়?

  • নতুন Scanner স্বাভাবিকভাবে পরের লাইন পড়ে
  • compile error হয়
  • প্রোগ্রাম চিরকাল ইনপুটের অপেক্ষায় থাকে
  • NoSuchElementException আসে, কারণ System.in বন্ধ হয়ে গেছে

Java-র printf-এ %n আর \n-এর মধ্যে মূল পার্থক্য কোনটি?

  • %n অপারেটিং সিস্টেমের নিজস্ব line separator বসায়, \n সবসময় একটি LF অক্ষর
  • %n দুটি লাইন ফাঁকা রাখে, \n একটি
  • printf-এ \n কাজ করে না
  • %n পরের argument-কে নতুন লাইনে ছাপে

একটি প্রোগ্রাম String.format("%.2f", 3.5) দিয়ে CSV ফাইলে দাম লেখে। জার্মান সেটিংয়ের সার্ভারে চালালে ফাইলে কী যেতে পারে, আর সমাধান কী?

  • সবসময় 3.50, কারণ Java সব জায়গায় একই আউটপুট দেয়
  • FormatException আসবে; try-catch দিয়ে সমাধান
  • 3,50 — তাই যন্ত্রের জন্য আউটপুটে Locale.US বা Locale.ROOT স্পষ্ট লিখতে হবে
  • 4 — জার্মান locale দশমিক বাদ দেয়

Java text block-এ শেষের """ নিজের লাইনে, আর সব লেখাওয়ালা লাইনের চেয়ে ৪ ঘর বাঁয়ে রাখা হলো। ফলাফলের String-এ কী ঘটবে?

  • কিছুই বদলাবে না; শেষের """ indentation-এ প্রভাব ফেলে না
  • প্রতিটি লাইনের শুরুতে ৪টি space থেকে যাবে
  • compile error হবে
  • সব লাইন এক লাইনে জুড়ে যাবে

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:

মনে রাখুন

  1. প্রোগ্রামের তিন নল: System.in (ইনপুট), System.out (ফল), System.err (error). নলে শুধু অক্ষর চলে — টাইপ নেই।
  2. Scanner whitespace দেখে token ভাঙে; nextInt()/next() একটি token, nextLine() লাইনের বাকি সবটুকু। token-এর পরে nextLine-এর আগে একটি বাড়তি nextLine().
  3. ভুল ইনপুটে InputMismatchException — আগে hasNextInt() দিয়ে যাচাই করুন। System.in-এর Scanner একটিই, মাঝপথে বন্ধ নয়। বেশি ইনপুটে BufferedReader + Integer.parseInt.
  4. printf/String.format/formatted(): %[flag][width][.precision]conversion — %-12s, %,10.2f, %05d, শেষে %n. যন্ত্রের জন্য আউটপুটে Locale স্পষ্ট লিখুন।
  5. text block (""", Java 15) বহু লাইনের লেখা রাখে; সবচেয়ে কম indentation কেটে ফেলা হয়, আর শেষের """-এর অবস্থান তা নিয়ন্ত্রণ করে।

পরের পর্বে: আজ প্রোগ্রাম শুনতে আর গুছিয়ে বলতে শিখল। কিন্তু সে এখনো সবার জন্য একই কাজ করে। BMI ১৮.৫-এর কম হলে এক কথা, ২৫-এর বেশি হলে আরেক কথা; ভুল ইনপুট হলে আবার জিজ্ঞেস করা — এসবের জন্য লাগে সিদ্ধান্ত। if/else আর Java-র আধুনিক switch expression দিয়ে প্রোগ্রামকে ভাবতে শেখাব।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান