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

static আর final — class-এর ভাগের জিনিস আর না-বদলানো মান

static and final in Java — Shared Class Members, Constants, Final References and Immutable Classes

Taught in Bangla, this lesson explains static and final: class-level fields shared by all objects, static methods and utility classes, why main is static and the static-context error, static blocks and class initialization, final variables and constants, final references versus immutable objects, a recipe for immutable classes, and pitfalls of global state.

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

  • static field আর instance field-এর পার্থক্য — মেমরিতে কোথায় কয়টি কপি থাকে
  • static method, utility class, কেন main static, আর static context-এর সীমা
  • static block, class কখন initialize হয়, static import আর static nested class-এর এক ঝলক
  • final local variable, final field, static final constant আর compile-time constant
  • final reference বনাম immutable object, effectively final, immutable class বানানোর রেসিপি আর static-এর ফাঁদ

১ · দুটি ছোট শব্দ, দুটি আলাদা প্রশ্ন

পর্ব ১৫ থেকে ১৭-তে আপনি class থেকে object বানিয়েছেন, constructor দিয়ে সাজিয়েছেন, আর private দিয়ে ভেতরের ডেটা লুকিয়েছেন। প্রতিটি Student object-এর নিজের name, নিজের roll. কিন্তু কিছু তথ্য কোনো একজন student-এর নয়। “এ পর্যন্ত মোট কতজন ভর্তি হয়েছে?” — এই সংখ্যাটি রহিমের, না মিতুর? কারোরই না। এটা পুরো class-এর তথ্য।

Java-তে এই ধরনের জিনিস চিহ্নিত করার keyword হলো static. আর এই পর্বের দ্বিতীয় keyword final একেবারে আলাদা একটি প্রশ্নের উত্তর দেয়। দুটিকে আলাদা করে মনে রাখুন:

static — “কার জিনিস?”

উত্তর: class-এর নিজের, কোনো একটি object-এর নয়। পুরো প্রোগ্রামে একটিই কপি, সবাই ভাগ করে নেয়। object না বানিয়েও ব্যবহার করা যায়।

মালিকানার প্রশ্ন

final — “বদলানো যাবে?”

উত্তর: না। ভ্যারিয়েবলে একবার মান বসলে আর নতুন মান নয়; method হলে subclass আর নতুন করে লিখতে পারবে না; class হলে কেউ তাকে extend করতে পারবে না।

পরিবর্তনের প্রশ্ন

একটি স্কুলের ক্লাসরুম ভাবুন। প্রত্যেক ছাত্রের নিজের খাতা আছে — তাতে নিজের নাম, নিজের নম্বর। এগুলো instance জিনিস। কিন্তু দেয়ালে একটি নোটিশ বোর্ড ঝোলে — “আজ মোট উপস্থিত: ৩৫”। বোর্ড একটিই, সবাই সেটাই দেখে; কেউ বোর্ডে লিখলে সবাই নতুন লেখা দেখে। এটা static. আর হেডমাস্টারের সই করা, সিল মারা রুটিন — একবার টাঙানো হলে আর কেউ কেটে নতুন রুটিন লিখতে পারে না। এটা final.

দুটি keyword একসাথেও বসতে পারে: static final double VAT_RATE = 0.15; — একটিই কপি (static), আর কখনো বদলাবে না (final). এটাই Java-তে constant লেখার রীতি, অংশ ৫-এ বিস্তারিত। কিন্তু আগে দেখা যাক static জিনিস মেমরিতে আসলে কোথায় থাকে। নিচের Student class-এ school হলো static, name হলো সাধারণ instance field:

ধাপ ১Student class প্রথমবার ব্যবহারের আগে load হলো
class-এর এলাকা (Student)        heap
┌──────────────────────────┐
│ static school = "Dhaka"  │    (এখনো কোনো object নেই)
└──────────────────────────┘
object না থাকলেও static field-এর জায়গা তৈরি
ধাপ ২তিনটি object: new Student("Rahim") ...
class-এর এলাকা (Student)        heap
┌──────────────────────────┐    a ─▶ [ name = "Rahim" ]
│ static school = "Dhaka"  │    b ─▶ [ name = "Mitu"  ]
└──────────────────────────┘    c ─▶ [ name = "Karim" ]
name তিনটি, তিনটি object-এর ভেতরে; school একটিই, বাইরে
ধাপ ৩a.name = "Rahima";
class-এর এলাকা (Student)        heap
┌──────────────────────────┐    a ─▶ [ name = "Rahima" ]
│ static school = "Dhaka"  │    b ─▶ [ name = "Mitu"   ]
└──────────────────────────┘    c ─▶ [ name = "Karim"  ]
instance field বদলালে শুধু সেই object বদলায়
ধাপ ৪Student.school = "Rajshahi";
class-এর এলাকা (Student)        heap
┌──────────────────────────┐    a ─▶ [ name = "Rahima" ]
│ static school = "Rajshahi"│    b ─▶ [ name = "Mitu"   ]
└──────────────────────────┘    c ─▶ [ name = "Karim"  ]
একটি বদল — a, b, c তিনজনই এখন "Rajshahi" দেখে
ধাপ ৫b.school পড়লে কী হয়?
b.school   ──┐
c.school   ──┼──▶  Student.school = "Rajshahi"
Student.school┘
object দিয়ে লিখলেও compiler আসলে Student.school পড়ে — বিভ্রান্তিকর, তাই এড়িয়ে চলুন
  1. Class load হলো, object এখনো নেই। JVM যখন প্রথমবার Student class ব্যবহার করে, তখন class-এর তথ্য load করে আর static field-এর জন্য জায়গা তৈরি করে। কোনো new লেখা হয়নি, তবু Student.school আছে।
  2. তিনটি object, তিনটি name. প্রতিটি new heap-এ একটি নতুন object বানায়, আর প্রতিটি object-এর ভেতরে নিজস্ব name-এর ঘর থাকে। কিন্তু school-এর ঘর কোনো object-এর ভেতরে নেই — সেটা class-এর সাথে একবারই আছে।
  3. Instance field বদলানো। a.name বদলালে শুধু a-র object বদলায়। b আর c কিছুই টের পায় না — প্রত্যেকের নিজের খাতা।
  4. Static field বদলানো। Student.school-এ নতুন মান বসালে ঘর তো একটিই, তাই তিনটি object-ই এখন নতুন মান দেখে। নোটিশ বোর্ডে একবার লিখলে পুরো ক্লাস দেখে।
  5. Object দিয়ে static পড়া। b.school লেখাও বৈধ, কিন্তু compiler সেটাকে Student.school হিসেবেই নেয় — b কোন object, তাতে কিছু যায় আসে না। পড়ার সময় মনে হয় এটা b-এর নিজের জিনিস, তাই IntelliJ-এর মতো IDE সতর্ক করে। সবসময় class-এর নাম দিয়ে লিখুন।

“class-এর এলাকা” আসলে কোথায়? পর্ব ০৫-এ এক ঝলক দেখেছিলেন, Java 8 থেকে HotSpot JVM class-এর তথ্য (method-এর কোড, field-এর তালিকা) রাখে metaspace-এ (আগে রাখত PermGen-এ)। একটি সূক্ষ্ম কথা: static field-এর মানগুলো সেখানে থাকে না — JDK 7 থেকে HotSpot সেগুলো রাখে heap-এ, সেই class-এর Class object-এর সাথে। এটা JVM-এর ভেতরের ব্যবস্থা, ভাষার নিয়ম নয়। ধারণা হিসেবে “class-এর সাথে একটিই কপি” — এটুকু মনে রাখলেই যথেষ্ট; JVM-এর মেমরি নিয়ে বিস্তারিত পর্ব ৪৪-এ।

২ · static field — সবার জন্য একটিই কপি

যে field-এর আগে static লেখা, সেটাই static field (একে class variable-ও বলে)। সবচেয়ে প্রচলিত দুটি ব্যবহার — কয়টি object তৈরি হলো তা গোনা, আর প্রতিটি নতুন object-কে একটি অনন্য ক্রমিক নম্বর দেওয়া। কলেজে ভর্তির সময় অফিসের রেজিস্টার খাতা ভাবুন: খাতা একটিই, শেষ রোল কত দেওয়া হয়েছে তা সেখানে লেখা। নতুন কেউ এলে সেই সংখ্যার পরেরটা পায়, আর খাতায় সংখ্যাটা এক বাড়ে।

class Student {
    private static int nextRoll = 101;   // class-এর একটিই কপি
    private static int count = 0;
    private final int roll;              // প্রতিটি object-এর নিজের
    private final String name;

    Student(String name) {
        this.name = name;
        this.roll = nextRoll++;
        count++;
    }
    static int getCount() { return count; }
    String info() { return roll + " - " + name; }
}

class Main {
    public static void main(String[] args) {
        System.out.println("শুরুতে: " + Student.getCount());
        Student[] all = { new Student("Rahim"), new Student("Mitu"), new Student("Karim") };
        for (Student s : all) System.out.println(s.info());
        System.out.println("মোট student: " + Student.getCount());
    }
}

