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

Java class ও object — নকশা থেকে আসল জিনিস

Java Classes and Objects — Fields, Instance Methods, new, References, null and Garbage Collection

Taught in Bangla, this lesson introduces classes and objects: a class as a blueprint with fields and instance methods, creating objects with new and what happens on the heap, independent object state, aliasing through shared references, null and NullPointerException, passing objects to methods, and sketching UML class diagrams.

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

  • Class আর object-এর পার্থক্য — নকশা বনাম আসল জিনিস, আর পর্ব ০২ ও ০৫-এর ধারণা এক জায়গায় আনা
  • Field আর instance method দিয়ে class লেখা, new দিয়ে object বানানো, dot দিয়ে ব্যবহার
  • new চলার সময় মেমরিতে কী ঘটে — heap, default মান, stack-এ reference
  • Reference শেয়ার (aliasing), null, NullPointerException-এর সহায়ক মেসেজ, method-এ object পাঠানো আর garbage collection
  • বাস্তব প্রজেক্টে class-এর নিয়ম, UML class diagram, আর বিবরণ থেকে class খুঁজে বের করা

১ · Class হলো নকশা, object হলো আসল জিনিস

পর্ব ১৩ পর্যন্ত আপনি Java-র “মূল ভাষা” শিখেছেন — টাইপ, লুপ, method, array. প্রতিটি প্রোগ্রামে একটি class Main ছিল, আর সব method ছিল static. এবার মডিউল ৩ শুরু, আর এখান থেকে Java তার আসল রূপে আসে: object-ভিত্তিক ভাষা।

পর্ব ০২-এ আপনি কাগজে-কলমে ভেবেছিলেন — একটি সমস্যাকে কয়েকটি জিনিসে ভাগ করা, যারা প্রত্যেকে কিছু জানে আর কিছু করতে পারে। ওয়ালেট জানে তার মালিক আর ব্যালান্স, আর পারে টাকা জমা নিতে, টাকা দিতে। এই “জানা” হলো state, আর “পারা” হলো behaviour. আজ সেই ভাবনাকে Java-র নিয়মে লিখবেন।

একটি আবাসন প্রকল্পের কথা ভাবুন। স্থপতি একটি ফ্ল্যাটের নকশা আঁকলেন — তিনটি ঘর, একটি বারান্দা, প্রতিটি ফ্ল্যাটে একটি মিটার। নকশায় কেউ থাকতে পারে না; সেটা কাগজ। সেই একই নকশা থেকে বিশটি আসল ফ্ল্যাট তৈরি হলো। প্রতিটি ফ্ল্যাটের কাঠামো এক, কিন্তু ভেতরের জিনিস আলাদা — ৩-এ ফ্ল্যাটে লাল পর্দা, মিটারে ২৪০ ইউনিট; ৫-বি ফ্ল্যাটে নীল পর্দা, মিটারে ৯০ ইউনিট। নকশা হলো class, প্রতিটি আসল ফ্ল্যাট হলো একটি object.

Java-র ভাষায়: class হলো একটি নকশা, যা বলে দেয় এই ধরনের প্রতিটি জিনিসের কোন কোন তথ্য থাকবে আর সে কোন কোন কাজ করতে পারবে। object হলো সেই নকশা থেকে বানানো একটি নির্দিষ্ট, আসল জিনিস, যা প্রোগ্রাম চলার সময় মেমরিতে থাকে। একটি object-কে বলা হয় তার class-এর একটি instance — “রিনার অ্যাকাউন্ট হলো BankAccount-এর একটি instance.”

Class — নকশা

Source কোডে একবার লেখা হয়। নিজে কোনো তথ্য ধরে না, শুধু কাঠামো বলে দেয়। একটি টাইপ-ও বটে: BankAccount এখন int বা String-এর মতো একটি টাইপের নাম।

compile-এর সময়

Object — আসল জিনিস

প্রোগ্রাম চলার সময় new দিয়ে তৈরি হয়, heap-এ থাকে। প্রতিটির নিজের তথ্য। একটি class থেকে শূন্য, এক বা লাখো object হতে পারে।

চলার সময়

Field — যা জানে

Class-এর ভেতরে ঘোষণা করা ভ্যারিয়েবল, যেমন balance. প্রতিটি object-এর জন্য আলাদা কপি।

state

Method — যা করে

Class-এর ভেতরের কাজ, যেমন deposit. যে object-এর ওপর ডাকা হয়, তার field নিয়েই কাজ করে।

behaviour

এই পর্বে দুটি পুরোনো ধারণা এক জায়গায় মিলবে। পর্ব ০২ থেকে আসবে নকশার ভাবনা — কোন জিনিস, কী দায়িত্ব। আর পর্ব ০৫ থেকে আসবে মেমরির ছবি — object থাকে heap-এ, ভ্যারিয়েবলে থাকে তার reference. এই দুটি ছবি একসাথে মাথায় রাখতে পারলে বাকি পুরো মডিউল সহজ হয়ে যাবে।

মূল ধারণা

“Class না object?” — প্রশ্ন করুন: এটা কি কাগজের নকশা, নাকি সেই নকশায় বানানো একটি নির্দিষ্ট জিনিস? “Student” একটি class; “রোল ১৭-এর সাদিয়া” একটি object. “Book” একটি class; লাইব্রেরির তাকে রাখা “পদ্মা নদীর মাঝি”-র ৩ নম্বর কপি একটি object.

২ · প্রথম class — field, instance method আর dot

বিকাশ বা নগদের মতো একটি মোবাইল ওয়ালেটের অ্যাকাউন্ট ভাবুন। প্রতিটি অ্যাকাউন্ট জানে তার মালিকের নাম আর ব্যালান্স; আর পারে টাকা জমা নিতে আর টাকা তোলার সুযোগ দিতে। Java-তে এর নকশা:

class BankAccount {
    String owner;                // field: মালিকের নাম
    long balance;                // field: ব্যালান্স, টাকায়

    void deposit(long amount) {  // instance method
        balance += amount;
    }

    long getBalance() {
        return balance;
    }
}

তিনটি জিনিস লক্ষ করুন। এক, owner আর balance কোনো method-এর ভেতরে নয়, সরাসরি class-এর ভেতরে — এরা field. পর্ব ১২-এর local variable ছিল এক ডাকের জন্য; field বাঁচে যতদিন object বাঁচে। দুই, deposit-এর আগে static নেই। এটি একটি instance method — কোনো একটি নির্দিষ্ট object-এর ওপর ডাকতে হয়, আর ভেতরে balance লিখলে মানে হয় “যে object-এর ওপর ডাকা হয়েছে, তার balance”. তিন, class-এর নাম বড় হাতের অক্ষরে শুরু (BankAccount), আর প্রতিটি শব্দের প্রথম অক্ষর বড় — একে বলে PascalCase; field আর method-এর নাম ছোট হাতের camelCase.

Object বানানো আর dot দিয়ে ব্যবহার

BankAccount rina = new BankAccount();   // object তৈরি
rina.owner = "Rina";                    // field-এ লেখা
rina.deposit(1500);                     // method ডাকা
long b = rina.getBalance();             // 1500

new হলো object বানানোর চাবি: “BankAccount নকশা থেকে একটি নতুন object বানাও, আর তার ঠিকানা আমাকে দাও।” সেই ঠিকানা বসে rina ভ্যারিয়েবলে, যার টাইপ BankAccount. তারপর dot (.) দিয়ে object-এর ভেতরে ঢোকা: rina.owner মানে “রিনার object-এর owner field”, rina.deposit(1500) মানে “রিনার object-কে বলো ১৫০০ জমা নিতে।” পর্ব ০২-এর ভাষায় এটাই message passing — object-কে একটি বার্তা পাঠানো।

new BankAccount()-এর শেষের বন্ধনী দেখে মনে হয় একটি method ডাকা হচ্ছে। আসলে ডাকা হচ্ছে একটি constructor — object তৈরির মুহূর্তে চলা বিশেষ কোড। আমরা কোনো constructor লিখিনি, তবু কোড চলছে, কারণ class-এ একটিও constructor না থাকলে compiler নিজেই একটি খালি, parameter-হীন constructor বসিয়ে দেয় — একে বলে default constructor. নিজের constructor লিখে শুরুতেই মালিকের নাম দেওয়া, আর this keyword — এগুলো পরের পর্বের (পর্ব ১৬) বিষয়।

Default মান — field কখনো “আবর্জনা” রাখে না

ওপরের কোডে balance-এ আমরা কোনো মান দিইনি। তাহলে নতুন object-এ balance কত? পর্ব ১২-এ দেখেছেন, মান না দিয়ে local variable পড়লে compile error হয়। Field-এর নিয়ম আলাদা: object তৈরির সময় JVM প্রতিটি field-এ একটি default value বসিয়ে দেয় — পর্ব ১৩-এ array-র ঘরে যে মান বসত, ঠিক সেগুলোই।

Field-এর টাইপDefault মানউদাহরণ
byte, short, int, long0long balance; → 0
float, double0.0double cgpa; → 0.0
char'\u0000' (শূন্য মানের অক্ষর)char grade;
booleanfalseboolean borrowed; → false
যেকোনো reference টাইপ (String, array, অন্য class)nullString owner; → null

কেন এই পার্থক্য? Local variable-এর জীবন একটি method-এর ভেতরে, compiler পুরো পথ দেখে নিশ্চিত হতে পারে মান দেওয়া হয়েছে কি না। কিন্তু field যেকোনো method থেকে, যেকোনো ক্রমে পড়া হতে পারে — compiler সেটা প্রমাণ করতে পারে না। তাই Java ঠিক করেছে, field অন্তত একটি নিশ্চিত, পরিচিত মান নিয়ে শুরু করবে। C-তে malloc করা struct-এ পুরোনো আবর্জনা থাকতে পারে; Java-তে কখনো নয়।

সাবধান: reference টাইপের default মান null — খালি String "" নয়। নতুন BankAccount-এ owner হলো null, তাই rina.owner.length() লিখলে প্রোগ্রাম ভেঙে পড়বে (অংশ ৬)। নতুনদের সবচেয়ে সাধারণ ভুলগুলোর একটি এটাই।