আউটপুট: শুরুতে: 0, তারপর 101 - Rahim, 102 - Mitu, 103 - Karim, শেষে মোট student: 3. প্রথম লাইনে একটিও object নেই, তবু Student.getCount() চলেছে — কারণ count আর getCount দুটিই class-এর। Constructor-এর ভেতরে this.roll = nextRoll++; লাইনটি আগে পুরোনো মান (101) roll-এ বসায়, তারপর ভাগের nextRoll-কে বাড়িয়ে 102 করে। পরের object সেই 102 পায়। roll আর name final — constructor-এ একবার বসানোর পর আর বদলাবে না (অংশ ৫)। আর count private রেখে শুধু পড়ার জন্য একটি static getter দেওয়া হয়েছে — পর্ব ১৭-এর encapsulation এখানেও খাটে, বাইরের কেউ count = 0 করে হিসাব নষ্ট করতে পারবে না। (main-এর class public ছাড়া লেখা, কারণ অনলাইন runner ফাইলটিকে অন্য নামে compile করে; নিজের প্রজেক্টে সাধারণত public class Main থাকে Main.java-তে।)

এবার ধাপে ধাপে দেখুন, প্রতিটি new-এর সময় ভাগের দুটি static field আর নতুন object-এর roll কীভাবে বদলায়:

1private static int nextRoll = 101;2private static int count = 0;3Student(String name) {4    this.roll = nextRoll++;5    count++;6}7Student a = new Student("Rahim");8Student b = new Student("Mitu");9Student c = new Student("Karim");10a.info(); b.info(); c.info();
ভাগের static আর প্রতিটি object-এর roll
Student.nextRoll
Student.count
a.roll
b.roll
c.roll
  1. Class প্রস্তুত। প্রথমবার Student ব্যবহারের আগে JVM static field দুটিকে তাদের শুরুর মান দেয়: nextRoll = 101, count = 0. এটা ঘটে একবারই, পুরো প্রোগ্রামে।
  2. প্রথম object. Constructor-এ nextRoll++ একটি post-increment: আগে পুরোনো মান 101 দেয়, যা বসে a.roll-এ, তারপর nextRoll হয় 102. count হয় 1.
  3. দ্বিতীয় object. একই constructor, কিন্তু nextRoll তো ভাগের — সে এখন 102. তাই b.roll = 102. a.roll 101-ই আছে, কারণ ওটা a-র নিজের ঘর।
  4. তৃতীয় object. c.roll = 103, nextRoll এখন 104 — পরের ভর্তির জন্য প্রস্তুত। যদি nextRoll static না হতো, প্রতিটি object নিজের nextRoll = 101 নিয়ে জন্মাত, আর তিনজনই পেত 101!
  5. ফল। তিনটি object, তিনটি আলাদা রোল, আর একটিই রেজিস্টার খাতা। (একটি সতর্কতা: একাধিক thread একসাথে new Student করলে nextRoll++ নিরাপদ নয় — দুজন একই রোল পেতে পারে। পর্ব ৪০-এ এর সমাধান দেখবেন।)

৩ · static method — object ছাড়াই কাজ

পর্ব ১২-এ আপনি যত method লিখেছেন, সবই ছিল static method. তখন object জানা ছিল না, তাই সেটাই একমাত্র পথ ছিল। এখন প্রশ্নটা বদলেছে: কখন একটি method static হওয়া উচিত? উত্তর — যখন কাজটি কোনো নির্দিষ্ট object-এর অবস্থার ওপর নির্ভর করে না, শুধু argument নিয়েই করা যায়।

Java-র নিজের library-তে এর সবচেয়ে পরিচিত উদাহরণ Math class: Math.max(3, 7), Math.sqrt(2), Math.round(4.6). দুটি সংখ্যার বড়টি বের করতে কোনো “Math object” লাগে না — new Math() লেখাই যায় না, কারণ Math-এর constructor private. এমন class, যার সব method static আর যার object বানানোর কোনো মানে নেই, তাকে বলে utility class. Arrays, Collections, Objects — সবাই এই দলের।

final class TaxCalculator {
    static final double VAT_RATE = 0.15;   // constant (অংশ ৫)
    private TaxCalculator() { }            // কেউ object বানাতে পারবে না

    static double vat(double amount)      { return amount * VAT_RATE; }
    static double withVat(double amount)  { return amount + vat(amount); }
}

double total = TaxCalculator.withVat(1000);   // object ছাড়াই, class-এর নাম দিয়ে

তিনটি জিনিস খেয়াল করুন: class-টি final (কেউ extend করবে না), constructor private (কেউ object বানাবে না), আর সব method static. এটাই utility class-এর প্রচলিত ছাঁচ।

কেন main static

প্রোগ্রাম শুরুর মুহূর্তে কোনো object নেই। JVM-কে কোথাও থেকে শুরু করতে হবে। যদি main একটি instance method হতো, তাহলে JVM-কে আগে new Main() করতে হতো — কিন্তু কোন constructor দিয়ে? তার argument কী? সেই constructor নিজেই exception দিলে? Java-র নকশাকারেরা এই প্রশ্নগুলো এড়াতে নিয়ম করেছিলেন: শুরুর method হবে public static void main(String[] args) — যাতে JVM শুধু class load করে, কোনো object না বানিয়েই, সরাসরি Main.main(args) ডাকতে পারে। (Java 21-এ এই নিয়ম শিথিল করার একটি preview feature এসেছিল, যা পরে Java 25-এ স্থায়ী হয় — ভাবনার প্রশ্ন ১-এ দেখুন।)

static method যা পারে না

Static method কোনো object-এর হয়ে চলে না। তাই তার কাছে this নেই — “কোন object?” প্রশ্নের কোনো উত্তর নেই। এর সরাসরি ফল: static method সরাসরি কোনো instance field পড়তে বা instance method ডাকতে পারে না। নতুনদের সবচেয়ে চেনা compile error এখান থেকেই আসে:

class Student {
    String name = "Rahim";
    static void show() {
        System.out.println(name);      // কার name?
    }
}
class Main {
    void greet() { }
    public static void main(String[] args) {
        greet();                       // কোন object-এর greet?
        System.out.println(this);      // static-এ this নেই
    }
}
Main.java:4: error: non-static variable name cannot be referenced from a static context
        System.out.println(name);
                           ^
Main.java:10: error: non-static method greet() cannot be referenced from a static context
        greet();
        ^
Main.java:11: error: non-static variable this cannot be referenced from a static context
        System.out.println(this);
                           ^
3 errors

মেসেজগুলো OpenJDK 21-এ যাচাই করা। “static context” মানে static method বা static block-এর ভেতরের জায়গা, যেখানে কোনো this নেই। সমাধান দুটি, আর কোনটা ঠিক তা নির্ভর করে অর্থের ওপর: (১) একটি object বানিয়ে তার মাধ্যমে ডাকুন — new Main().greet(); বা s.name; অথবা (২) যদি জিনিসটা সত্যিই কোনো object-এর নয়, তাহলে সেটাকে static করুন। ভুল পথ হলো error সরাতে চোখ বন্ধ করে সব কিছুতে static বসিয়ে দেওয়া — অংশ ৯-এ দেখবেন, AI ঠিক এটাই প্রায়ই করে।

উল্টো দিকে কোনো বাধা নেই: instance method যেকোনো static field পড়তে ও static method ডাকতে পারে — যেমন constructor nextRoll বদলেছে। নিয়মটা একমুখী: object-এর জগৎ থেকে class-এর জগৎ দেখা যায়, class-এর জগৎ থেকে কোনো নির্দিষ্ট object দেখা যায় না, যতক্ষণ না আপনি একটি reference হাতে দেন। আরেকটি তথ্য: static method override হয় না — subclass একই নামে static method লিখলে সেটা আগেরটিকে শুধু “লুকায়” (method hiding). পর্ব ১৯-২০-এ পার্থক্যটি পরিষ্কার হবে।

static import — পরিমিত ব্যবহার

বারবার Math. লিখতে ক্লান্ত লাগলে static import ব্যবহার করা যায়:

import static java.lang.Math.sqrt;    // শুধু একটি নাম — ভালো
import static java.lang.Math.*;       // সব নাম — সাবধানে

double d = sqrt(a * a + b * b);       // Math. লিখতে হলো না

সুবিধা আছে, ঝুঁকিও আছে। max(x, y) পড়ে পাঠক বুঝবেন না এটা Math.max, না আপনার নিজের class-এর কোনো method. Oracle-এর নিজের নির্দেশনাও বলে static import “খুব কম” ব্যবহার করতে — যেখানে একই class-এর constant বা method একটি ফাইলে বারবার আসে, শুধু সেখানে। JUnit টেস্টে import static org.junit.jupiter.api.Assertions.*; এর একটি প্রচলিত, গ্রহণযোগ্য ব্যবহার (পর্ব ৪৭)।

৪ · static block আর class কখন জেগে ওঠে

কিছু static field-এর শুরুর মান এক লাইনে দেওয়া যায় না — যেমন একটি টেবিল ভরাট করা, বা একটি ফাইল থেকে কনফিগারেশন পড়া। এর জন্য আছে static initializer block: class-এর ভেতরে static { … }. এই block চলে একবারই, যখন class প্রথমবার initialize হয়।

class District {
    static final java.util.Map<String, Integer> CODES = new java.util.TreeMap<>();
    static {
        CODES.put("Dhaka", 1);
        CODES.put("Rajshahi", 2);
        System.out.println("District প্রস্তুত");   // একবারই ছাপবে
    }
    static int codeOf(String name) { return CODES.get(name); }
}

“প্রথমবার initialize” ঠিক কখন? Class initialization ঘটে class-এর প্রথম “সক্রিয় ব্যবহারে” — প্রথম new, প্রথম static method ডাক, বা প্রথম static field পড়া/লেখা (Java Language Specification §12.4.1). শুধু ভ্যারিয়েবল ঘোষণা করলে (District d;) হয় না। আর একটি চমকপ্রদ ব্যতিক্রম আছে, পরের অংশে যার কারণ বুঝবেন। ধাপে ধাপে দেখুন:

  1. Main initializestatic { … }main-এর আগেই
  2. Config.MAX5constant — class জাগে না
  3. District.codeOfstatic { … }প্রথম ব্যবহার
  4. আবার codeOf—block আর চলে না
  1. Main সবার আগে। java Main চালালে JVM main ডাকার আগেই Main class initialize করে, তাই তার static block-ই প্রথম লাইন ছাপে — main-এর ভেতরের কিছু চলার আগেই।
  2. Config.MAX পড়া হলো, কিন্তু Config-এর static block চলল না! static final int MAX = 5; একটি compile-time constant. compiler 5 সংখ্যাটিকেই সরাসরি Main-এর bytecode-এ বসিয়ে দিয়েছে, তাই চলার সময় Config class ছোঁয়াই হয়নি। (Wandbox-এর OpenJDK 21-এ যাচাই করা; MAX-এর মান Integer.parseInt("5") দিয়ে দিলে এটা আর constant নয়, তখন block চলে।)
  3. District.codeOf("Dhaka") — প্রথম সক্রিয় ব্যবহার। static method ডাকার আগে JVM District initialize করে: static field-গুলো সাজায়, static block ওপর থেকে নিচে চালায় (“District প্রস্তুত”), তারপর method চলে আর 1 ফেরত দেয়।
  4. দ্বিতীয় ডাক — block আর চলে না। codeOf("Rajshahi") সরাসরি 2 ফেরত দেয়। একটি class পুরো প্রোগ্রামে একবারই initialize হয়। এ কারণেই static block ভারী, এককালীন প্রস্তুতির জন্য উপযুক্ত — আবার এ কারণেই সেখানে exception হলে class-টি আর কখনো ব্যবহারযোগ্য হয় না (ExceptionInInitializerError).

static nested class — এক ঝলক

Class-এর ভেতরেও class লেখা যায়। ভেতরের class-এর আগে static থাকলে সেটা static nested class — বাইরের class-এর কোনো object-এর সাথে বাঁধা নয়, শুধু নামের দিক থেকে তার ভেতরে থাকে, যেমন Map.Entry. static না থাকলে সেটা inner class, যার প্রতিটি object একটি বাইরের object-এর সাথে যুক্ত। দুটির পার্থক্য আর কখন কোনটা — পর্ব ২৭-এ।

৫ · final ভ্যারিয়েবল — একবার মান, আর নয়

এবার দ্বিতীয় keyword. যেকোনো ভ্যারিয়েবলের আগে final লিখলে compiler নিশ্চিত করে যে তাতে ঠিক একবার মান বসবে। দ্বিতীয়বার চেষ্টা করলে compile error. চারটি জায়গায় এটা দেখা যায়:

কোথায়উদাহরণমান কখন বসে
Local variablefinal int total = a + b;ঘোষণায়, বা পরে একবার (সব পথে ঠিক একবার)
Parametervoid pay(final double amount)ডাকার সময়; method-এর ভেতরে আর বদলানো যায় না
Instance fieldprivate final int roll;ঘোষণায়, অথবা প্রতিটি constructor-এ ঠিক একবার
Static fieldstatic final double VAT_RATE = 0.15;ঘোষণায়, অথবা static block-এ একবার

Instance field-এর ক্ষেত্রটি সবচেয়ে কাজের। অংশ ২-এর roll ঘোষণার সময় কোনো মান পায়নি — এমন final field-কে বলে blank final. compiler কড়া পাহারা দেয়: প্রতিটি constructor-এর প্রতিটি পথে ঠিক একবার মান বসাতেই হবে। একটি constructor ভুলে গেলে:

Main.java:4: error: variable roll might not have been initialized
    Student() { }
                ^

আর মান বসানোর পর নতুন মান দিতে গেলে (মেসেজ দুটিই OpenJDK 21-এ যাচাই করা):

error: cannot assign a value to final variable roll
error: cannot assign a value to static final variable VAT_RATE

এর মূল্য কী? roll final হওয়ায় class-এর পাঠক এক নজরে জানেন — একবার ভর্তি হলে কোনো student-এর রোল আর বদলায় না। এটা একটি invariant, যা compiler নিজে পাহারা দেয়; কোনো setter ভুল করে লিখে ফেলাও সম্ভব নয়। Local variable-এ final লেখা ঐচ্ছিক রুচির ব্যাপার — কেউ লম্বা method-এ পড়ার সুবিধার জন্য লেখেন, কেউ লেখেন না।

Constant — static final আর UPPER_SNAKE_CASE

যে মান পুরো প্রোগ্রামে একটিই আর কখনো বদলায় না, সেটা static final. Java-র রীতি অনুযায়ী এর নাম লেখা হয় সব বড় হাতের অক্ষরে, শব্দের মাঝে আন্ডারস্কোর দিয়ে — UPPER_SNAKE_CASE: VAT_RATE, MAX_STUDENTS, Math.PI, Integer.MAX_VALUE. নাম দেখেই পাঠক বোঝেন, এটা বদলায় না। একটি দোকানের বিলিং প্রোগ্রামের constant টেবিল এমন হতে পারে:

final class ShopConfig {
    private ShopConfig() { }
    static final String SHOP_NAME     = "Rahman Store";
    static final double VAT_RATE      = 0.15;
    static final int    MAX_ITEMS     = 50;     // এক রসিদে সর্বোচ্চ
    static final long   FREE_DELIVERY = 100_000; // পয়সায়, মানে ১০০০ টাকা
}

এখন প্রোগ্রামের কোথাও 0.15 বা 50-এর মতো রহস্যময় সংখ্যা (magic number) থাকবে না; থাকবে ShopConfig.VAT_RATE — নামই অর্থ বলে দেয়, আর হার বদলালে বদলাতে হয় এক জায়গায়। তবে একটি প্রশ্ন নিজেকে করুন: মানটি কি সত্যিই কখনো বদলায় না? π বদলায় না। কিন্তু VAT-এর হার সরকারের বাজেটে বদলাতে পারে, আর তখন constant বদলানো মানে পুরো প্রোগ্রাম আবার compile ও deploy করা। যে মান বাস্তবে বদলায়, বড় সিস্টেমে তা সাধারণত কনফিগারেশন ফাইল বা ডেটাবেস থেকে পড়া হয় — অনুশীলন ৪-এ এটা নিয়ে ভাববেন।

Compile-time constant আর inlining

অংশ ৪-এর stepper-এ Config.MAX পড়লেও Config class জাগেনি। কারণ: যে static final field-এর টাইপ primitive বা String, আর মান একটি constant expression (যেমন 5, "Rahman Store", 60 * 60), সেটা একটি compile-time constant. compiler এর ব্যবহার দেখলেই মানটি সরাসরি ব্যবহারকারী class-এর bytecode-এ বসিয়ে দেয় — একে বলে inlining.

এর একটি বাস্তব ফাঁদ আছে। ধরুন একটি library-তে static final int TIMEOUT = 30;, আর আপনার কোড সেটা ব্যবহার করে। Library-র লেখক মানটি 60 করলেন, শুধু library আবার compile করলেন। আপনার পুরোনো compile করা class কিন্তু এখনো 30-ই ব্যবহার করবে — কারণ 30 সংখ্যাটি আপনার bytecode-এর ভেতরে বসানো, library থেকে পড়া হয় না। তাই যে মান ভবিষ্যতে বদলাতে পারে, public API-তে তাকে compile-time constant বানানো বিপজ্জনক; সেখানে একটি static method (getTimeout()) নিরাপদ।

৬ · final reference ≠ immutable object

এই পর্বের সবচেয়ে জরুরি, আর সবচেয়ে বেশি ভুল বোঝা কথা: final ভ্যারিয়েবলকে আটকায়, object-কে নয়। পর্ব ১২-তে দেখেছেন, object ভ্যারিয়েবলে থাকে শুধু একটি reference — ঠিকানা। final StringBuilder sb মানে ঠিকানাটি বদলানো যাবে না; সেই ঠিকানার বাড়ির ভেতরের আসবাব বদলানো যাবে কি না, তা ঠিক করে object নিজে।

বাসার গেটে নামফলক — “বাড়ি ১২, রোড ৫”। নামফলকটি সিমেন্ট দিয়ে গেঁথে দিলেন (final). এখন আর অন্য বাড়ির ঠিকানা সেখানে লেখা যাবে না। কিন্তু বাড়ির ভেতরে সোফা সরানো, নতুন আলমারি আনা — সবই চলবে। নামফলক গাঁথা মানে বাড়ি বদলানো নিষেধ, বাড়ির ভেতর বদলানো নয়।

ধাপ ১final StringBuilder sb = new StringBuilder("cha");
stack                      heap
sb (final) ══════▶  [ StringBuilder: "cha" ]
══ মানে তীরটি গাঁথা — আর ঘোরানো যাবে না
ধাপ ২sb.append(" + biscuit");
stack                      heap
sb (final) ══════▶  [ StringBuilder: "cha + biscuit" ]
✔ বৈধ — একই object, ভেতরটা বদলাল
ধাপ ৩sb = new StringBuilder("coffee");
stack                      heap
sb (final) ══════▶  [ StringBuilder: "cha + biscuit" ]
            ✗ ─ ─▶  [ StringBuilder: "coffee" ]