আরেকটি নকশা — Student আর গড় নম্বর

একই ধাঁচে একজন শিক্ষার্থীর নকশা। Field হিসেবে একটি array-ও রাখা যায় (পর্ব ১৩):

class Student {
    String name;
    int roll;
    int[] marks;                 // default: null — array নিজে তৈরি করতে হবে

    double average() {
        if (marks == null || marks.length == 0) return 0.0;
        int sum = 0;
        for (int m : marks) sum += m;
        return (double) sum / marks.length;
    }
}

average() কোনো parameter নেয় না — তবু সে জানে কার নম্বরের গড় বের করতে হবে, কারণ সে যে object-এর ওপর ডাকা হয়, সেই object-এর marks ব্যবহার করে। sadia.average() সাদিয়ার গড় দেয়, tanvir.average() তানভীরের। পর্ব ১২-এর static method হলে লিখতে হতো average(sadia.marks) — তথ্য আর কাজ আলাদা থাকত। Object-এ তথ্য আর তার ওপরের কাজ একসাথে থাকে; এটাই object-ভিত্তিক নকশার মূল কথা।

৩ · new চলার সময় মেমরিতে কী ঘটে

পর্ব ০৫-এ দেখেছিলেন, JVM-এর মেমরির দুটি বড় অংশ: stack — প্রতিটি method-ডাকের নিজের খাতা (stack frame), যেখানে local variable থাকে; আর heap — যেখানে সব object থাকে, যতক্ষণ কেউ তাদের ব্যবহার করে। BankAccount acc = new BankAccount(); — এই একটি লাইনে আসলে কয়েকটি আলাদা ঘটনা ঘটে। ধাপে ধাপে দেখুন:

ধাপ ১BankAccount acc = ... (বাঁ দিক)
stack: main-এর frame         heap
┌────────────────────┐
│ acc = ?            │       (এখনো কিছু নেই)
└────────────────────┘
শুধু একটি reference রাখার জায়গা — object নয়
ধাপ ২new: heap-এ জায়গা, default মান
stack: main-এর frame         heap
┌────────────────────┐       ┌───────────────────────┐
│ acc = ?            │       │ BankAccount           │
└────────────────────┘       │   owner   = null      │
                             │   balance = 0         │
                             └───────────────────────┘
জায়গা নেওয়া হলো, প্রতিটি field পেল default মান
ধাপ ৩constructor চলে (এখানে default constructor)
stack: main-এর frame         heap
┌────────────────────┐       ┌───────────────────────┐
│ acc = ?            │       │ BankAccount  ✔ প্রস্তুত │
└────────────────────┘       │   owner   = null      │
                             │   balance = 0         │
                             └───────────────────────┘
default constructor কিছুই বদলায় না — object এখন ব্যবহারযোগ্য
ধাপ ৪= : ঠিকানা বসল acc-এ
stack: main-এর frame         heap
┌────────────────────┐       ┌───────────────────────┐
│ acc = ●────────────┼──────▶│ BankAccount           │
└────────────────────┘       │   owner   = null      │
                             │   balance = 0         │
                             └───────────────────────┘
acc-এ object নেই, আছে object-এ পৌঁছানোর reference
ধাপ ৫acc.owner = "Rina"; acc.deposit(1500);
stack: main-এর frame         heap
┌────────────────────┐       ┌───────────────────────┐
│ acc = ●────────────┼──────▶│ BankAccount           │
└────────────────────┘       │   owner   = ●─────────┼──▶ [ String "Rina" ]
                             │   balance = 1500      │
                             └───────────────────────┘
dot মানে: তীর ধরে object-এ যাও, তারপর ভেতরে লেখো
  1. বাঁ দিকে শুধু একটি ভ্যারিয়েবল। BankAccount acc লিখলে main-এর frame-এ একটি ঘর তৈরি হয়, যার কাজ একটি BankAccount object-এর reference ধরে রাখা। এই ঘোষণায় কোনো object তৈরি হয় না — C++ থেকে আসা পাঠকের জন্য এটা বড় পার্থক্য।
  2. new heap-এ জায়গা নেয়। JVM দেখে নেয় BankAccount-এর কয়টি field, কত জায়গা লাগবে, তারপর heap-এ সেই জায়গা নেয়। প্রথম কাজটিই হলো প্রতিটি field-এ default মান বসানো: owner = null, balance = 0. এই কারণেই Java-র object-এ কখনো আবর্জনা মান থাকে না।
  3. Constructor চলে। এবার constructor-এর কোড চলে। আমাদের class-এ compiler-এর বসানো default constructor, যা কোনো field বদলায় না। পর্ব ১৬-এ নিজের constructor লিখলে এই ধাপেই owner-এ নাম বসবে।
  4. Reference ফেরত আসে। new-এর ফল হলো নতুন object-এর reference. = সেটি acc-এ রাখে। ছবির তীরটিই reference — একটি ঠিকানা, object নিজে নয়। এক লাইনের কোড, কিন্তু চারটি ঘটনা: জায়গা, default মান, constructor, ঠিকানা।
  5. Dot দিয়ে object-এ পৌঁছানো। acc.owner = "Rina" মানে: acc-এর তীর ধরে heap-এর object-এ যাও, তার owner ঘরে লেখো। খেয়াল করুন, "Rina" নিজেও একটি String object, তাই owner-এ থাকে আরেকটি তীর। deposit(1500) একইভাবে তীর ধরে গিয়ে balance বদলায়।

এই ছবি থেকে একটি গুরুত্বপূর্ণ সত্য বেরিয়ে আসে: Java-তে acc-এর মতো ভ্যারিয়েবলের টাইপ হলো reference type. পর্ব ০৬-এর আট ধরনের primitive type (int, double, boolean…) ছাড়া বাকি সব — String, array, আপনার নিজের প্রতিটি class — reference type. Primitive ভ্যারিয়েবলের ঘরে থাকে সরাসরি মান; reference ভ্যারিয়েবলের ঘরে থাকে একটি তীর।

Object কি সবসময় সত্যিই heap-এ? ভাষার নিয়মে (Java Language Specification) হ্যাঁ — প্রতিটি object new দিয়ে তৈরি, আর reference দিয়ে পৌঁছাতে হয়। ভেতরে ভেতরে JVM-এর JIT compiler কখনো কখনো চালাকি করে: যদি দেখে একটি object কোনো method-এর বাইরে কখনো যায় না, তাহলে তাকে heap-এ না বানিয়ে তার field-গুলোকে সাধারণ local মানে ভেঙে ফেলতে পারে। একে বলে escape analysis — ভাবনার প্রশ্ন ৩-এ বিস্তারিত। কিন্তু এটা শুধু গতির কৌশল; আপনার প্রোগ্রামের আচরণ সবসময় “object heap-এ, ভ্যারিয়েবলে reference” মডেলের মতোই।

৪ · প্রতিটি object-এর নিজের state

একই নকশা থেকে বিশটি ফ্ল্যাট — প্রতিটির মিটারে আলাদা রিডিং। ঠিক তেমনি, একই BankAccount class থেকে দুটি object বানালে প্রতিটির balance আলাদা। এক object-এর field বদলালে অন্যটির গায়ে আঁচড়ও লাগে না। নিচের প্রোগ্রামে রিনা আর করিমের দুটি অ্যাকাউন্ট; এবার withdraw-এ একটি নিয়মও আছে — ব্যালান্সের বেশি টাকা তোলা যাবে না। চালিয়ে দেখুন:

class BankAccount {
    String owner;
    long balance;                          // টাকায়, শুরুতে 0
    void deposit(long amount) { balance += amount; }
    boolean withdraw(long amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }
}
class Main {
    public static void main(String[] args) {
        BankAccount rina = new BankAccount();
        BankAccount karim = new BankAccount();
        rina.owner = "Rina";
        karim.owner = "Karim";
        rina.deposit(1500);
        karim.deposit(300);
        System.out.println("Rina  -500: " + rina.withdraw(500));
        System.out.println("Karim -500: " + karim.withdraw(500));
        System.out.println(rina.owner + " " + rina.balance + ", " + karim.owner + " " + karim.balance);
    }
}

আউটপুট তিন লাইন: Rina -500: true, Karim -500: false, Rina 1000, Karim 300. দুটি new মানে heap-এ দুটি আলাদা object, প্রতিটির নিজের owner আর balance. withdraw-এর কোড একটাই, কিন্তু রিনার object-এর ওপর ডাকায় সে রিনার ১৫০০ দেখে টাকা দিয়েছে; করিমের ওপর ডাকায় করিমের ৩০০ দেখে অনুরোধ ফিরিয়ে দিয়েছে। একটি ফাইলে দুটি class আছে, আর কোনোটির আগে public নেই — কারণ অনলাইন runner ফাইলটিকে অন্য নামে compile করে; বাস্তব প্রজেক্টের নিয়ম অংশ ৮-এ। আর একটি দুর্বলতা খেয়াল করুন: main চাইলে সরাসরি rina.balance = -5000; লিখে নিয়ম এড়িয়ে যেতে পারে। এই দরজা বন্ধ করা হবে পর্ব ১৭-এ, private দিয়ে।

লুকানো receiver — method কীভাবে জানে “কার” balance?

deposit-এর ভেতরে লেখা শুধু balance += amount;. কোথাও লেখা নেই রিনার না করিমের। তবু ঠিক object-টিই বদলায়। রহস্যটা হলো: প্রতিটি instance method-এর ডাকে Java চুপিচুপি একটি অতিরিক্ত জিনিস পাঠায় — dot-এর বাঁ দিকের object-এর reference. Method-এর ভেতরে তার নাম this, আর balance লেখা আসলে this.balance-এর সংক্ষিপ্ত রূপ। যে object-এর ওপর method ডাকা হয়, তাকে বলে receiver. ধাপে ধাপে দেখুন:

1void deposit(long amount) {2    balance += amount;   // আসলে this.balance3}45BankAccount rina  = new BankAccount();6BankAccount karim = new BankAccount();7rina.deposit(500);8karim.deposit(200);
heap আর deposit-এর frame
rina-র object: balance
karim-এর object: balance
deposit: this
deposit: amount
  1. দুটি object, দুটি আলাদা balance. দুটি new, তাই heap-এ দুটি object; দুটিরই balance default মান 0. Method-এর কোড কিন্তু মেমরিতে একবারই আছে — প্রতিটি object-এ method-এর কপি থাকে না, থাকে শুধু field.
  2. rina.deposit(500) ডাকা হলো। নতুন frame তৈরি হলো। Parameter amount-এ এল 500. আর একটি লুকানো parameter this-এ এল dot-এর বাঁ দিকের reference — রিনার object-এর ঠিকানা। আপনি এটা লেখেননি, compiler নিজে ব্যবস্থা করেছে।
  3. balance += amount চলে। balance মানে this.balance, আর this এখন রিনার object দেখাচ্ছে। তাই রিনার balance হলো 500. করিমের object-এর দিকে কেউ তাকায়ইনি।
  4. karim.deposit(200). একই method, নতুন frame. এবার this-এ এল করিমের object-এর ঠিকানা, amount-এ 200.
  5. একই কোড, অন্য object. এবার this.balance মানে করিমের balance, যা হলো 200. রিনার 500 অক্ষত। একটি কোড, অনেক object — প্রতিটি ডাকে this বলে দেয় কার ওপর কাজ হবে। Python-এ এই জিনিসটিই self নামে নিজে হাতে লিখতে হয়।

Instance বনাম static — এক ঝলক

তাহলে পর্ব ১২-এর static method কী ছিল? static মানে “class-এর, কোনো object-এর নয়”। static method-এ কোনো this নেই — সে কোনো object-এর ওপর ডাকা হয় না, তাই instance field সরাসরি পড়তেও পারে না। Math.max(3, 7) একটি static method: সর্বোচ্চ মান বের করতে কোনো “Math object”-এর state লাগে না। অন্যদিকে deposit একটি নির্দিষ্ট অ্যাকাউন্টের balance বদলায় — তাই instance method.

Static method-এর ভেতর থেকে instance field পড়তে চাইলে কী হয়? main-এ সরাসরি balance = 10; লিখলে javac বলে non-static variable balance cannot be referenced from a static context — অর্থাৎ “কার balance? এখানে তো কোনো object নেই।” নতুনদের কাছে এটি খুব পরিচিত error. প্রশ্নটা সবসময় একই: এই কাজের জন্য কি একটি নির্দিষ্ট object-এর তথ্য লাগে? লাগলে instance, না লাগলে static. static field আর final-এর পুরো আলোচনা পর্ব ১৮-এ।

৫ · একটি object, দুটি reference — aliasing

পর্ব ১৩-এ array-র সাথে যা দেখেছেন, সব object-এর সাথে তা-ই ঘটে: BankAccount b = a; লিখলে নতুন কোনো object তৈরি হয় না। কপি হয় শুধু তীরটি। এখন দুটি ভ্যারিয়েবল একই object দেখায় — একে বলে aliasing. একটি দিয়ে বদলালে অন্যটি দিয়েও সেই বদল দেখা যায়, কারণ object তো একটাই।

আপনার বিকাশ অ্যাকাউন্টের নম্বর একটি কাগজে লিখে ছোট ভাইকে দিলেন। এখন দুজনের কাছে দুটি কাগজ, কিন্তু অ্যাকাউন্ট একটাই। ভাই সেই নম্বরে ৭০০ টাকা পাঠালে আপনার অ্যাপেও ৭০০ বেশি দেখাবে। কাগজ কপি করা আর অ্যাকাউন্ট কপি করা এক জিনিস নয়। পরে ভাই তার কাগজ মুছে অন্য একটি নম্বর লিখলে আপনার কাগজ বা অ্যাকাউন্ট — কোনোটাই বদলায় না।

ধাপ ১BankAccount a = new BankAccount();
stack            heap
a ●─────────▶ [ #1  balance = 0 ]

একটি object, একটি তীর
ধাপ ২BankAccount b = a;
stack            heap
a ●─────────┐
            ├─▶ [ #1  balance = 0 ]
b ●─────────┘
নতুন object নেই — শুধু তীরের কপি
ধাপ ৩b.deposit(700); তারপর a.balance পড়া
stack            heap
a ●─────────┐
            ├─▶ [ #1  balance = 700 ]
b ●─────────┘
b দিয়ে বদলালাম, a দিয়েও 700 দেখা যায়
ধাপ ৪b = new BankAccount();
stack            heap
a ●─────────▶ [ #1  balance = 700 ]
b ●─────────▶ [ #2  balance = 0 ]

শুধু b-এর তীর ঘুরল; a আর #1 অক্ষত
ধাপ ৫a == b ?
ধাপ ২-৩-এ:  a == b  →  true   (একই object)
ধাপ ৪-এর পরে: a == b  →  false  (দুটি আলাদা object)

== দেখে তীর একই জায়গায় কি না — ভেতরের মান নয়
  1. একটি object. new একবার, তাই heap-এ একটি object (ছবিতে #1), আর a-তে তার reference.
  2. b = a — তীরের কপি। = সবসময় ভ্যারিয়েবলের মান কপি করে। a-এর মান হলো একটি reference, তাই b পেল সেই একই reference. Object-এর সংখ্যা এখনো এক। দুটি নাম, একটি জিনিস।
  3. এক তীর দিয়ে বদল, অন্য তীর দিয়ে দেখা। b.deposit(700) তীর ধরে #1-এ গিয়ে balance বদলাল। এখন a.balance পড়লেও 700 — কারণ a-ও #1-কেই দেখাচ্ছে। Bug-এর একটি বড় উৎস এটাই: কোডের এক কোণে কেউ “নিজের কপি” ভেবে object বদলায়, আর অন্য কোণে আরেকজন অবাক হয়।
  4. তীর ঘোরানো। b = new BankAccount() নতুন object #2 বানাল আর শুধু b-এর তীর সেদিকে ঘোরাল। a এখনো #1 দেখায়, তার balance এখনো 700. ভ্যারিয়েবলে নতুন মান বসানো আর object-এর ভেতরটা বদলানো — দুটি সম্পূর্ণ আলাদা কাজ।
  5. == কী তুলনা করে। Reference-এর ক্ষেত্রে == জানতে চায় দুটি তীর একই object-এ কি না — একে বলে reference equality বা identity. ধাপ ২-৩-এ উত্তর true, ধাপ ৪-এর পরে false — যদিও #2-এর balance 0 আর #1-এর 700, তাতে কিছু যায় আসে না; মান সমান হলেও আলাদা object হলে false.

Identity বনাম equality — একটি ঝলক

দুটি আলাদা Book object, দুটিরই title “পদ্মা নদীর মাঝি”. এরা কি “সমান”? দুই অর্থে প্রশ্ন করা যায়। Identity: এরা কি একই object? উত্তর না, == দেবে false. Equality: এরা কি অর্থের দিক থেকে একই জিনিস (একই বই)? সেটা নির্ভর করে আপনি “সমান” বলতে কী বোঝেন — একই title? একই ISBN? এই প্রশ্নের উত্তর class-এর লেখক দেন equals() method লিখে। আপনার নিজের class-এ equals না লিখলে সেটি ==-এর মতোই আচরণ করে। পর্ব ০৮-এ String-এর বেলায় এটা দেখেছিলেন (== নয়, equals); নিজের class-এ equals ঠিকভাবে লেখা হবে পর্ব ২৩-এ।

নিচের প্রোগ্রামে aliasing, method-এ object পাঠানো, default মান আর identity — চারটিই একসাথে। লাইব্রেরির একটি বই:

class Book {
    String title;
    int timesBorrowed;
    boolean borrowed;
}
class Main {
    static void lend(Book b) {             // reference-এর কপি আসে
        b.borrowed = true;
        b.timesBorrowed++;
    }
    public static void main(String[] args) {
        Book a = new Book();
        System.out.println(a.title + " " + a.timesBorrowed + " " + a.borrowed);
        a.title = "Padma Nadir Majhi";
        Book shelf = a;                    // নতুন object নয়
        lend(shelf);
        System.out.println(a.title + ": " + a.borrowed + ", " + a.timesBorrowed);
        Book c = new Book();
        c.title = "Padma Nadir Majhi";
        System.out.println("a == shelf : " + (a == shelf));
        System.out.println("a == c     : " + (a == c));
        System.out.println("same title : " + a.title.equals(c.title));
    }
}

আউটপুট: null 0 false, Padma Nadir Majhi: true, 1, a == shelf : true, a == c : false, same title : true. প্রথম লাইনে তিনটি field-এর default মান — String-এর null (String জোড়ার সময় “null” লেখা হয়ে ছাপা হয়), int-এর 0, boolean-এর false. তারপর shelf = a কোনো নতুন বই বানায়নি; lend(shelf) সেই একমাত্র বইয়ের reference-এর একটি কপি পেয়ে ভেতরটা বদলেছে, তাই a দিয়ে পড়লেও true, 1. এটাই পর্ব ১২-এর pass-by-value: reference কপি হয়, object নয়। c দেখতে হুবহু একই বই, কিন্তু আলাদা new — তাই a == c false, যদিও title দুটি equals দিয়ে সমান।

Object ছাপলে কী আসে — Book@...

কৌতূহলে কেউ লেখেন System.out.println(a);. আউটপুট আসে এমন কিছু:

Book@251a69d7

এটা কোনো ভুল নয়, আবার কাজেরও নয়। প্রতিটি Java class নিঃশব্দে Object নামের একটি মূল class থেকে কিছু method পায়, তার একটি toString(). আপনি নিজে না লিখলে সেটি ফেরত দেয় class-এর নাম, একটি @, আর object-এর hash code — ষোলো-ভিত্তিক (hexadecimal) সংখ্যায়। সংখ্যাটা প্রতিবার চালালে আলাদা হতে পারে, আর এটি মেমরির ঠিকানা নয় (অনেকে তা-ই ভাবেন)। তাই এই কোর্সের চালানো যায় এমন কোডে কখনো object সরাসরি ছাপা হয় না। Object-কে অর্থপূর্ণভাবে ছাপতে তার class-এ নিজের toString() লিখতে হয় — যেমন Book[title=Padma Nadir Majhi, borrowed=true]. কীভাবে, তা পর্ব ২৩-এ।

৬ · null আর NullPointerException

একটি reference ভ্যারিয়েবল কোনো object না দেখিয়েও থাকতে পারে। তখন তার মান null — “তীর নেই”. Field-এ reference টাইপের default মান null; আর আপনি নিজেও লিখতে পারেন Book b = null;. সমস্যা শুরু হয় যখন null-এর ওপর dot চালানো হয় — তীর নেই, অথচ তীর ধরে object-এ যেতে বলা হচ্ছে। JVM তখন ছোড়ে NullPointerException (সংক্ষেপে NPE).

class Library {
    Book featured;               // default: null — কোনো বই রাখা হয়নি
}
// main-এর ভেতরে:
Library lib = new Library();
lib.featured.borrow();           // featured null — এখানে ভেঙে পড়বে

Java 14-এর আগে মেসেজ ছিল শুধু java.lang.NullPointerException আর লাইন নম্বর। এক লাইনে a.b.c.d() থাকলে বোঝার উপায় ছিল না কোনটি null. Java 14-এ JEP 358 নিয়ে আসে “helpful NullPointerException” — এখন JVM নিজেই বলে দেয় কী করতে গিয়ে, আর কোন জিনিসটি null ছিল বলে ব্যর্থ হলো (Java 15 থেকে এটি ডিফল্টভাবে চালু)। OpenJDK 21-এ চালিয়ে পাওয়া আসল মেসেজ:

Exception in thread "main" java.lang.NullPointerException: Cannot invoke "Book.borrow()" because "lib.featured" is null

পড়ুন বাঁ থেকে ডানে: “Book.borrow() ডাকা গেল না, কারণ lib.featured হলো null.” সাথে সাথে বোঝা যায় — lib ঠিক আছে, সমস্যা featured field-এ, যেখানে কেউ কোনো বই রাখেনি। Field পড়তে গেলে মেসেজ হয় Cannot read field "title" because ... is null, আর লিখতে গেলে Cannot assign field.

কখনো নামের জায়গায় <local1> দেখবেন। Local variable-এর নাম class ফাইলে থাকে শুধু যদি javac-কে -g option দিয়ে compile করা হয়। Maven, Gradle আর IDE সাধারণত তা দেয়, তাই সেখানে আসল নাম দেখবেন। কিন্তু শুধু javac Main.java চালালে, বা এই পাতার অনলাইন runner-এ, একই মেসেজ আসে এভাবে: because "<local1>.featured" is null — অর্থাৎ “প্রথম local variable-এর featured”. দুটিই OpenJDK 21-এ যাচাই করা।

NPE থেকে বাঁচার অভ্যাস

  • শুরুতেই মান দিন। যে field কখনো null হওয়া উচিত নয়, তাকে object তৈরির সময়েই মান দিন — constructor দিয়ে (পর্ব ১৬), বা ঘোষণার সাথেই (int[] marks = new int[0];).
  • সীমানায় যাচাই। বাইরে থেকে আসা reference (method-এর parameter) null হতে পারলে শুরুতেই পরীক্ষা করুন: if (b == null) return false;. Java-র নিজের Objects.requireNonNull(b) null পেলে সাথে সাথে স্পষ্ট মেসেজসহ ব্যর্থ হয় — পরে গভীরে গিয়ে ভাঙার চেয়ে ভালো।
  • “নেই” বোঝাতে null ফেরত দেওয়ার আগে ভাবুন। খালি তালিকা ফেরত দেওয়া (null-এর বদলে) প্রায়ই নিরাপদ; আর “থাকতেও পারে, না-ও থাকতে পারে” বোঝাতে Optional আছে (পর্ব ৩৬)।

null-এর ওপর == নিরাপদ, dot নয়। b == null কোনো object-এ যায় না, শুধু তীর আছে কি না দেখে — তাই কখনো NPE দেয় না। কিন্তু b.equals(x)-এ b null হলে NPE. String তুলনায় তাই অনেকে উল্টো লেখেন: "Rina".equals(name) — name null হলেও শুধু false আসে।

৭ · Method-এ object আর garbage collection

অংশ ৫-এর প্রোগ্রামে lend(shelf) দেখলেন: method পায় reference-এর একটি কপি, আর সেই কপি ধরে আসল object-এর ভেতরটা বদলাতে পারে। পর্ব ১২-এর নিয়মই — pass by value, যেখানে object-এর “মান” হলো তার reference. দুটি ফল মনে রাখুন। (১) Method object-এর field বদলালে caller সেই বদল দেখে। (২) Method নিজের parameter-এ b = new Book(); বসালে caller কিছুই টের পায় না — শুধু method-এর নিজের তীর ঘুরল। তাই কোনো method যদি parameter-এ পাওয়া object বদলায়, সেটা তার নাম বা Javadoc-এ স্পষ্ট বলা উচিত (lend, markBorrowed) — নইলে caller অবাক হবেন।

যে object-এর দিকে কেউ তাকিয়ে নেই

Java-তে object মুছে ফেলার কোনো delete নেই। তাহলে মেমরি ফেরত আসে কীভাবে? পর্ব ০৫-এ দেখেছেন: garbage collection. GC কিছু শুরুর বিন্দু থেকে — যেমন চলমান method-গুলোর local variable, যাদের বলে GC root — তীর ধরে ধরে হাঁটে। যে object-এ কোনো পথেই পৌঁছানো যায় না, সে আর কারও কাজে আসবে না; সেই object মুছে ফেলার যোগ্য।

ধাপ ১Book x = new Book(); Book y = new Book();
stack            heap
x ●─────────▶ [ বই #1 ]
y ●─────────▶ [ বই #2 ]

দুটি object-ই পৌঁছানো যায়
ধাপ ২x = y;
stack            heap
x ●─────────┐   [ বই #1 ]  ← কোনো তীর নেই
            ├─▶ [ বই #2 ]
y ●─────────┘
#1 এখন unreachable — GC-র যোগ্য
ধাপ ৩y = null;
stack            heap
x ●───────────▶ [ বই #2 ]
y = null         [ বই #1 ]  (যোগ্য, এখনো পড়ে আছে)

#2-এ x দিয়ে এখনো পৌঁছানো যায় — নিরাপদ
ধাপ ৪method শেষ — frame মুছে গেল
stack            heap
(frame নেই)      [ বই #1 ]  ← যোগ্য
                 [ বই #2 ]  ← যোগ্য

local variable-ই ছিল শেষ পথ; এখন দুটিই unreachable
ধাপ ৫পরে কোনো এক সময় GC চলে
stack            heap
                 (জায়গা খালি — আবার ব্যবহারযোগ্য)

কখন চলবে, JVM ঠিক করে — আপনি নন
  1. দুটি বই, দুটি তীর। দুটি new, দুটি object. x আর y দুটিই চলমান method-এর frame-এ — GC root — তাই দুটি বইয়েই পৌঁছানো যায়।
  2. x = y — বই #1 একা হয়ে গেল। x-এর তীর এখন #2-এ। #1-এর দিকে আর কোনো তীর নেই; প্রোগ্রামের কোনো কোড আর কখনো #1-এ পৌঁছাতে পারবে না। এমন object-কে বলে unreachable, আর সে GC-র যোগ্য। খেয়াল করুন — যোগ্য, মুছে ফেলা নয়। সে আপাতত heap-এ পড়ে থাকে।
  3. y = null — কিন্তু #2 নিরাপদ। y-এর তীর গেল, কিন্তু x এখনো #2 দেখায়। একটি object যোগ্য হয় তখনই, যখন তার দিকে সব পথ বন্ধ — একটি পথ খোলা থাকলেও সে বেঁচে থাকে।
  4. Method শেষ। Frame মুছে গেলে তার local variable-ও নেই। #2-এর শেষ তীর ছিল x; এখন সে-ও unreachable. যেকোনো method-এর ভেতরে তৈরি অস্থায়ী object এভাবেই নিজে থেকে যোগ্য হয়ে যায় — আপনাকে কিছু করতে হয় না।
  5. GC চলে — আপনার হুকুমে নয়। কখন GC চলবে, তা JVM ঠিক করে — সাধারণত heap-এর একটি অংশ ভরে এলে। System.gc() লিখলে শুধু অনুরোধ করা হয়, নিশ্চয়তা নয়। তাই “object মুছে গেলে এই কাজ করো” ধরনের নির্ভরতা Java-তে চলে না; ফাইল বা নেটওয়ার্ক সংযোগ বন্ধ করতে আছে try-with-resources (পর্ব ২৯)। GC-র ভেতরের কৌশল পর্ব ৪৪-এ।

একটি সূক্ষ্ম কথা: GC থাকলেও Java-তে memory leak হতে পারে। কোনো দীর্ঘজীবী object — যেমন একটি static তালিকা — যদি এমন object-এর reference ধরে রাখে, যা আর দরকার নেই, তাহলে সেগুলো reachable থেকে যায়, আর GC তাদের ছোঁয় না। GC শুধু “পৌঁছানো যায় না” এমন object সরায়, “দরকার নেই” এমন object নয় — সেটা বোঝা আপনার দায়িত্ব।

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

C-র struct-এ শুধু ডেটা থাকে; Java class-এ ডেটা আর method একসাথে, আর free-এর বদলে GC. C++-এ BankAccount acc; লিখলেই stack-এ একটি পূর্ণ object তৈরি হয়; Java-তে একই লেখা শুধু একটি reference বানায়, object আসে কেবল new দিয়ে, সবসময় heap-এ — আর Java-তে destructor নেই। Python-এর মতোই Java-র ভ্যারিয়েবল object-এর reference রাখে, কিন্তু Java-তে কোন field থাকবে তা compile-এর সময় class-এ ঠিক হয়ে যায় (চলতে চলতে obj.newField = 1 যোগ করা যায় না), আর Python-এর self-এর বদলে Java-তে this নিজে থেকেই থাকে। তুলনা করতে দেখুন C++-এর class ও object আর Python-এর class ও object পর্ব।

৮ · বাস্তব প্রজেক্টে class — ফাইল, UML আর নকশা

এক ফাইলে এক public class

এই পাতার runner-এ সব class এক ফাইলে, কোনোটিতে public নেই। বাস্তব প্রজেক্টে রীতি ভিন্ন, আর একটি নিয়ম compiler নিজেই চাপিয়ে দেয়:

  • একটি .java ফাইলে বড়জোর একটি top-level public class, আর ফাইলের নাম হুবহু সেই class-এর নামে। public class BankAccount থাকবে BankAccount.java-এ। নাম না মিললে javac বলে class BankAccount is public, should be declared in a file named BankAccount.java.
  • রীতি: প্রতিটি class নিজের ফাইলে। public ছাড়া class একই ফাইলে রাখা বৈধ, কিন্তু দলগত কাজে প্রায় সবাই প্রতিটি class আলাদা ফাইলে রাখেন — খুঁজে পাওয়া সহজ, Git-এ পরিবর্তন দেখা সহজ।
  • public ছাড়া class শুধু নিজের package-এর ভেতর থেকে ব্যবহার করা যায়। কে কোন class, field বা method দেখতে পাবে — এই নিয়মগুলোকে বলে access modifier, পর্ব ১৭-এর বিষয়।
wallet-app/src/
├── Main.java           public class Main        (main method এখানে)
├── BankAccount.java    public class BankAccount
└── Transaction.java    public class Transaction

UML class diagram — কোড লেখার আগের ছবি

দলে আলোচনার সময় পুরো কোড না লিখে class-এর নকশা আঁকা হয় একটি মানক চিহ্নে — UML class diagram. প্রতিটি class একটি বাক্স, তিন তলা: ওপরে নাম, মাঝে field, নিচে method. নামের আগের চিহ্ন বলে কে দেখতে পাবে: - মানে private, + মানে public, ~ মানে package-এর ভেতরে। টাইপ লেখা হয় নামের পরে, কোলনের পরে।

┌─────────────────────────────────────┐
│             BankAccount             │  ← নাম
├─────────────────────────────────────┤
│ - owner : String                    │  ← field (state)
│ - balance : long                    │
├─────────────────────────────────────┤
│ + deposit(amount : long) : void     │  ← method (behaviour)
│ + withdraw(amount : long) : boolean │
│ + getBalance() : long               │
└─────────────────────────────────────┘

ছবিতে field-গুলো - (private) — আমাদের এখনকার কোডে নয়, কিন্তু পর্ব ১৭-এর পর এটাই হবে লক্ষ্য। দুটি class-এর সম্পর্ক বোঝাতে বাক্সের মাঝে রেখা টানা হয় (“একটি BankAccount-এর অনেক Transaction”); inheritance-এর জন্য ফাঁপা ত্রিভুজ-মাথার তীর — পর্ব ১৯-এ দেখবেন। IntelliJ-সহ অনেক টুল কোড থেকে এই ছবি বানাতে পারে, আর ইন্টারভিউ বা লিখিত পরীক্ষায় “ছোট একটি system-এর class diagram আঁকুন” ধরনের প্রশ্ন আসতে পারে।

বিবরণ থেকে class — বিশেষ্য আর ক্রিয়া

কোন কোন class লাগবে, বুঝবেন কীভাবে? পর্ব ০২-এর কৌশল — বিশেষ্য-ক্রিয়া বিশ্লেষণ. একটি ছোট লাইব্রেরি সিস্টেমের বিবরণ পড়ুন:

“লাইব্রেরিতে অনেক বই থাকে। প্রতিটি বইয়ের একটি title আর লেখক আছে। একজন সদস্য একসাথে সর্বোচ্চ তিনটি বই ধার নিতে পারেন, আর সময়মতো ফেরত দিতে হয়। লাইব্রেরি কোনো বই খুঁজে দিতে পারে।”

  • মোটা বিশেষ্য → class: Book, Member, Library.
  • দাগ দেওয়া বিশেষ্য (কোনো কিছুর গুণ) → field: Book-এর title, author; Member-এর ধার নেওয়া বইয়ের তালিকা।
  • ক্রিয়া → method, যার কাছে তথ্য আছে তার কাছে: member.borrow(book), member.returnBook(book), library.find(title).
  • নিয়ম → method-এর ভেতরের যাচাই: “সর্বোচ্চ তিনটি” — borrow চতুর্থ বই ফিরিয়ে দেবে, ঠিক যেমন withdraw ব্যালান্সের বেশি টাকা ফিরিয়েছে।

এরপর প্রতিটি class-এর জন্য পর্ব ০২-এর CRC কার্ড — দায়িত্ব কী, সহযোগী কে — আর তারপর UML বাক্স। কোড আসে সবার শেষে। একটি সতর্কসংকেত মনে রাখুন: যদি একটি class-এর নাম হয় LibraryManager আর তার ভেতরে বই, সদস্য, জরিমানা, রিপোর্ট সব থাকে, তাহলে সম্ভবত আপনি একটি god class বানাচ্ছেন — দায়িত্বগুলো আলাদা class-এ ভাগ করুন।

পরীক্ষা ও ইন্টারভিউতে
  • Class আর object-এর পার্থক্য? — Class হলো নকশা বা টাইপ (source কোডে, একবার); object হলো সেই নকশা থেকে new দিয়ে তৈরি নির্দিষ্ট instance, যা চলার সময় heap-এ থাকে আর যার নিজের field-মান আছে।
  • Book b; লিখলে কয়টি object তৈরি হয়? — একটিও না; শুধু একটি reference ভ্যারিয়েবল। Object তৈরি হয় কেবল new দিয়ে।
  • Field আর local variable-এর default মানে পার্থক্য? — Field পায় 0 / 0.0 / false / null; local variable কোনো default পায় না, মান না দিয়ে পড়লে compile error.
  • b = a;-এর পরে b দিয়ে field বদলালে a কী দেখে? — একই বদল, কারণ দুটি reference একই object দেখায় (aliasing).
  • একটি object কখন garbage collection-এর যোগ্য? — যখন কোনো GC root থেকে কোনো reference-পথে তাতে আর পৌঁছানো যায় না; কখন আসলে মোছা হবে তা JVM ঠিক করে।

৯ · AI-এর যুগে — class-এর নকশা যাচাই করা আপনার কাজ

AI-কে “একটি লাইব্রেরি ম্যানেজমেন্ট সিস্টেম Java-তে লিখে দাও” বললে কয়েক সেকেন্ডে কয়েকশো লাইন আসে, আর কোড চলেও। কিন্তু class-এর নকশায় AI-এর কিছু পরিচিত অভ্যাস আছে, যা ছোট প্রজেক্টে চোখে পড়ে না, আর বড় হলে দলকে ভোগায়। কোড চলে কি না, সেটা compiler বলবে; নকশা ঠিক কি না, সেটা বলতে হবে আপনাকে।

AI যা করেকেন সমস্যাআপনি যা করবেন
সব field public, বা getter-setter দিয়ে সব খুলে দেয়যে কেউ balance = -5000 লিখে নিয়ম ভাঙতে পারেনিয়ম আছে এমন field-এ সরাসরি লেখা বন্ধ — কাজ হবে method দিয়ে (পর্ব ১৭)
একটি LibraryManager-এ সব কাজ — god classএক জায়গার বদলে অন্য জায়গা ভাঙে, টেস্ট করা কঠিনবিশেষ্য-ক্রিয়া থেকে class ভাগ করতে বলুন, প্রতিটির দায়িত্ব এক লাইনে
ব্যাখ্যায় class আর object গুলিয়ে ফেলে (“Book object-এ title field ঘোষণা করুন”)নতুনদের মাথায় নকশা আর instance মিশে যায়নিজে প্রশ্ন করুন: এটা নকশা, না নির্দিষ্ট জিনিস?
Object সরাসরি ছাপে, toString ছাড়াআউটপুটে Book@251a69d7 — কোনো তথ্য নেইনির্দিষ্ট field ছাপতে বলুন, বা toString চান (পর্ব ২৩)
সব method static, ডেটা আলাদা array-তেObject-এর সুবিধা হারায়, তথ্য আর কাজ আলাদা থাকেযে কাজে object-এর state লাগে, তা instance method হবে
Reference টাইপের field null রেখে দেয়প্রথম ব্যবহারেই NullPointerExceptionপ্রতিটি field কোথায় মান পায় দেখুন — শুরুতে না constructor-এ

AI-কে class-এর নকশা বা কোড চাওয়ার সময় আগে নকশা চান, কোড পরে। নিচের কাঠামো ব্যবহার করুন:

আমি Java 21 শিখছি, এখন class আর object শিখছি (constructor, private, inheritance এখনো শিখিনি)। সিস্টেমের বিবরণ: [যেমন — একটি মোবাইল ওয়ালেট, যেখানে প্রতিটি অ্যাকাউন্টে মালিক আর ব্যালান্স আছে; টাকা জমা আর তোলা যায়; ব্যালান্সের বেশি তোলা যাবে না] ধাপ ১ — কোড লেখার আগে: ১. বিবরণ থেকে বিশেষ্য আর ক্রিয়া আলাদা করো; কোনগুলো class, কোনগুলো field, কোনগুলো method হবে — কারণসহ বলো। ২. প্রতিটি class-এর দায়িত্ব এক বাক্যে লেখো; কোনো class-এ তিনটির বেশি অসম্পর্কিত দায়িত্ব থাকলে ভাগ করো। ৩. প্রতিটি class-এর UML বাক্স টেক্সটে আঁকো (নাম, field, method). ধাপ ২ — তারপর কোড: ৪. প্রতিটি field-এর মান কোথায় আসে আর কোনটি null থাকতে পারে, তা comment-এ বলো। ৫. যে কাজে object-এর state লাগে, তা instance method হবে, static নয়। ৬. কোনো object সরাসরি println করবে না; নির্দিষ্ট field ছাপবে। ৭. main-এ অন্তত দুটি object বানিয়ে দেখাও যে তাদের state আলাদা। ৮. শেষে বলো: কোন field-এ বাইরে থেকে সরাসরি লেখা বিপজ্জনক, আর কেন।

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

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

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

প্র ০১Class আর object-এর ধারণা কোথা থেকে এলো? একটি সিমুলেশন ভাষা কীভাবে আজকের Java-র পূর্বপুরুষ হলো?

Class আর object-এর জন্ম নরওয়েতে, ১৯৬০-এর দশকে, একটি খুব বাস্তব সমস্যা থেকে। অসলোর Norwegian Computing Center-এ Ole-Johan Dahl আর Kristen Nygaard কাজ করছিলেন সিমুলেশন নিয়ে — জাহাজঘাট, কারখানা, মানুষের সারি কীভাবে আচরণ করে, তা কম্পিউটারে মডেল করা। এমন মডেলে অনেকগুলো “জিনিস” থাকে (প্রতিটি জাহাজ, প্রতিটি ক্রেন), যাদের প্রত্যেকের নিজের অবস্থা আর নিজের আচরণ, আর তারা একসাথে, পাশাপাশি চলে।

Simula থেকে Simula 67. প্রথমে তাঁরা Simula নামে একটি সিমুলেশন ভাষা তৈরি করেন (এখন যাকে Simula I বলা হয়)। তারপর অভিজ্ঞতা থেকে একটি সাধারণ-উদ্দেশ্যের ভাষা — Simula 67. এই ভাষায় প্রথমবার একসাথে এলো সেই ধারণাগুলো, যা আজ আপনি Java-তে দেখছেন: class (নকশা), object (নকশা থেকে তৈরি instance, যার নিজের ডেটা), subclass (একটি class থেকে আরেকটি বানানো — পরে যার নাম হয় inheritance), আর virtual procedure (subclass-এ আচরণ বদলানো — পর্ব ২০-এর dynamic dispatch-এর পূর্বসূরি)। Simula-তে object তৈরি হতো new দিয়ে আর reference দিয়ে ব্যবহার হতো, আর ভাষাটিতে garbage collection-ও ছিল — শুনতে চেনা লাগছে কি?

Simula থেকে Java পর্যন্ত পথ। ১৯৭০-এর দশকের শেষে কেমব্রিজে PhD-র কাজে Bjarne Stroustrup Simula ব্যবহার করে মুগ্ধ হন — ভাষাটি দিয়ে বড় প্রোগ্রাম সাজানো সহজ ছিল, কিন্তু চলত ধীরে। তাই তিনি C-র গতির সাথে Simula-র class জোড়া দিয়ে “C with Classes” তৈরি করেন, যা পরে হয় C++. অন্যদিকে Xerox PARC-এ Alan Kay-এর দল Simula-র ধারণা থেকে অনুপ্রাণিত হয়ে তৈরি করে Smalltalk. ১৯৯০-এর দশকে Java এসেছে এই দুই ধারার মাঝখানে: ব্যাকরণ C++-এর মতো, কিন্তু GC, “সবকিছুই reference দিয়ে” আর runtime-এর ধারণায় Smalltalk ও Simula-র ছাপ। ২০০১ সালে Dahl আর Nygaard একসাথে ACM Turing Award পান — object-oriented programming-এর মূল ধারণাগুলো উদ্ভাবনের স্বীকৃতিতে।

কেন এই ইতিহাস আজও কাজের। Simula জন্মেছিল বাস্তব জগৎ মডেল করতে — এই কারণেই পর্ব ০২-এ আমরা বিবরণের বিশেষ্য থেকে class খুঁজেছি। একই সাথে মনে রাখুন, সিমুলেশনে “একটি জাহাজ = একটি object” স্বাভাবিক, কিন্তু সব সমস্যায় এমন সরাসরি মিল নেই; কিছু সমস্যা আসলে ডেটা রূপান্তরের, যেখানে জোর করে object বানালে নকশা ভারী হয় (প্রশ্ন ৫)।

মূল উপলব্ধি: আজকের class, new, reference আর GC — সবগুলোর বয়স অর্ধশতাব্দীরও বেশি; এরা এসেছে সিমুলেশনের বাস্তব প্রয়োজন থেকে। Java এগুলো আবিষ্কার করেনি, বরং একটি নিরাপদ, বহনযোগ্য প্যাকেজে সাজিয়ে কোটি প্রোগ্রামারের কাছে পৌঁছে দিয়েছে।

প্র ০২Alan Kay বলেছিলেন OOP-এর আসল ধারণা “messaging”, object নয়। Java-র class-কেন্দ্রিক OOP কি সেই ধারণা থেকে সরে গেছে?

“Object-oriented” শব্দটি প্রচলনের কৃতিত্ব সাধারণত দেওয়া হয় Alan Kay-কে, যিনি ১৯৭০-এর দশকে Xerox PARC-এ Smalltalk-এর মূল নকশাকারদের একজন। মজার ব্যাপার, পরে তিনি বারবার বলেছেন, মানুষ তাঁর ধারণার ভুল অংশে জোর দিয়েছে। ১৯৯৮ সালে একটি mailing list-এ তিনি লিখেছিলেন, অনেক আগে এই বিষয়ের জন্য “objects” শব্দটি বানানোর জন্য তিনি দুঃখিত, কারণ এতে মানুষ ছোট ধারণাটিতে মনোযোগ দেয় — বড় ধারণা হলো “messaging”. আর ২০০৩ সালের একটি ইমেইলে তিনি OOP-কে সংজ্ঞায়িত করেছিলেন মোটামুটি এভাবে: শুধু messaging, নিজের অবস্থা নিজের কাছে রাখা ও লুকানো, আর সব কিছুর চরম late-binding.

Kay-এর ছবি। তাঁর মডেলে প্রতিটি object যেন একটি ছোট, স্বাধীন কম্পিউটার — জীবকোষের মতো, যার ভেতরে কী আছে বাইরে থেকে দেখা যায় না, আর অন্যদের সাথে যোগাযোগ শুধু বার্তা দিয়ে। কোন বার্তার উত্তরে কী কাজ হবে, তা ঠিক হয় একদম শেষ মুহূর্তে, চলার সময় (late binding). Smalltalk-এ চলার সময় একটি class-এ নতুন method যোগ করা যায়, আর যে বার্তা object বোঝে না, তাকেও সে নিজের মতো সামলাতে পারে।

Java-র ছবি। Java-তে কেন্দ্রে আছে class আর static type. rina.deposit(500) লেখার সময়েই compiler পরীক্ষা করে BankAccount-এ deposit(long) আছে কি না; না থাকলে প্রোগ্রাম চলবেই না। Field আর method-এর তালিকা compile-এর সময় স্থির। এতে Kay-এর “চরম late binding” অনেকটা কম — তবে পুরোপুরি নয়: কোন subclass-এর method চলবে, তা Java-তেও চলার সময় ঠিক হয় (পর্ব ২০)। বিনিময়ে Java পেয়েছে আগাম ভুল ধরা, IDE-র নিখুঁত auto-complete আর বড় দলে নিরাপদে কোড বদলানোর ক্ষমতা।

কোন পথ “আসল” OOP? উত্তর নির্ভর করে লক্ষ্যের ওপর। পর্ব ০২-এ আপনি object-কে “বার্তা পাঠানো” হিসেবে ভেবেছিলেন — সেই মানসিকতা Java-তেও খুব কাজের: method ডাকাকে ভাবুন object-কে অনুরোধ করা হিসেবে, তার ভেতরে হাত ঢোকানো হিসেবে নয়। তাই rina.balance -= 500 নয়, rina.withdraw(500) — object নিজে ঠিক করুক অনুরোধ মানবে কি না। Kay-এর ধারণার কিছু অংশ অন্য জায়গায় বেঁচে আছে: Erlang-এর process আর actor model-এ সত্যিকারের বার্তা পাঠানো হয়, আর বড় মাপে microservice-গুলো একে অপরকে বার্তা পাঠিয়েই কাজ করে।

মূল উপলব্ধি: Java class দিয়ে কাঠামো দেয় আর type দিয়ে নিরাপত্তা দেয়; কিন্তু ভালো Java নকশার মন Kay-এর কথাতেই — object-এর ভেতরের অবস্থা লুকানো থাকবে, আর বাইরের সবাই শুধু অনুরোধ (method) পাঠাবে।

প্র ০৩C++ stack-এ object বানাতে দেয়, Java সব object heap-এ রাখে। এই সিদ্ধান্ত কেন, এর দাম কত, আর JVM কীভাবে সেই দাম কমায়?

C++-এ BankAccount acc; লিখলে object তৈরি হয় সরাসরি stack-এ; function শেষ হলে নিজে থেকেই মুছে যায় — খুব দ্রুত, কোনো GC লাগে না। Java-তে একই লেখা শুধু একটি reference বানায়; object আসে new দিয়ে, heap-এ। ১৯৯০-এর দশকে Java-র নকশাকারেরা জেনেশুনে এই পথ নিয়েছিলেন।

কেন heap. প্রধান কারণ নিরাপত্তা। C++-এ stack-এর object-এর ঠিকানা কোথাও রেখে দিয়ে function থেকে ফিরে গেলে সেই ঠিকানা মৃত মেমরি দেখায় — “dangling pointer”, যা ভয়ংকর আর খুঁজে পাওয়া কঠিন bug-এর উৎস। Java-তে প্রতিটি object heap-এ আর GC-র তত্ত্বাবধানে, তাই reference যতক্ষণ আছে object ততক্ষণ বেঁচে থাকে — dangling reference অসম্ভব। দ্বিতীয় কারণ সরলতা: সব object-এর আচরণ এক রকম (সবসময় reference দিয়ে), copy constructor বা object কখন কপি হচ্ছে তা নিয়ে C++-এর জটিল নিয়মগুলো লাগে না।

দাম। প্রতিটি object-এর সাথে একটি header (class-এর তথ্য, hash code, lock-এর অবস্থা), reference দিয়ে পৌঁছানোর একটি অতিরিক্ত ধাপ, আর GC-র কাজ। লাখো ছোট object — যেমন একটি গেমের প্রতিটি বিন্দুর জন্য একটি Point — মেমরি আর cache-এর দিক থেকে ব্যয়বহুল হতে পারে।

JVM কীভাবে দাম কমায়। প্রথমত, heap-এ জায়গা নেওয়া প্রায় বিনামূল্যে: HotSpot প্রতিটি thread-কে heap-এর একটি নিজস্ব টুকরো দেয় (TLAB — thread-local allocation buffer), আর নতুন object মানে প্রায়ই শুধু একটি pointer সামনে সরানো। দ্বিতীয়ত, generational GC: বেশিরভাগ object খুব অল্প সময় বাঁচে, আর তরুণ object-এর এলাকা পরিষ্কার করা সস্তা, কারণ কাজ হয় শুধু বেঁচে থাকা অল্প object নিয়ে। তৃতীয়ত, escape analysis: JIT compiler (HotSpot-এর C2) দেখে কোনো object একটি method-এর বাইরে “পালায়” কি না — কোনো field-এ রাখা হয় না, return হয় না, অন্য thread-এ যায় না। না পালালে সে object-টি বানানোই বাদ দিতে পারে, আর তার field-গুলোকে সাধারণ local মান বা register-এ রাখে (একে বলে scalar replacement); সেই object-এর lock-ও বাদ দিতে পারে। Java 6-এর শেষ দিকের আপডেট থেকে HotSpot-এ এটি ডিফল্টভাবে চালু। খেয়াল করুন, এটা JIT-এর optimization; ভাষার নিয়মে object তখনো heap-এর।

ভবিষ্যৎ। OpenJDK-র Project Valhalla বহু বছর ধরে “value class” নিয়ে কাজ করছে — এমন object, যার identity নেই, তাই JVM তাকে C++-এর মতো সমতলভাবে (array-র ভেতরে সরাসরি) রাখতে পারবে। এটি এখনো চূড়ান্ত Java-র অংশ নয়; কাজ চলছে।

মূল উপলব্ধি: Java সরলতা আর মেমরি-নিরাপত্তার জন্য “সব object heap-এ” বেছে নিয়েছে, আর তার দাম চুকিয়েছে অত্যন্ত দ্রুত allocation, generational GC আর escape analysis দিয়ে। ভাষার মডেল সরল, ভেতরের ইঞ্জিন চতুর।

প্র ০৪Java-তে “সবকিছু class-এর ভেতরে” — একটি সাধারণ function-ও। এই সিদ্ধান্তের লাভ-ক্ষতি কী, আর নতুন Java কীভাবে এই নিয়ম নরম করছে?

C, C++ বা Python-এ দুটি সংখ্যার বড়টি বের করার একটি function যেকোনো জায়গায়, স্বাধীনভাবে লেখা যায়। Java 1.0 থেকে নিয়ম ছিল: প্রতিটি method কোনো না কোনো class-এর ভেতরে। এমনকি “Hello, World” লিখতেও একটি class আর public static void main(String[] args) লাগে — পর্ব ০৪-এ যা দেখেছেন।

লাভ। প্রতিটি নামের একটি নির্দিষ্ট “বাড়ি” আছে: Math.max, Integer.parseInt — কোন function কোথা থেকে এলো, তা নাম দেখেই বোঝা যায়, আর দুটি library-র একই নামের function-এ সংঘর্ষ হয় না। JVM-এর গঠনও সরল থাকে: সব কোড class ফাইলে, class loader সেগুলো লোড করে (পর্ব ০৫)। আর ভাষাটি শেখানোর সময় একটিই মডেল — সব কিছু class আর object.

ক্ষতি। যে কাজের কোনো state নেই, তার জন্যও একটি class বানাতে হয়, তাই জন্ম নিয়েছে “utility class” — Math, Arrays, Collections — যাদের সব method static, আর যাদের object বানানোর কোনো মানে নেই। ২০০৬ সালে Steve Yegge-এর বিখ্যাত ব্যঙ্গ-প্রবন্ধ “Execution in the Kingdom of Nouns” বলেছিল, Java-র রাজ্যে বিশেষ্য (class) রাজা, আর ক্রিয়ারা (কাজ) কোনো বিশেষ্যের সঙ্গী না হয়ে বাইরে বেরোতে পারে না। নতুনদের জন্য প্রথম প্রোগ্রামেই public, static, String[] — না বুঝে মুখস্থ করা কয়েকটি শব্দ।

Java কীভাবে নরম হলো। Java 8-এর lambda (পর্ব ৩৪) “শুধু একটি কাজ” পাঠানো সহজ করে দেয়, যদিও নিচে তা একটি interface-এর object. Java 11 থেকে java Hello.java লিখে এক-ফাইলের প্রোগ্রাম আলাদা compile ছাড়াই চালানো যায়। আর Java 21-এ JEP 445 একটি preview feature হিসেবে আনে “unnamed classes and instance main methods”: চাইলে class ঘোষণা ছাড়াই ফাইলে সরাসরি void main() { System.out.println("Hello"); } লেখা যায় — public, static, String[] args কিছুই লাগে না। Java 21-এ এটি চালাতে --enable-preview দিতে হয়, আর preview মানে পরের সংস্করণে বদলাতে পারে। বাস্তবেও বদলেছে: পরের কয়েকটি সংস্করণে (Java 22–24) নাম আর খুঁটিনাটি বদলে বৈশিষ্ট্যটি আবার preview হয়েছে, আর শেষে Java 25-এ JEP 512 (“compact source files and instance main methods”) নামে স্থায়ী হয়েছে। এই কোর্স Java 21 ধরে চলে, তাই আমরা সাধারণ class Main আর static main-ই ব্যবহার করব। লক্ষ করুন, ভেতরে compiler তখনো একটি class বানায় — নিয়মটি ভাঙেনি, শুধু লুকানো হয়েছে।

মূল উপলব্ধি: “সবকিছু class-এ” Java-কে সুশৃঙ্খল আর নামের সংঘর্ষমুক্ত রেখেছে, কিন্তু ছোট কাজে অপ্রয়োজনীয় আনুষ্ঠানিকতা চাপিয়েছে। আধুনিক Java মূল মডেল না ভেঙে সেই আনুষ্ঠানিকতা কমাচ্ছে — নতুনদের জন্য প্রবেশপথ সহজ, ভেতরের নিয়ম আগের মতোই।

প্র ০৫OOP কি অতিরিক্ত প্রশংসিত? “Data-oriented programming” কী বলে, আর Java নিজেই কেন সেদিকে কিছুটা ঝুঁকছে?

১৯৯০ আর ২০০০-এর দশকে OOP প্রায় একমাত্র “সঠিক” পদ্ধতি হিসেবে শেখানো হতো — সব কিছু class, গভীর inheritance-এর গাছ, প্রতিটি সমস্যার জন্য design pattern. সময়ের সাথে এর সমালোচনাও জোরালো হয়েছে, আর আজকের Java নিজেই কিছুটা ভিন্ন পথ নিচ্ছে।

সমালোচনা। Erlang-এর স্রষ্টা Joe Armstrong-এর একটি উক্তি বিখ্যাত (Peter Seibel-এর Coders at Work বইয়ে): object-ভিত্তিক ভাষার সমস্যা হলো, এরা সাথে একটি গোটা অদৃশ্য পরিবেশ বয়ে বেড়ায় — আপনি চেয়েছিলেন একটি কলা, পেলেন কলা হাতে একটি গরিলা আর পুরো জঙ্গল। অর্থাৎ একটি class নিতে গেলে তার সব নির্ভরতাও নিতে হয়। আরেকটি সমালোচনা আসে গেম ও উচ্চগতির সফটওয়্যার জগৎ থেকে — “data-oriented design” (যেমন Mike Acton-এর CppCon 2014-এর আলোচনা): CPU-র cache ডেটা পাশাপাশি থাকলে দ্রুত চলে, অথচ হাজারো ছোট object heap-এ ছড়িয়ে থাকে আর প্রতিটিতে পৌঁছাতে আলাদা reference — প্রশ্ন ৩-এর দামটিই।

Java-র উত্তর — data-oriented programming. Java-র ভাষা-স্থপতি Brian Goetz ২০২২ সালে InfoQ-তে “Data Oriented Programming in Java” প্রবন্ধে একটি পরিষ্কার পার্থক্য টানেন। কিছু জিনিস সত্যিই object — যার একটি অবস্থা আছে, যা নিজের নিয়ম রক্ষা করে, যেমন আমাদের BankAccount: withdraw নিশ্চিত করে ব্যালান্স ঋণাত্মক হবে না। আবার কিছু জিনিস শুধু ডেটা — একটি লেনদেনের রসিদ, একটি API-র উত্তর, একটি স্থানাঙ্ক। এদের জন্য প্রতিবার getter-setter-ভরা class বানানো অপ্রয়োজনীয় আনুষ্ঠানিকতা। তাঁর প্রস্তাব: ডেটাকে ডেটা হিসেবে মডেল করুন — অপরিবর্তনীয় (immutable), স্বচ্ছ, আর সম্ভাব্য রূপগুলো সীমিত। এর উপকরণ Java সাম্প্রতিক সংস্করণগুলোতে এনেছে: record (Java 16-এ স্থায়ী, পর্ব ২৫), sealed class (Java 17, পর্ব ২৬), আর switch-এ pattern matching (Java 21-এ স্থায়ী)।

তাহলে OOP কি ভুল? না — অতি-ব্যবহার ভুল। যেখানে অবস্থা আর তার নিয়ম একসাথে রাখা দরকার (অ্যাকাউন্ট, ঝুড়ি, গেমের চরিত্র), সেখানে object এখনো সেরা হাতিয়ার: encapsulation ভুল অবস্থা অসম্ভব করে দেয়। যেখানে ডেটা শুধু এক জায়গা থেকে আরেক জায়গায় যায় আর রূপান্তরিত হয়, সেখানে record আর ছোট pure function বেশি সরল। আধুনিক Java প্রোগ্রামার দুটিই ব্যবহার করেন, প্রশ্ন করে: “এটা কি নিজের নিয়ম রক্ষাকারী একটি জিনিস, নাকি শুধু তথ্যের একটি প্যাকেট?”

মূল উপলব্ধি: OOP একটি শক্তিশালী হাতিয়ার, ধর্ম নয়। এই মডিউলে class ও object গভীরভাবে শিখুন, কারণ Java-র পুরো জগৎ তার ওপর দাঁড়িয়ে; তারপর মডিউল ৪-এ record আর pattern matching শিখে বেছে নিন কোথায় কোনটি মানায়।

অনুশীলন

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

  1. আউটপুট অনুমান করুন: অংশ ৪-এর প্রোগ্রামে BankAccount karim = new BankAccount(); লাইনটি বদলে লিখুন BankAccount karim = rina;. বাকি সব একই। না চালিয়ে তিনটি লাইনের আউটপুট বলুন, তারপর চালিয়ে মিলিয়ে নিন।

    এখন object একটাই, দুটি নাম। rina.owner = "Rina", তারপর karim.owner = "Karim" — একই object-এর owner বদলে হলো “Karim”. জমা: ১৫০০ + ৩০০ = ১৮০০, একই balance-এ। rina.withdraw(500) → true, balance ১৩০০। karim.withdraw(500) → true (একই object, ১৩০০ আছে), balance ৮০০। আউটপুট: Rina -500: true, Karim -500: true, Karim 800, Karim 800. একটি শব্দ বদলে দুটি অ্যাকাউন্ট একটিতে মিশে গেল — aliasing-এর বিপদ ঠিক এটাই।

  2. NPE-এর মেসেজ পড়ুন: একটি প্রোগ্রাম ভেঙে পড়ে এই মেসেজে: Cannot invoke "String.length()" because "acc.owner" is null. কোডে আছে BankAccount acc = new BankAccount(); আর তার পরের লাইনে acc.owner.length(). কী null, কেন null, আর দুটি সমাধান কী হতে পারে?

    acc null নয় — সে একটি আসল object দেখায়। null হলো তার owner field, কারণ কেউ তাতে মান দেয়নি, আর reference টাইপের field-এর default মান null. length() ডাকতে গিয়ে তীর ধরে যাওয়ার জায়গা নেই, তাই NPE. সমাধান ১: ব্যবহারের আগে মান দিন (acc.owner = "Rina";) — আরও ভালো, পর্ব ১৬-এর constructor দিয়ে object তৈরির সময়েই নাম বাধ্যতামূলক করুন। সমাধান ২: যদি owner সত্যিই না থাকতে পারে, ব্যবহারের আগে পরীক্ষা করুন: if (acc.owner != null). মেসেজটি কীভাবে সরাসরি সমস্যার field দেখিয়ে দিল, খেয়াল করুন — Java 14-এর আগে এটা পাওয়া যেত না।

  3. GC-র যোগ্য কয়টি? নিচের লাইনগুলো চলার পরে কয়টি Book object garbage collection-এর যোগ্য? Book p = new Book(); Book q = new Book(); Book r = p; p = q; q = null;

    একটিও না। তীরগুলো হাতে আঁকুন: শুরুতে p → #1, q → #2. r = p: r → #1. p = q: p → #2. q = null: q-এর তীর নেই। শেষে #1-কে দেখায় r, #2-কে দেখায় p — দুটিই reachable. অনেকে ভাবেন “p তো বদলে গেছে, তাই #1 গেল”, কিন্তু r তখনো #1 ধরে আছে। একটি object যোগ্য হয় কেবল তখন, যখন তার দিকে সব পথ বন্ধ।

  4. নিজে লিখুন — Student: অংশ ২-এর Student class দিয়ে main-এ দুজন শিক্ষার্থী বানান, প্রত্যেকের marks-এ তিনটি নম্বর দিন (array, পর্ব ১৩), আর দুজনের নাম ও গড় ছাপুন। তারপর তৃতীয় একজন বানান যার marks-এ কিছুই দেননি — average() কী দেয়, আর কেন ভেঙে পড়ে না?
    Student sadia = new Student();
    sadia.name = "Sadia";
    sadia.marks = new int[] {78, 85, 92};
    Student tanvir = new Student();
    tanvir.name = "Tanvir";
    tanvir.marks = new int[] {60, 71, 65};
    Student nadia = new Student();         // marks দেওয়া হয়নি
    System.out.println(sadia.name + " " + sadia.average());    // Sadia 85.0
    System.out.println(tanvir.name + " " + tanvir.average());  // Tanvir 65.33333333333333
    System.out.println(nadia.average());                       // 0.0

    একই average(), তিনটি object, তিনটি আলাদা ফল — প্রতিটি ডাকে this আলাদা। nadia-র marks হলো default মান null; method-এর প্রথম লাইন marks == null পরীক্ষা করে 0.0 ফেরত দেয়, তাই NPE হয় না। ওই পরীক্ষা না থাকলে marks.length-এ NPE আসত, মেসেজে লেখা থাকত Cannot read the array length because "this.marks" is null (OpenJDK 21-এ যাচাই করা)। লক্ষ করুন, instance method-এর ভেতর থেকে field-এর বেলায় মেসেজে this.marks আসে — -g ছাড়াও, কারণ field-এর নাম সবসময় class ফাইলে থাকে।

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

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

ক্রমJava-তে BankAccount acc = new BankAccount(); চলার সময় ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 📦 JVM নিশ্চিত হয় BankAccount class লোড করা আছে (প্রথমবার হলে লোড করে)
  2. 🧱 heap-এ object-এর জন্য জায়গা নেওয়া হয়
  3. 0️⃣ প্রতিটি field পায় default মান (0, false, null)
  4. ⚙️ constructor-এর কোড চলে
  5. ➡️ নতুন object-এর reference acc ভ্যারিয়েবলে বসে

Java-তে class আর object সম্পর্কে নিচের কোন কথাটি সঠিক?

  • Object হলো নকশা, class হলো সেই নকশা থেকে তৈরি জিনিস
  • Class একটি নকশা; একটি class থেকে অনেক object বানানো যায়, প্রতিটির নিজের state
  • একটি class থেকে কেবল একটি object বানানো যায়
  • প্রতিটি object-এ method-গুলোর আলাদা কপি থাকে, কিন্তু field সবার এক

class Item { int qty; String name; boolean sold; } — new Item() দিয়ে তৈরি object-এ qty, name আর sold-এর মান কী হবে?

  • অনির্ধারিত আবর্জনা মান
  • compile error, কারণ মান দেওয়া হয়নি
  • 0, null, false
  • 0, "" (খালি String), false

Account a = new Account(); Account b = a; b.balance = 500; System.out.println(a.balance); — কী ছাপবে (balance একটি int field, শুরুতে 0)?

  • 500
  • 0
  • compile error
  • NullPointerException

Book x = new Book(); Book y = x; Book z = new Book(); Book w; — এই চারটি লাইনে মোট কয়টি Book object তৈরি হয়েছে?

  • ১টি
  • ২টি
  • ৩টি
  • ৪টি

Book p = new Book(); Book q = p; p = null; — এর পরে Book object-টির কী অবস্থা?

  • সাথে সাথে মুছে যায়
  • garbage collection-এর যোগ্য হয়ে যায়
  • q দিয়ে ব্যবহার করলে NullPointerException হবে
  • বেঁচে থাকে, কারণ q এখনো তাকে দেখায়

নিজের toString() লেখা হয়নি এমন একটি Book object-কে System.out.println দিয়ে ছাপলে সাধারণত কী আসে?

  • সব field-এর নাম আর মান
  • compile error
  • class-এর নাম, @ চিহ্ন আর একটি hexadecimal hash code
  • object-এর আসল মেমরি ঠিকানা

একটি class-এ long balance; instance field আছে। static void main-এর ভেতরে সরাসরি balance = 10; লিখলে কী হবে?

  • compile error — static context থেকে instance field পড়া যায় না
  • প্রথম তৈরি হওয়া object-এর balance বদলাবে
  • চলার সময় NullPointerException
  • সব object-এর balance 10 হয়ে যাবে

শব্দকোষ

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

মনে রাখুন

  1. Class = নকশা, object = আসল জিনিস। Class-এ field (যা জানে) আর method (যা করে); new দিয়ে object, প্রতিটির নিজের state; dot দিয়ে ব্যবহার।
  2. new = heap-এ জায়গা → field-এ default মান (0, false, null) → constructor → reference ফেরত। ভ্যারিয়েবলে object নয়, তার reference.
  3. Instance method-এ লুকানো this থাকে — যে object-এর ওপর ডাকা হয়েছে; static method-এ কোনো this নেই।
  4. b = a = একই object-এ দ্বিতীয় তীর (aliasing); == দেখে একই object কি না। null-এর ওপর dot চালালে NullPointerException — মেসেজ পড়ুন, সে বলে দেয় কোনটি null.
  5. কোনো পথে পৌঁছানো না গেলে object GC-র যোগ্য, কখন মুছবে JVM ঠিক করে। বাস্তবে এক ফাইলে এক public class; নকশা আঁকুন UML-এ, class খুঁজুন বিশেষ্য-ক্রিয়া দিয়ে।

পরের পর্বে: এই পর্বে প্রতিটি object তৈরি হয়েছে খালি অবস্থায়, আর পরে লাইন ধরে ধরে field-এ মান বসাতে হয়েছে — ভুলে গেলেই null. পর্ব ১৬-এ constructor: object জন্মের মুহূর্তেই সঠিক মান নিয়ে আসবে, আর this keyword-এর পুরো ব্যবহার দেখবেন।

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

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