error: cannot assign a value to final variable sb
ধাপ ৪sb.setLength(0);
stack                      heap
sb (final) ══════▶  [ StringBuilder: "" ]  (খালি)
✔ বৈধ — final থাকা অবস্থায়ও পুরো লেখা মুছে ফেলা গেল
  1. final reference তৈরি। sb-এ বসল একটি StringBuilder object-এর ঠিকানা। final বলছে: এই ঠিকানা আর কখনো বদলাবে না।
  2. append — বৈধ। আমরা sb-এ নতুন ঠিকানা বসাইনি, একই object-কে বলেছি নিজের ভেতরে লেখা যোগ করতে। StringBuilder তো বদলানোর জন্যই তৈরি (পর্ব ০৮)। final এখানে কিছুই আটকায় না।
  3. নতুন object বসানো — compile error. এবার আমরা sb-এর ঠিকানাটাই বদলাতে চাইছি। ঠিক এটাই final নিষেধ করে। মেসেজটি OpenJDK 21-এ যাচাই করা।
  4. setLength(0) — এটাও বৈধ! পুরো লেখা মুছে দেওয়া গেল, অথচ sb এখনো final. “final মানে এর মান কেউ বদলাতে পারবে না” — এই ধারণা object-এর ক্ষেত্রে ভুল। final শুধু তীরটি গাঁথে।

একই কথা collection-এর ক্ষেত্রে, আর এখানেই বাস্তব bug জন্মায়:

static final List<String> HOLIDAYS = new ArrayList<>();
HOLIDAYS.add("21 February");          // চলে — final হলেও
HOLIDAYS.clear();                     // এটাও চলে!

static final List<String> FIXED = List.of("21 February", "26 March");
FIXED.add("16 December");             // চলার সময় UnsupportedOperationException

প্রথম HOLIDAYS নাম দেখে constant মনে হয়, কিন্তু যেকোনো কোড তাতে যোগ বা মুছে ফেলতে পারে। সত্যিই না-বদলানো তালিকা চাইলে List.of(…) বা List.copyOf(…) দিন — এগুলো unmodifiable list ফেরত দেয়, যাতে add করলে exception আসে (পর্ব ৩০)। মনে রাখুন: immutable হওয়া object-এর নিজের গুণ, ভ্যারিয়েবলের final থেকে সেটা আসে না। String, Integer, LocalDate immutable — final ছাড়াও; StringBuilder, ArrayList mutable — final দিয়েও।

Effectively final — final না লিখেও final

Java 8 থেকে একটি নতুন ধারণা যোগ হয়েছে: যে local variable-এ ঘোষণার পর আর কখনো নতুন মান বসানো হয় না, সেটা final না লিখলেও effectively final. এর গুরুত্ব আসে lambda-তে (পর্ব ৩৪): lambda বা anonymous class বাইরের যে local variable ব্যবহার করে, সেটা final বা effectively final হতেই হবে।

int count = 0;
count++;                                  // count আর effectively final নয়
Supplier<Integer> s = () -> count;        // ✗
Main.java:10: error: local variables referenced from a lambda expression must be final or effectively final
        Supplier<Integer> s = () -> count;
                                    ^

কেন এই নিয়ম? Lambda পরে, এমনকি অন্য thread-এ চলতে পারে, যখন method-এর local variable-এর frame আর নেই। তাই Java মানটির একটি কপি lambda-র সাথে রেখে দেয়। ভ্যারিয়েবল বদলাতে থাকলে “কোন মানের কপি?” প্রশ্নে বিভ্রান্তি হতো — Java সেই সম্ভাবনাটাই বন্ধ করেছে। বিস্তারিত পর্ব ৩৪-এ।

final আটকায় ভ্যারিয়েবলকে, object-কে নয়।
তীর গাঁথা যায়; বাড়ির ভেতরটা বাঁচাতে লাগে immutable object.

৭ · final method, final class আর immutable class-এর রেসিপি

Method-এর আগে final লিখলে subclass সেই method নতুন করে লিখতে (override করতে) পারে না। Class-এর আগে লিখলে কেউ সেই class থেকে subclass বানাতেই পারে না — এটা final class. পরের পর্বে inheritance শেখার পর এর পূর্ণ অর্থ বুঝবেন; আপাতত একটি পরিচিত উদাহরণ — String নিজেই final:

error: cannot inherit from final String
class MyText extends String { }
                     ^

কেন একটি class-কে বন্ধ করে দেওয়া? কারণ subclass চাইলে method override করে আচরণ বদলে দিতে পারত — একটি “String” যার মান হঠাৎ বদলায়, তা পুরো Java জগতের নিরাপত্তা ভেঙে দিত (ভাবনার প্রশ্ন ৪)। final method একই কাজ করে ছোট পরিসরে: class খোলা থাকে, কিন্তু নির্দিষ্ট কিছু method-এর আচরণ সবার জন্য স্থির। বিস্তারিত পর্ব ১৯-তে।

Immutable class বানানোর রেসিপি

টাকার হিসাবের জন্য একটি Taka class ভাবুন। ব্যাংক ব্যালান্স, বিল, VAT — এই মানগুলো যদি কোথাও গোপনে বদলে যায়, বিপদ। সমাধান: একবার তৈরি হলে যে object আর বদলায় না। Joshua Bloch-এর Effective Java বইয়ের নিয়মগুলোর ভিত্তিতে একটি immutable class-এর রেসিপি পাঁচ ধাপে সাজানো যায়:

  1. সব field private final — বাইরে থেকে ছোঁয়া যাবে না, ভেতর থেকেও দ্বিতীয়বার বসানো যাবে না।
  2. কোনো setter নেই — অবস্থা বদলানোর কোনো method নেই। “বদলানো” মানে নতুন object ফেরত দেওয়া, যেমন plus.
  3. Class final — যাতে কোনো subclass mutable আচরণ ঢোকাতে না পারে।
  4. Defensive copy — field-এ কোনো mutable object (List, array, Date) থাকলে constructor-এ তার কপি রাখুন, আর getter-এ ভেতরেরটা না দিয়ে কপি বা unmodifiable view দিন।
  5. constructor-এ this বাইরে ফাঁস করবেন না — পুরোপুরি তৈরি হওয়ার আগে অন্য কেউ object দেখে ফেললে নিশ্চয়তা ভাঙে।
final class TaxCalculator {
    static final double VAT_RATE = 0.15;
    private TaxCalculator() { }          // object বানানো নিষেধ
    static long vatOf(long paisa) { return Math.round(paisa * VAT_RATE); }
}

final class Taka {
    private final long paisa;
    Taka(long paisa) { this.paisa = paisa; }
    Taka plus(Taka other) { return new Taka(paisa + other.paisa); }
    Taka withVat() { return new Taka(paisa + TaxCalculator.vatOf(paisa)); }
    @Override public String toString() { return String.format("%d.%02d tk", paisa / 100, paisa % 100); }
}

class Main {
    public static void main(String[] args) {
        Taka tea = new Taka(2500), cake = new Taka(3500);
        Taka bill = tea.plus(cake);
        System.out.println("bill = " + bill + ", VAT soho = " + bill.withVat());
        System.out.println("tea ekhono = " + tea);
        final StringBuilder log = new StringBuilder("log");
        log.append(" | ").append(bill);   // final, তবু ভেতরটা বদলায়
        System.out.println(log);
    }
}

আউটপুট: bill = 60.00 tk, VAT soho = 69.00 tk, tea ekhono = 25.00 tk, log | 60.00 tk. Taka টাকা রাখে পয়সায়, long হিসেবে — double-এর দশমিক ভুল (পর্ব ০৬) এড়াতে। tea.plus(cake) tea-কে বদলায়নি, একটি নতুন Taka ফেরত দিয়েছে; তাই পরের লাইনে tea এখনো 25.00. withVat-ও নতুন object বানায়, আর VAT-এর হিসাব নেয় utility class TaxCalculator-এর static method আর VAT_RATE constant থেকে। শেষ তিন লাইন অংশ ৬-এর কথাটি আবার দেখায়: log final, তবু append তার ভেতরটা বদলেছে। @Override নিয়ে চিন্তা করবেন না — এটা Object-এর toString নতুন করে লেখা বোঝায়, পর্ব ১৯ আর ২৩-এ বিস্তারিত।

Immutable object-এর লাভ বড়। (১) Thread safety: যে object বদলায়ই না, তাকে একশো thread একসাথে পড়লেও কোনো গোলমাল নেই — কোনো lock লাগে না (thread-safe). (২) নির্ভয়ে ভাগ করা: একটি Taka দশটি object-এর সাথে ভাগ করলেও কেউ অন্যের হিসাব নষ্ট করতে পারবে না; পর্ব ১২-এর aliasing-এর ভয় থাকে না। (৩) সহজ যুক্তি: একবার ঠিকঠাক তৈরি হলে চিরকাল ঠিক। খরচ একটাই — প্রতিটি “বদলে” নতুন object, যা সাধারণত নগণ্য। Java 16 থেকে এমন ডেটা-class অনেক সংক্ষেপে লেখা যায় record দিয়ে — পর্ব ২৫-এ দেখবেন, record Taka(long paisa) { } লিখলেই প্রথম দুটি ধাপ আর class-এর final হওয়া নিজে থেকে হয়ে যায়।

Defensive copy ভুলে গেলে: final class Batch { private final List<String> names; Batch(List<String> n) { names = n; } … } — দেখতে immutable. কিন্তু যিনি তালিকাটি পাঠিয়েছেন, তাঁর হাতে এখনো একই ArrayList-এর reference আছে। তিনি add করলে আপনার “immutable” Batch-ও বদলে যায়। সমাধান: constructor-এ names = List.copyOf(n); — একটি স্বাধীন, unmodifiable কপি।

৮ · static-এর ফাঁদ — global state, টেস্ট আর memory leak

static field সুবিধাজনক: যেকোনো জায়গা থেকে ClassName.field লিখলেই পাওয়া যায়, কোনো object পাস করতে হয় না। ঠিক এই সুবিধাই তাকে বিপজ্জনক করে। static final constant নিরাপদ — সে বদলায় না। সমস্যা হলো বদলানো যায় এমন static field, যা কার্যত একটি global state.

কে বদলাল?

পুরো প্রোগ্রামের যেকোনো লাইন একটি public static field বদলাতে পারে। bug হলে সন্দেহভাজন হাজার লাইন। মেট্রো রেলের একটিমাত্র টিকিট মেশিনের ব্যালান্স সবাই হাত দিয়ে বদলাতে পারলে যা হতো।

লুকানো নির্ভরতা

টেস্ট কঠিন

একটি টেস্ট static কাউন্টার 3 করে রেখে গেল, পরের টেস্ট ধরে নিল 0. টেস্টের ক্রম বদলালেই ফল বদলায়। static method-কে নকল (mock) দিয়ে বদলানোও কঠিন (পর্ব ৪৭)।

testability

Memory leak

static field একটি GC root — class যতক্ষণ আছে, সে যা ধরে রাখে তা garbage collector সরাতে পারে না। static List<Order> CACHE-এ শুধু যোগ হয়, মোছে না — কয়েক দিনে OutOfMemoryError.

মেমরি ফুরানো

Thread-এর সংঘর্ষ

অংশ ২-এর nextRoll++ দুটি thread একসাথে চালালে দুজন একই রোল পেতে পারে। ভাগের, বদলানো জিনিস মানেই synchronization-এর প্রশ্ন (পর্ব ৪০)।

concurrency

Java-তে “মেমরি তো GC সামলায়” — তবু leak হয় কীভাবে? Leak মানে এখানে মেমরি হারানো নয়, বরং অপ্রয়োজনীয় object-কে ভুল করে ধরে রাখা। একটি static map-এ প্রতিটি request-এর তথ্য রাখলেন, মুছলেন না — GC দেখে কেউ এখনো সেগুলোর দিকে তাকিয়ে আছে, তাই সরায় না। একে বলে memory leak, আর Java সার্ভারে এর সবচেয়ে প্রচলিত উৎস এই ধরনের static collection.

Singleton — একটিমাত্র object-এর ধারণা

কখনো কখনো একটি class-এর পুরো প্রোগ্রামে ঠিক একটি object থাকা দরকার — যেমন একটি কনফিগারেশন বা একটি লগার। এই নকশার নাম Singleton, আর এটা static দিয়েই তৈরি হয়:

final class AppConfig {
    private static final AppConfig INSTANCE = new AppConfig();
    private AppConfig() { }                       // বাইরে থেকে new নিষেধ
    static AppConfig getInstance() { return INSTANCE; }
}

Singleton খুব জনপ্রিয়, আবার খুব সমালোচিতও — কারণ ভেতরে ভেতরে এটা একটি global state, আর ওপরের চারটি সমস্যার অনেকগুলো নিয়ে আসে। ভাবনার প্রশ্ন ৫-এ এই বিতর্ক আর enum দিয়ে singleton বানানোর কৌশল দেখবেন; design pattern হিসেবে পূর্ণ আলোচনা পর্ব ৪৮-এ। আপাতত নিয়ম: static রাখুন constant আর অবস্থাহীন (stateless) utility method-এর জন্য; বদলানো অবস্থা রাখুন object-এর ভেতরে।

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

C++-এর static member আর Java-র static প্রায় একই; কিন্তু C-তে function-এর ভেতরের static local variable (যা ডাকের মাঝে মান মনে রাখে) Java-তে নেই — Java-তে local variable static হতে পারে না। C++-এর const আর Java-র final আলাদা: const object-এর ভেতরটাও রক্ষা করতে পারে, final শুধু ভ্যারিয়েবলকে। Python-এ class-এর ভেতরে সরাসরি লেখা ভ্যারিয়েবলই class attribute (Java-র static field-এর মতো), @staticmethod আছে, কিন্তু ভাষার স্তরে final নেই — typing.Final শুধু type checker-এর জন্য, আর UPPER_CASE নাম একটি রীতি। তুলনার জন্য দেখুন C++-এর class ও object (static member অংশ) আর Python-এর class ও object (class attribute অংশ) পর্ব।

পরীক্ষা ও ইন্টারভিউতে
  • static method থেকে instance variable সরাসরি পড়া যায় কি? — না; static context-এ this নেই, তাই javac বলে “non-static variable cannot be referenced from a static context”; একটি object-এর মাধ্যমে পড়তে হয়।
  • main কেন static? — JVM কোনো object না বানিয়েই class-এর নাম দিয়ে শুরুর method ডাকতে পারে।
  • final, finally আর finalize-এর পার্থক্য? — final: না-বদলানো ভ্যারিয়েবল/method/class; finally: try-এর পরে সবসময় চলা block (পর্ব ২৯); finalize: Object-এর পুরোনো method, Java 9 থেকে deprecated.
  • final reference আর immutable object কি একই? — না; final শুধু reference-কে নতুন object দেখাতে বাধা দেয়, object-এর ভেতরটা বদলাতে পারে, যদি না class নিজে immutable হয়।
  • static block কখন চলে? — class প্রথমবার initialize হওয়ার সময়, একবারই, field ঘোষণার সাথে ওপর থেকে নিচের ক্রমে।

৯ · AI-এর যুগে — static দিয়ে error চাপা দেওয়া ধরুন

“non-static variable cannot be referenced from a static context” — এই error AI-কে দেখালে সবচেয়ে সহজ যে সমাধান সে দেয়: সংশ্লিষ্ট field আর method-এর আগে static বসিয়ে দেওয়া। Error চলে যায়, প্রোগ্রাম চলে। কিন্তু এখন প্রতিটি Student-এর আলাদা নাম নেই — সবার নাম একটিই, শেষে যেটা বসানো হয়েছে। Compile error সরে গিয়ে এসেছে একটি নীরব logic bug, যা অনেক বেশি বিপজ্জনক।

AI যা করেকেন সমস্যাআপনি যা করবেন
static context error “সারাতে” field ও method static করেপ্রতিটি object-এর নিজস্ব তথ্য একটি ভাগের ঘরে চলে যায়; শেষের মান সবাইকে ঢেকে দেয়জিজ্ঞেস করুন: “এই জিনিসটি কি সত্যিই কোনো object-এর নয়?” না হলে object বানিয়ে ডাকুন
public static mutable field দিয়ে ডেটা আদান-প্রদানglobal state — কে বদলাল বোঝা যায় না, টেস্ট ও thread-এ সমস্যাডেটা constructor বা parameter দিয়ে পাঠাতে বলুন
বলে “final List মানে তালিকা বদলানো যাবে না”ভুল — add, clear সবই চলেList.of / List.copyOf চান, আর অংশ ৬-এর stepper দিয়ে যাচাই করুন
“immutable” class-এ mutable List সরাসরি রাখেবাইরের reference দিয়ে ভেতরটা বদলানো যায়constructor আর getter-এ defensive copy চান
static cache-এ শুধু যোগ করে, কখনো মোছে নাদীর্ঘ সময় চললে memory leakআকারের সীমা বা মোছার নিয়ম কোথায়, জিজ্ঞেস করুন

AI-কে class লিখতে বা রিভিউ করতে বলার সময় নিচের কাঠামো ব্যবহার করুন। শর্তগুলো না দিলে AI নিজের সুবিধামতো static ছড়িয়ে দেবে:

আমি Java 21 শিখছি, class, constructor, encapsulation জানি। নিচের কাজের জন্য class লিখে দাও। কাজ: [যেমন — ভর্তির সময় প্রতিটি Student-কে ক্রমিক রোল দেওয়া, অথবা VAT-সহ বিল হিসাব] শর্ত: ১. প্রতিটি static field আর static method-এর পাশে এক লাইনে কারণ লেখো: কেন এটা কোনো object-এর নয়। ২. বদলানো যায় এমন public static field একটিও রাখবে না; constant হলে static final, নাম UPPER_SNAKE_CASE. ৩. যে field constructor-এর পর বদলায় না, সেটা private final করো। ৪. কোনো class "immutable" বললে দেখাও: final class, setter নেই, mutable field-এর defensive copy আছে। ৫. final reference আর immutable object-এর পার্থক্য আমার কোডে কোথায় প্রাসঙ্গিক, এক লাইনে বলো। ৬. compile error সরাতে কোনো কিছু static করবে না — দরকার হলে object বানিয়ে ডাকবে।

AI-এর লেখা class হাতে পেলে — যাচাইয়ের চেকলিস্ট

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

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

প্র ০১public static void main(String[] args) — নতুনদের প্রথম দিনেই এতগুলো keyword কেন? Java 21-এর “instance main method” কী বদলাতে চেয়েছে?

প্রতিটি শব্দের কারণ আছে। public — JVM বাইরে থেকে ডাকবে, তাই দেখা যেতে হবে। void — প্রোগ্রাম শেষে কোনো মান ফেরত যায় না; exit code দিতে হলে System.exit. String[] args — কমান্ড লাইনের argument. আর static — এই পর্বের মূল বিষয়: শুরুর মুহূর্তে কোনো object নেই, তাই JVM-কে এমন method ডাকতে হবে যার জন্য object লাগে না। Instance method হলে JVM-কে আগে new Main() করতে হতো, আর তখন প্রশ্ন উঠত — constructor-এ argument থাকলে? constructor exception দিলে? একাধিক constructor থাকলে কোনটি? static method এই সব প্রশ্ন এক কথায় এড়িয়ে যায়: “class load করো, method ডাকো।” C-র main থেকে আসা প্রোগ্রামারদের কাছেও এটা পরিচিত মডেল ছিল।

কিন্তু শেখানোর খরচ। প্রথম দিনে “Hello World” লিখতে গিয়ে শিক্ষার্থীকে class, public, static, void, array — পাঁচটি ধারণা “পরে বুঝবে” বলে মুখস্থ করতে হয়। Python-এ একই কাজ এক লাইনে। বহু বছর ধরে এটা Java-র বিরুদ্ধে একটি স্থায়ী অভিযোগ ছিল।

JEP 445. Java 21-এ একটি JEP preview হিসেবে আসে — “Unnamed Classes and Instance Main Methods”. এর পেছনের ভাবনাটি Brian Goetz ২০২২ সালের একটি লেখায় তুলে ধরেছিলেন (নিচে দেখুন)। এতে main static না হলেও চলে, public না হলেও চলে, String[] args বাদ দেওয়া যায়, এমনকি চারপাশের class ঘোষণাও বাদ দিয়ে শুধু void main() { System.out.println("Hello"); } লেখা যায়। Instance main হলে JVM নিজেই class-এর argument-ছাড়া constructor দিয়ে একটি object বানিয়ে তারপর main ডাকে — অর্থাৎ ওপরের “কোন constructor?” প্রশ্নের উত্তর নির্দিষ্ট করে দেওয়া হলো। Preview হওয়ায় Java 21-এ এটা চালাতে --enable-preview লাগে। পরের কয়েকটি সংস্করণে আরও preview-তে নকশা ঘষামাজা হয় (যেমন “unnamed class”-এর নাম বদলে “implicitly declared class” হয়), আর Java 25-এ JEP 512-এর মাধ্যমে বৈশিষ্ট্যটি স্থায়ী হয়। খুঁটিনাটি সংস্করণভেদে কিছুটা আলাদা, তাই নিজের JDK-র নথি মিলিয়ে নিন।

গুরুত্বপূর্ণ কথা: এতে ভাষার মূল কাঠামো বদলায়নি, শুধু শুরুর দরজা সহজ হয়েছে। বড় প্রোগ্রাম এখনো class, package আর public static void main দিয়েই লেখা হয়, আর পুরোনো কোড একইভাবে চলে। Goetz নিজে একে বলেছেন “paving the on-ramp” — নতুনদের জন্য ঢালু রাস্তা, যা দিয়ে ধীরে ধীরে পূর্ণ Java-তে ওঠা যায়।

মূল উপলব্ধি: main static কারণ শুরুতে কোনো object নেই — এটা একটি প্রকৌশল সমাধান, রীতি নয়। Java 21-এর preview (Java 25-এ স্থায়ী) দেখায়, একই সমস্যার অন্য সমাধানও সম্ভব: JVM নিজে নির্দিষ্ট নিয়মে object বানিয়ে নিলে static-এর দরকার ফুরায়।

প্র ০২Steve Yegge-এর বিখ্যাত লেখা “Execution in the Kingdom of Nouns” Java নিয়ে কী অভিযোগ করেছিল? static method কি object-oriented নকশা থেকে “পালানোর দরজা”?

প্রেক্ষাপট। ২০০৬ সালে প্রোগ্রামার ও ব্লগার Steve Yegge “Execution in the Kingdom of Nouns” নামে একটি ব্যঙ্গাত্মক লেখা লেখেন, যা প্রোগ্রামিং জগতে আজও বহুল উদ্ধৃত। তাঁর গল্পে Java একটি রাজ্য, যেখানে বিশেষ্য (noun — class, object) রাজা, আর ক্রিয়া (verb — কাজ, function) দ্বিতীয় শ্রেণির নাগরিক: কোনো ক্রিয়া একা ঘুরতে পারে না, সবসময় কোনো বিশেষ্যের সাথে বাঁধা থাকতে হয়। ফলে “ময়লা ফেলো” লিখতে হয় GarbageDisposalStrategy, TrashTakerOuter জাতীয় class বানিয়ে। এই কারণেই Java কোডে …Manager, …Helper, …Factory নামের অসংখ্য class দেখা যায়।

static method-এর জায়গা। Java-তে স্বাধীন function নেই — প্রতিটি method কোনো class-এ থাকতে হয়। কিন্তু অনেক কাজ আসলেই কোনো object-এর নয়: দুটি সংখ্যার বড়টি, একটি তারিখ পার্স করা, VAT হিসাব। Java-র উত্তর হলো static method আর utility class — class-কে শুধু একটি নামের ঝুলি (namespace) হিসেবে ব্যবহার করা। Math.max আসলে C-র একটি সাধারণ function, শুধু একটি class-এর নামের পেছনে রাখা। এই অর্থে static হলো “procedural escape hatch” — object-oriented কাঠামোর ভেতরে থেকে procedural কোড লেখার দরজা।

দরজাটি ভালো না খারাপ? দুই দিকই আছে। Pure, অবস্থাহীন static method (পর্ব ১২-এর pure method) চমৎকার: পরীক্ষা করা সহজ, পার্শ্বপ্রতিক্রিয়া নেই। সমস্যা হয় যখন দরজাটি দিয়ে পুরো প্রোগ্রাম বেরিয়ে যায় — সব ডেটা static field-এ, সব যুক্তি static method-এ। তখন সেটা class-এর পোশাক পরা একটি C প্রোগ্রাম, global variable সহ, আর object-oriented নকশার কোনো সুবিধাই থাকে না।

Java নিজে কীভাবে বদলেছে। Yegge-এর সমালোচনার কয়েক বছর পর Java 8 (২০১৪) lambda আর method reference আনে — এখন একটি “ক্রিয়া” মান হিসেবে পাঠানো যায় (Math::max), যা অনেকাংশে স্বাধীন function-এর কাজ করে। Interface-এ static method রাখার অনুমতিও Java 8-এ এসেছে। Kotlin-এর মতো JVM ভাষা top-level function সরাসরি দেয়, যদিও bytecode-এ সেগুলো শেষ পর্যন্ত একটি class-এর static method-ই হয়।

মূল উপলব্ধি: সব কাজ object-এর নয় — যে কাজ অবস্থাহীন, তার জন্য static method সৎ ও সঠিক নকশা। কিন্তু যখন static হয়ে ওঠে ডেটা লুকানোর আর object এড়ানোর অভ্যাস, তখন সেটা দরজা নয়, দেয়ালের ফাটল।

প্র ০৩Effective Java-র Item 17 বলে “Minimize mutability”. সব কিছু immutable করলে লাভ কী, খরচ কী — আর Java নিজে কোথায় ভুল করেছিল?

নিয়মটি। Joshua Bloch — যিনি Sun-এ Java Collections Framework-এর নকশা করেছিলেন — তাঁর Effective Java বইয়ের তৃতীয় সংস্করণে (২০১৭-র শেষে প্রকাশিত) Item 17-এ লিখেছেন, class-কে immutable রাখা উচিত যদি না mutable হওয়ার খুব ভালো কারণ থাকে; আর mutable হলেও তার পরিবর্তনযোগ্য অংশ যতটা সম্ভব কম রাখা উচিত। এই পর্বের পাঁচ-ধাপের রেসিপি মূলত সেখানকার নিয়মগুলো থেকেই সাজানো।

লাভ। Bloch-এর যুক্তি: immutable object সহজ — সে ঠিক একটি অবস্থায় থাকে, যে অবস্থায় তৈরি হয়েছিল। স্বাভাবিকভাবেই thread-safe, synchronization লাগে না। নির্ভয়ে ভাগ করা যায়, তাই কপি বানানোর দরকার নেই — BigInteger.ZERO-এর মতো একটি object সারা প্রোগ্রাম ব্যবহার করতে পারে। HashMap-এর key হিসেবে নিরাপদ, কারণ key-এর মান বদলে গিয়ে map-এর হিসাব গোলমাল হওয়ার সুযোগ নেই (পর্ব ৩১)। আর ব্যর্থতা-নিরাপদ: কোনো কাজ মাঝপথে exception দিলেও পুরোনো object অক্ষত থাকে।

খরচ। প্রতিটি আলাদা মানের জন্য আলাদা object. একটি লুপে String দিয়ে দশ হাজারবার += করলে দশ হাজার অস্থায়ী object — এ কারণেই StringBuilder নামে একটি mutable “সঙ্গী” class আছে (পর্ব ০৮)। Bloch একে বলেন companion class: immutable class সাধারণ ব্যবহারের জন্য, mutable সঙ্গী ভারী বহু-ধাপের হিসাবের জন্য। আধুনিক JVM-এর garbage collector স্বল্পায়ু ছোট object খুব দ্রুত সরায়, তাই বেশিরভাগ ক্ষেত্রে খরচ চোখেই পড়ে না।

Java-র নিজের শিক্ষা। Java 1.0-র java.util.Date mutable ছিল — setYear, setMonth সব আছে। কেউ একটি Date field-এর getter থেকে object নিয়ে setTime করলে মূল object-এর তারিখই বদলে যেত; এমন bug বহু বছর ধরে বহু সিস্টেমে দেখা গেছে, আর defensive copy-র উপদেশ অনেকটা এখান থেকেই। Java 8-এ java.time (LocalDate, Instant …) পুরোপুরি immutable নকশায় এসে এই ভুল শুধরে নেয় (পর্ব ৩৮)। Java 16-এর record (পর্ব ২৫) immutable ডেটা-class লেখাকে প্রায় এক লাইনের কাজ বানিয়েছে — ভাষা নিজেই এখন এই নিয়মের দিকে ঠেলে দেয়।

মূল উপলব্ধি: পরিবর্তনযোগ্যতা একটি খরচ, যা প্রতিটি পাঠক, প্রতিটি thread আর প্রতিটি bug-খোঁজায় দিতে হয়। ডিফল্ট হিসেবে immutable বেছে নিন; mutable করুন শুধু মেপে-জোখে, যেখানে সত্যিই দরকার।

প্র ০৪String কেন final আর immutable? কেউ যদি String-এর subclass বানিয়ে মান বদলাতে পারত, কী ভাঙত?

String সম্ভবত Java-র সবচেয়ে বেশি ব্যবহৃত class — ফাইলের নাম, URL, পাসওয়ার্ড, class-এর নাম, map-এর key, সবই String. তাই তার নকশার একটি ছোট সিদ্ধান্ত পুরো প্ল্যাটফর্মকে প্রভাবিত করে। String immutable (ভেতরের অক্ষর বদলানোর কোনো public method নেই) এবং final (কেউ subclass বানাতে পারে না)। দুটি একসাথে না হলে প্রথমটির কোনো নিশ্চয়তা থাকত না।

১. নিরাপত্তা। ধরুন একটি method আগে যাচাই করে যে ফাইলের নাম "/home/user/report.txt" অনুমোদিত, তারপর ফাইলটি খোলে। যদি String বদলানো যেত, যাচাই আর খোলার মাঝখানে অন্য কোনো thread নামটি "/etc/passwd" করে দিতে পারত — যাচাই হলো এক জিনিসের, খোলা হলো আরেকটির। এই ধরনের আক্রমণকে বলে time-of-check to time-of-use (TOCTOU). Java-র শুরুর দিনগুলোতে ব্রাউজারে অবিশ্বস্ত applet চলত, তাই class loader, নেটওয়ার্ক সংযোগ আর ফাইল-অনুমতির সব সিদ্ধান্ত String-এর ওপর নির্ভর করত; String বদলানো গেলে সেই নিরাপত্তা-ব্যবস্থাই অর্থহীন হতো। আর final না হলে কেউ EvilString extends String বানিয়ে, toString বা equals override করে, দেখতে-String কিন্তু আচরণে-অন্য একটি object পাঠাতে পারত।

২. String pool. পর্ব ০৮-এ দেখেছেন, একই literal-এর জন্য JVM একটিই object রাখে (string pool). একশো জায়গায় "Dhaka" লেখা থাকলে একশোটি reference একটি object-এ। এটা নিরাপদ শুধু এ কারণে যে কেউ সেই object বদলাতে পারে না; নইলে এক জায়গার বদল বাকি নিরানব্বই জায়গায় ছড়িয়ে পড়ত।

৩. hashCode cache আর HashMap. String তার hashCode প্রথমবার হিসাব করে নিজের ভেতরে মনে রাখে, কারণ মান তো আর বদলাবে না। HashMap-এ String সবচেয়ে প্রচলিত key — key-এর মান বদলে গেলে map তাকে আর খুঁজেই পেত না।

৪. Thread safety. String একাধিক thread-এ কোনো lock ছাড়াই ভাগ করা যায়।

খরচ ও পরিপূরক। বারবার জোড়া লাগাতে নতুন object লাগে — তাই StringBuilder. আর পাসওয়ার্ডের ক্ষেত্রে একটি উল্টো সমস্যা আছে: immutable String মেমরি থেকে মুছে ফেলা যায় না, GC না সরানো পর্যন্ত থাকে। এজন্য JPasswordField.getPassword()-এর মতো API char[] ফেরত দেয়, যা কাজ শেষে শূন্য দিয়ে মুছে ফেলা যায়।

মূল উপলব্ধি: final আর immutable একসাথে একটি চুক্তি — “এই মান কেউ, কোনোভাবে বদলাতে পারবে না।” পুরো প্ল্যাটফর্মের নিরাপত্তা, মেমরি-সাশ্রয় আর গতি সেই চুক্তির ওপর দাঁড়িয়ে।

প্র ০৫Singleton কি design pattern না anti-pattern? Joshua Bloch কেন বলেন enum দিয়ে singleton বানানো প্রায়ই সবচেয়ে ভালো উপায়?

জন্ম। ১৯৯৪ সালে Erich Gamma, Richard Helm, Ralph Johnson আর John Vlissides — যাঁরা “Gang of Four” নামে পরিচিত — Design Patterns বইয়ে ২৩টি pattern বর্ণনা করেন; Singleton তার একটি: একটি class-এর একটিমাত্র object নিশ্চিত করা, আর সেটা পাওয়ার একটি বিশ্বব্যাপী পথ দেওয়া। কনফিগারেশন, লগার, কানেকশন পুল — উদাহরণ সহজেই মেলে, তাই এটা দ্রুত সবচেয়ে জনপ্রিয় (আর সবচেয়ে অপব্যবহৃত) pattern হয়ে ওঠে।

সমালোচনা। সময়ের সাথে অনেকে Singleton-কে anti-pattern বলতে শুরু করেন। কারণ এই পর্বের অংশ ৮-এর সব সমস্যা সে নিয়ে আসে: এটা আসলে সুন্দর নামের একটি global variable. যে class ভেতরে AppConfig.getInstance() ডাকে, তার নির্ভরতা constructor বা parameter-এ দেখা যায় না — লুকানো থাকে। টেস্টে নকল কনফিগারেশন বসানো কঠিন, আর এক টেস্টের রেখে যাওয়া অবস্থা পরের টেস্টে চুইয়ে পড়ে। “একটিমাত্র” আজ সত্য হলেও কাল দুটি ডেটাবেস লাগলে পুরো নকশা ভাঙে। আধুনিক Java-য় তাই সাধারণ পরামর্শ: object একটিই বানান, কিন্তু সেটা constructor দিয়ে যার দরকার তাকে পাঠান (dependency injection) — Spring-এর মতো framework ঠিক এটাই করে, ডিফল্টভাবে প্রতিটি bean-এর একটি object বানিয়ে (পর্ব ৫০)।

বাস্তবায়নের ফাঁদ। হাতে লেখা singleton ভাঙার কয়েকটি পথ আছে। Lazy (প্রথম ডাকে বানানো) singleton-এ দুটি thread একসাথে ঢুকলে দুটি object তৈরি হতে পারে; এর সমাধান নিয়ে “double-checked locking” কৌশল বহু বছর ভুলভাবে লেখা হয়েছে, Java 5-এর memory model ও volatile আসার আগে যা নির্ভরযোগ্য ছিল না। Reflection দিয়ে private constructor ডাকা যায়। আর class টি Serializable হলে deserialize করলে নতুন object জন্মায়, যদি না বিশেষ readResolve method লেখা হয়।

Bloch-এর সমাধান। Effective Java-র Item 3-এ Bloch দেখান: enum AppConfig { INSTANCE; … } — একটি উপাদানের enum. JVM নিজে নিশ্চয়তা দেয় যে প্রতিটি enum constant ঠিক একবার তৈরি হবে, thread-safe ভাবে (class initialization একবারই হয় — অংশ ৪); reflection দিয়ে enum-এর নতুন object বানানো নিষেধ; আর serialization-এ enum বিশেষভাবে সামলানো হয়, নতুন object জন্মায় না। তাঁর ভাষায়, একটি উপাদানের enum প্রায়ই singleton বানানোর সবচেয়ে ভালো উপায় — একমাত্র সীমাবদ্ধতা, enum অন্য class extend করতে পারে না। enum নিয়ে বিস্তারিত পর্ব ২৪-এ।

মূল উপলব্ধি: “একটিমাত্র object” আর “সবাই যেখান থেকে খুশি ধরতে পারে” — দুটি আলাদা সিদ্ধান্ত। প্রথমটি প্রায়ই ঠিক; দ্বিতীয়টি global state. Singleton লাগলে enum দিয়ে নিরাপদে বানান, আর সম্ভব হলে সেটা হাতে হাতে পাঠান।

অনুশীলন

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

  1. static মুছে দেখুন: অংশ ২-এর প্রোগ্রামে private static int nextRoll = 101; থেকে শুধু static শব্দটি মুছে দিন। না চালিয়ে বলুন তিনজনের রোল কী হবে, আর কোনো compile error আসবে কি না। তারপর চালিয়ে মিলিয়ে নিন।

    কোনো compile error নেই — constructor একটি instance context, সেখানে instance field ব্যবহার বৈধ। কিন্তু তিনজনই পাবে 101. কারণ এখন nextRoll প্রতিটি object-এর নিজের ঘর: প্রতিটি নতুন object 101 নিয়ে জন্মায়, নিজের কপি 102 করে, আর সেই 102 আর কেউ দেখে না। count এখনো static, তাই “মোট student: 3” ঠিকই আসবে। এটাই এই পর্বের মূল শিক্ষা হাতে-কলমে: static না হলে “ভাগের রেজিস্টার খাতা” প্রতিটি ছাত্রের নিজের খাতায় পরিণত হয়, আর ক্রমিক নম্বর অর্থহীন হয়ে যায়। আর getCount থেকে count-এর static মুছলে উল্টো ঘটনা — compile error “non-static variable count cannot be referenced from a static context”.

  2. Leak খুঁজুন: নিচের class একটি অনলাইন দোকানের সার্ভারে চলে, প্রতিটি অর্ডারে record(o) ডাকা হয়। কয়েক সপ্তাহ পর সার্ভার OutOfMemoryError দিয়ে বন্ধ হয়ে যায়। কারণ কী, আর অন্তত দুটি সমাধান বলুন।
    class OrderLog { static final List<Order> ALL = new ArrayList<>(); static void record(Order o) { ALL.add(o); } }

    ALL একটি static field, তাই GC root — class থাকা পর্যন্ত তালিকা আর তার ভেতরের প্রতিটি Order বেঁচে থাকে। তালিকায় শুধু যোগ হয়, কখনো মোছে না, তাই প্রতিটি অর্ডার চিরকাল মেমরিতে। final এখানে কোনো সুরক্ষা দেয় না — শুধু তালিকার reference স্থির, ভেতরে যোগ চলতেই থাকে (অংশ ৬)। সমাধান: (১) অর্ডার মেমরিতে না রেখে ডেটাবেস বা ফাইলে লিখুন (পর্ব ৩৭, ৪৯); (২) মেমরিতে রাখতেই হলে আকারের সীমা দিন — যেমন শুধু শেষ ১০০০টি রাখা, পুরোনোটি মুছে (পর্ব ৩২-এর ArrayDeque কাজে লাগে); (৩) static না রেখে একটি সাধারণ object-এ রাখুন, যার জীবনকাল নিয়ন্ত্রণ করা যায়। এছাড়া একাধিক thread একসাথে ArrayList-এ add করলে ডেটা নষ্টও হতে পারে — দ্বিতীয় একটি bug.

  3. Immutable বানান: নিচের class-টিকে সত্যিকারের immutable করুন। কোন কোন লাইন বদলাতে হবে?
    class Batch { String name; List<String> students; Batch(String n, List<String> s) { name = n; students = s; } List<String> getStudents() { return students; } void setName(String n) { name = n; } }
    final class Batch {
        private final String name;
        private final List<String> students;
    
        Batch(String name, List<String> students) {
            this.name = name;
            this.students = List.copyOf(students);   // defensive copy
        }
        String name() { return name; }
        List<String> students() { return students; }  // unmodifiable, তাই নিরাপদ
    }

    রেসিপির সব ধাপ: class final; field private final; setName বাদ (নাম বদলাতে চাইলে withName(String) নতুন Batch ফেরত দেবে); constructor-এ List.copyOf — এতে বাইরের তালিকা পরে বদলালেও Batch বদলায় না, আর ফেরত দেওয়া তালিকায় কেউ add করলে exception আসে। String নিজেই immutable, তাই name-এর কপি লাগে না। এটাই প্রায় হুবহু record Batch(String name, List<String> students) — তবে record-এও List.copyOf আপনাকে নিজে লিখতে হয়, compact constructor-এ (পর্ব ২৫)।

  4. Constant না কনফিগারেশন? নিচের প্রতিটি মান static final constant হওয়া উচিত, না বাইরের কনফিগারেশন থেকে পড়া উচিত — কারণসহ বলুন: (ক) এক সপ্তাহে দিনের সংখ্যা, (খ) VAT-এর হার, (গ) ডেটাবেসের পাসওয়ার্ড, (ঘ) একটি রসিদে সর্বোচ্চ কতটি পণ্য, (ঙ) পূর্ণ নম্বর ১০০।

    (ক) DAYS_PER_WEEK = 7 — constant; প্রকৃতির নিয়ম, কখনো বদলাবে না। (খ) VAT — কনফিগারেশন ভালো; সরকার বাজেটে হার বদলাতে পারে, পণ্যভেদে আলাদাও হয়; constant হলে প্রতিবার নতুন compile ও deploy লাগবে। (গ) পাসওয়ার্ড — কখনোই কোডে নয়! কোডে লেখা গোপন তথ্য git-এ যায়, যেকোনো ডেভেলপার দেখে; environment variable বা নিরাপদ secret store থেকে পড়ুন। (ঘ) সর্বোচ্চ পণ্য — ব্যবসার নিয়ম; বদলানোর সম্ভাবনা কম হলে constant চলে, তবে মালিক প্রায়ই বদলাতে চাইলে কনফিগারেশন। (ঙ) পূর্ণ নম্বর — প্রসঙ্গের ওপর নির্ভর করে; একটি নির্দিষ্ট পরীক্ষার সিস্টেমে constant চলে, কিন্তু বিভিন্ন পরীক্ষার পূর্ণ নম্বর আলাদা হলে সেটা পরীক্ষার object-এর একটি final field হওয়া উচিত, static নয়। মূল প্রশ্ন সবসময় একটিই: মানটি কি সত্যিই সবার জন্য এক, আর কখনো বদলায় না?

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

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

ক্রমJava প্রোগ্রামে কোনো class-এর প্রথম object (new Student("Rahim")) তৈরি হওয়ার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 📂 class loader Student class-এর তথ্য load করে
  2. 🧷 static field-গুলো জায়গা পায়, শুরুতে ডিফল্ট মান (0, null)
  3. ⚙️ static field-এর শুরুর মান আর static block ওপর থেকে নিচে একবার চলে
  4. 📦 heap-এ নতুন object-এর জায়গা হয়, instance field ডিফল্ট মান পায়
  5. 🏗️ constructor চলে, final instance field-এ মান বসে

class Counter { static int count = 0; Counter() { count++; } } — main-এ new Counter(); new Counter(); new Counter(); System.out.println(Counter.count); লিখলে কী ছাপবে?

  • 0
  • 1
  • 3
  • compile error

Java-তে একটি static method-এর ভেতর থেকে একই class-এর instance field name সরাসরি পড়তে গেলে কী ঘটে?

  • প্রথম তৈরি হওয়া object-এর name পাওয়া যায়
  • compile error: non-static variable cannot be referenced from a static context
  • চলার সময় NullPointerException
  • compiler field-টিকে নিজেই static বানিয়ে নেয়

final List<String> names = new ArrayList<>(); — এর পরের কোন লাইনটি compile error দেবে?

  • names.add("Rahim");
  • names.clear();
  • names.remove("Rahim");
  • names = new ArrayList<>();

Java-তে public static void main(String[] args)-এ main method static কেন?

  • যাতে JVM কোনো object না বানিয়েই main ডাকতে পারে
  • static method instance method-এর চেয়ে দ্রুত চলে বলে
  • যাতে main অন্য কোনো method ডাকতে না পারে
  • যাতে main প্রোগ্রামে একবারের বেশি চলতে না পারে

একটি immutable class বানানোর জন্য নিচের কোনটি করা উচিত নয়?

  • সব field private final রাখা
  • class-টিকে final করা
  • প্রতিটি field-এর জন্য setter method দেওয়া
  • mutable List পেলে constructor-এ তার কপি রাখা

একটি library-তে public static final int LIMIT = 10; আছে, আপনার class সেটা ব্যবহার করে। library-তে মান 20 করে শুধু library আবার compile করা হলো, আপনার class নয়। আপনার class চলার সময় LIMIT কত দেখবে?

  • 20
  • 10
  • চলার সময় error
  • 0

Java-তে নিচের কোন বাক্যটি সঠিক?

  • String class-কে extend করে নতুন class বানানো যায়
  • static block প্রতিটি নতুন object তৈরির সময় চলে
  • method-এর ভেতরে static local variable ঘোষণা করা যায়
  • final method override করা যায় না, কিন্তু তার class থেকে subclass বানানো যায়

শব্দকোষ

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

মনে রাখুন

  1. static = class-এর নিজের, একটিই কপি, সব object ভাগ করে; class-এর নাম দিয়ে ব্যবহার করুন (Student.getCount())। Counter আর ক্রমিক ID এর আদর্শ ব্যবহার।
  2. static method-এ this নেই — instance field/method সরাসরি নয়; error সরাতে চোখ বন্ধ করে static বসাবেন না। main static, কারণ শুরুতে কোনো object নেই।
  3. static block class initialization-এ একবারই চলে; compile-time constant ব্যবহারকারীর bytecode-এ inline হয়।
  4. final = একবারই মান; constant = static final + UPPER_SNAKE_CASE. কিন্তু final reference ≠ immutable object — final StringBuilder-এ append চলে।
  5. Immutable class: private final field, setter নেই, final class, defensive copy — thread-safe আর নির্ভয়ে ভাগযোগ্য। বদলানো static অবস্থা এড়িয়ে চলুন: global state, টেস্টের ঝামেলা, memory leak.

পরের পর্বে: inheritance — extends দিয়ে একটি class থেকে আরেকটি বানানো, super দিয়ে parent-এর constructor ডাকা, আর override. সেখানেই বুঝবেন final method আর final class কেন আর কখন দরজা বন্ধ করে।

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

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