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

nested, inner আর anonymous class — class-এর ভেতরে class

Nested, Inner, Local and Anonymous Classes — Outer.this, Captured Variables, the Road to Lambdas and Choosing the Right Kind

Taught in Bangla, this lesson covers classes inside classes: static nested classes for builders and nodes, inner classes and the hidden Outer.this reference with its memory risk, local classes and the effectively final rule, anonymous classes and converting them to lambdas, the compiled class files and nestmates, and how to choose.

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

  • কেন একটি class আরেকটির ভেতরে থাকে, আর Java-র চার ধরনের nested class কীভাবে আলাদা
  • static nested class দিয়ে Builder, Map.Entry-ধাঁচের সহকারী আর লিংকড লিস্টের Node
  • inner class-এর লুকানো Outer.this, outer.new Inner() আর মেমরি আটকে থাকার ঝুঁকি
  • local ও anonymous class, ভ্যারিয়েবল কপি করার নিয়ম, আর anonymous class থেকে lambda-তে রূপান্তর
  • পর্দার আড়ালে: Outer$1.class, synthetic accessor, Java 11-এর nestmates — আর কোনটা কখন বেছে নেবেন

১ · class-এর ভেতরে class — কেন?

এখন পর্যন্ত আপনার প্রতিটি class ছিল ফাইলের সবচেয়ে বাইরের স্তরে — Shape, Circle, Main সবাই পাশাপাশি। কিন্তু কিছু class আছে যাদের আলাদা কোনো জীবন নেই। একটি গানের তালিকা (Playlist) ভাবুন। তালিকার গানগুলো একে একে ঘুরে দেখার জন্য একটি ছোট সহকারী object লাগে, যে মনে রাখে “এখন কত নম্বর গানে আছি” — পর্ব ১১-এ দেখা iterator. এই সহকারী শুধু ওই একটি তালিকার জন্যই অর্থবহ। প্রোগ্রামের অন্য কোথাও কেউ একে আলাদাভাবে ব্যবহার করবে না।

এমন সহকারীকে যদি বাইরের স্তরে রাখেন, তিনটি সমস্যা হয়। প্রথমত, package-এ অপ্রয়োজনীয় নাম বাড়ে। দ্বিতীয়ত, সহকারী তালিকার private array পড়তে পারে না, তাই তালিকাকে নিজের ভেতরের জিনিস খুলে দিতে হয়। তৃতীয়ত, যে কোড পড়ছে সে বুঝতে পারে না যে এই দুটি class আসলে একটি জোড়া। সমাধান: সহকারীকে মূল class-এর ভেতরে লিখুন। এমন class-কে বলে nested class, আর যার ভেতরে থাকে তাকে বলা হয় outer (বাইরের) class.

একটি গার্মেন্টস কারখানার কাটিং সেকশন ভাবুন। সেখানে একজন মাপ নেওয়ার সহকারী আছেন, যিনি শুধু ওই সেকশনের টেবিল আর ওই সেকশনের ছাঁচ নিয়ে কাজ করেন। তাঁকে কারখানার মূল তালিকায় আলাদা বিভাগ হিসেবে দেখানোর কোনো মানে নেই — তিনি কাটিং সেকশনের ভেতরের মানুষ, আর সেকশনের তালাবদ্ধ আলমারিও খুলতে পারেন। আবার কারখানার নিরাপত্তারক্ষী কোনো নির্দিষ্ট সেকশনের নন; তিনি পুরো কারখানার নাম নিয়ে আছেন, কিন্তু কোনো একটি টেবিলের সাথে বাঁধা নন। প্রথমজন inner class-এর মতো, দ্বিতীয়জন static nested class-এর মতো।

Java-তে class-এর ভেতরে class রাখার চারটি উপায় আছে। তাদের পার্থক্য বুঝতে শুধু দুটি প্রশ্ন করুন: এর object বানাতে কি বাইরের class-এর একটি object লাগে? আর class-টি কোথায় লেখা — class-এর সদস্য হিসেবে, নাকি একটি method-এর ভেতরে?

static nested class

class-এর সদস্য, সামনে static. বাইরের object লাগে না — শুধু নামটা বাইরের class-এর ভেতরে। Bank.Transaction.

“একই পরিবারের নাম, আলাদা সংসার”

inner class

class-এর সদস্য, static নেই। প্রতিটি object একটি বাইরের object-এর সাথে বাঁধা, তার field সরাসরি পড়ে।

“আমি এই নির্দিষ্ট তালিকার সহকারী”

local class

একটি method-এর ভেতরে নাম দিয়ে লেখা class. শুধু সেই method-এর ভেতরে দেখা যায়।

“এই কাজটুকুর জন্যই জন্ম”

anonymous class

নামহীন, ঘোষণা আর new একসাথে। একবারের জন্য একটি interface বা class-এর রূপ।

“নাম লাগবে না, একবারই লাগবে”
ধরনকোথায় লেখাবাইরের object লাগে?নাম আছে?সাধারণ ব্যবহার
static nestedclass-এর সদস্যনাহ্যাঁBuilder, Map.Entry, Node
innerclass-এর সদস্যহ্যাঁ (Outer.this)হ্যাঁiterator, outer-এর state-এর ওপর দৃশ্য
localmethod/block-এর ভেতরেinstance method-এ লেখা হলে হ্যাঁহ্যাঁএক method-এর ভেতরের সহকারী
anonymousএকটি expression-এর ভেতরেinstance context-এ হ্যাঁনাlistener, পুরোনো ধাঁচের Comparator

শেষ তিনটিকে একসাথে বলা হয় inner class — Java-র নিয়মে যে nested class static নয়, সেটাই inner. তবে দৈনন্দিন কথায় “inner class” বললে সাধারণত দ্বিতীয় সারির সদস্য-class-টিকেই বোঝায়; এই পর্বেও তাই।

২ · static nested class — বাইরের object ছাড়াই

পর্ব ১৬-এ Builder pattern দেখেছেন, আর পর্ব ১৮-এ static. দুটি মিলিয়েই সবচেয়ে পরিচিত static nested class. একটি ব্যাংকের লেনদেন (Transaction) ভাবুন: কে পাঠাচ্ছে, কাকে, কত টাকা। লেনদেন বানাতে কোনো নির্দিষ্ট Bank object লাগে না, কিন্তু নামের দিক থেকে এটা ব্যাংকেরই জিনিস। আর লেনদেন বানানোর সহকারী Builder শুধু লেনদেনের জন্যই অর্থবহ:

class Bank {
    private final List<Transaction> log = new ArrayList<>();

    static class Transaction {                      // static nested
        private final String from, to;
        private final long amount;
        private Transaction(Builder b) {            // বাইরে থেকে new নিষেধ
            from = b.from; to = b.to; amount = b.amount;
        }
        public String toString() { return from + " -> " + to + ": " + amount + " Tk"; }

        static class Builder {                      // nested-এর ভেতরে nested
            private String from, to;
            private long amount;
            Builder from(String f) { from = f; return this; }
            Builder to(String t)   { to = t; return this; }
            Builder amount(long a) { amount = a; return this; }
            Transaction build() {
                if (amount <= 0) throw new IllegalArgumentException("amount must be positive");
                return new Transaction(this);       // private constructor — ভেতর থেকে চলে
            }
        }
    }
    void record(Transaction t) { log.add(t); }
}

// ব্যবহার:
Bank.Transaction t = new Bank.Transaction.Builder()
        .from("Rahim").to("Karim").amount(5000).build();
System.out.println(t);        // Rahim -> Karim: 5000 Tk

খেয়াল করার মতো চারটি জিনিস:

  • নাম বাইরের class দিয়ে শুরু। বাইরে থেকে লিখতে হয় Bank.Transaction, আর Builder হলো Bank.Transaction.Builder. নামই বলে দেয় কে কার সহকারী।
  • object বানাতে Bank object লাগেনি। new Bank.Transaction.Builder() — কোথাও new Bank() নেই। static nested class আসলে একটি সাধারণ top-level class-এর মতোই, শুধু ঠিকানাটা বাইরের class-এর ভেতরে।
  • একে অন্যের private জিনিস দেখতে পায়। Builder ডাকছে Transaction-এর private constructor, আর Transaction পড়ছে Builder-এর private field. একই top-level class-এর ভেতরের সবাই একে অন্যের private সদস্য ব্যবহার করতে পারে। ফলে বাইরের কেউ Builder এড়িয়ে যাচাই ছাড়া লেনদেন বানাতে পারে না।
  • বাইরের class-এর instance field সরাসরি পড়া যায় না। Transaction-এর ভেতর থেকে log লিখলে compiler আপত্তি করবে, কারণ কোন Bank-এর log? কোনো নির্দিষ্ট ব্যাংক object তো এর সাথে বাঁধা নেই। বাইরের class-এর static সদস্য অবশ্য সরাসরি পড়া যায়।

JDK-তে static nested class সবখানে

Map-এর প্রতিটি key-value জোড়া একটি Map.Entry — এটি Map interface-এর ভেতরে ঘোষিত nested interface (interface-এর ভেতরের সব সদস্য-টাইপ নিজে থেকেই static). java.util.LinkedList-এর ভেতরে আছে private static class Node, যেটি একটি মান আর আগের-পরের node-এর reference রাখে। বাইরের কেউ Node-এর কথা জানে না; লিস্টের ব্যবহারকারী দেখে শুধু add, get, remove. এভাবে nested class বাস্তবায়নের খুঁটিনাটি লুকিয়ে রাখে — পর্ব ১৭-এর encapsulation, এবার class-এর স্তরে। পর্ব ৩০-এ লিংকড লিস্ট বিস্তারিত দেখবেন।

মূল ধারণা

static nested class-এর static মানে “আমার object বাইরের কোনো object-এর সাথে বাঁধা নয়।” এর মানে এই নয় যে class-টির object একটিই হবে, বা তার field static. Bank.Transaction-এর হাজারটা object থাকতে পারে, প্রত্যেকের নিজস্ব from, to, amount.

৩ · inner class — যে object বাইরের object-কে ধরে রাখে

এবার উল্টো পরিস্থিতি। Playlist-এর iterator-কে জানতে হবে কোন তালিকার গান সে ঘুরছে। “রোড ট্রিপ” তালিকার iterator আর “পড়ার সময়” তালিকার iterator আলাদা গান দেবে। অর্থাৎ iterator object একটি নির্দিষ্ট Playlist object-এর সাথে বাঁধা। ঠিক এই কাজের জন্য inner class: static ছাড়া সদস্য-class.

inner class-এর প্রতিটি object-এর ভেতরে compiler একটি লুকানো field রাখে, যেটি তার বাইরের object-কে দেখায়। একে বলে enclosing instance, আর কোডে তাকে লেখা হয় Outer.this (এখানে Playlist.this). পর্ব ১৬-এর this মানে “এই object”; Playlist.this মানে “যে Playlist-এর ভেতরে আমি বাস করি।” inner class-এর ভেতরে songs লিখলে compiler নিজেই সেটাকে Playlist.this.songs বানিয়ে নেয় — তাই outer-এর private field-ও সরাসরি পড়া যায়।

এখন দুই ধরনের nested class-এর object মেমরিতে কেমন দেখায়, ধাপে ধাপে দেখুন। বিশেষ করে শেষ ধাপটি খেয়াল করুন:

1class Playlist {2    private final String[] songs;3    class SongIterator {        // inner4        int pos = 0;5        String next() { return songs[pos++]; }6    }7    static class Stats { int plays; }   // static nested8}9Playlist.Stats st = new Playlist.Stats();10Playlist road = new Playlist("Tumi Robe Nirobe", "Ekla Cholo Re");11Playlist.SongIterator it = road.new SongIterator();12it.next();13road = null;
stack-এর ভ্যারিয়েবল আর heap-এর object
st
road
it
it-এর লুকানো this$0
  1. static nested-এর object — একা। new Playlist.Stats() লিখতে কোনো Playlist object লাগেনি। heap-এ যে Stats object তৈরি হলো, তার ভেতরে শুধু তার নিজের field plays — বাইরের কারও দিকে কোনো তীর নেই।
  2. বাইরের object তৈরি। road একটি Playlist object (এখানে নাম দিলাম P1) দেখায়, যার ভেতরে দুটি গানের array.
  3. inner object — বাইরের object-এর সাথে বাঁধা। সিনট্যাক্সটা অদ্ভুত লাগতে পারে: road.new SongIterator(). এর মানে “road-এর জন্য একটি iterator বানাও।” compiler নতুন object-এর ভেতরে একটি লুকানো field রাখে — JDK-র javac একে নাম দেয় this$0 — আর সেখানে road-এর reference বসিয়ে দেয়। কোডে আপনি একে লেখেন Playlist.this.
  4. outer-এর field পড়া। next()-এর ভেতরে songs লেখা, কিন্তু SongIterator-এর নিজের কোনো songs নেই। compiler একে পড়ে this$0.songs হিসেবে — লুকানো তীর ধরে P1-এর array থেকে প্রথম গান। pos হলো ১।
  5. লুকানো তীরের দাম। road = null করে দিলেন, ভাবলেন তালিকাটা এখন garbage. কিন্তু it-এর ভেতরের this$0 এখনো P1-কে দেখাচ্ছে। যতক্ষণ it বেঁচে আছে, P1 আর তার পুরো গানের array-ও বেঁচে থাকবে। এক তালিকার জন্য সমস্যা নয়; কিন্তু inner object যদি কোনো দীর্ঘজীবী জায়গায় (static list, cache, অন্য thread) জমা হয়, বড় বড় outer object ছাড়া পায় না।

পুরো উদাহরণটি — একটি Playlist, যা Iterable হওয়ায় for-each লুপে চলে, আর যার iterator একটি private inner class:

import java.util.Iterator;

class Playlist implements Iterable<String> {
    private final String name;
    private final String[] songs;
    Playlist(String name, String... songs) { this.name = name; this.songs = songs; }

    // inner class — প্রতিটি iterator একটি নির্দিষ্ট Playlist-এর সাথে বাঁধা
    private class SongIterator implements Iterator<String> {
        private int pos = 0;
        public boolean hasNext() { return pos < songs.length; }  // outer-এর private field
        public String next() {
            return Playlist.this.name + " #" + (pos + 1) + ": " + songs[pos++];
        }
    }
    public Iterator<String> iterator() { return new SongIterator(); }
}

class Main {
    public static void main(String[] args) {
        Playlist road = new Playlist("Road Trip", "Tumi Robe Nirobe", "Ekla Cholo Re");
        for (String s : road) System.out.println(s);
        System.out.println(road.iterator().getClass().getName());
    }
}

আউটপুট: Road Trip #1: Tumi Robe Nirobe, Road Trip #2: Ekla Cholo Re, তারপর Playlist$SongIterator. iterator() outer-এর একটি instance method, তাই সেখানে শুধু new SongIterator() লিখলেই চলে — compiler নিজে this-কে বাইরের object হিসেবে বসায় (আসলে this.new SongIterator())। SongIterator private, তাই বাইরের কোড জানে শুধু Iterator<String> চুক্তি — পর্ব ২১-এর “চুক্তির ওপর নির্ভর করুন”। শেষ লাইনের নামটি খেয়াল করুন: nested class-এর আসল নাম বাইরের$ভেতরের, অংশ ৭-এ এর কারণ দেখবেন। (Iterable<String>-এর কৌণিক বন্ধনী generics, পর্ব ২৮-এ। নিজের প্রজেক্টে main class সাধারণত public class Main, এখানে অনলাইন runner-এর জন্য public নেই।)

বাইরে থেকে inner object বানানো, আর static context-এর ফাঁদ

outer-এর instance method-এর ভেতরে this আছে, তাই সেখানে সমস্যা নেই। কিন্তু static method-এ — যেমন main-এ — কোনো this নেই। সেখানে সরাসরি inner object বানাতে চাইলে:

class Main {
    class Inner { }
    public static void main(String[] args) {
        Inner in = new Inner();
    }
}
Main.java:4: error: non-static variable this cannot be referenced from a static context
        Inner in = new Inner();
                   ^
1 error

বার্তাটা প্রথমে বিভ্রান্তিকর — আপনি তো কোথাও this লেখেননি! কিন্তু compiler new Inner()-কে পড়ে this.new Inner() হিসেবে, কারণ inner object-এর একটি বাইরের object লাগবেই। static main-এ সেই this নেই। দুটি সমাধান: হয় একটি outer object বানিয়ে new Main().new Inner() লিখুন, নয়তো — এবং প্রায়ই এটাই আসল উত্তর — Inner-কে static করুন, যদি সে বাইরের object আদৌ ব্যবহার না করে। নতুনরা এই error দেখে প্রায়ই সব কিছুতে static বসিয়ে দেন; প্রশ্নটা হওয়া উচিত “এই class-এর কি সত্যিই বাইরের object লাগে?”

নাম মিলে গেলে — Outer.this দিয়ে আলাদা করা

class Outer {
    private int secret = 42;
    class Inner {
        int secret = 7;
        void show() {
            System.out.println(secret + " " + this.secret + " " + Outer.this.secret);
        }
    }
}
// new Outer().new Inner().show();   →   7 7 42

inner class-এর নিজের field বাইরের একই নামের field-কে ঢেকে দেয় (পর্ব ১৬-এর shadowing). secret আর this.secret দুটিই inner-এর ৭; বাইরেরটা পেতে লিখতে হয় Outer.this.secret.

মেমরি আটকে থাকার ঝুঁকি। inner object বাইরের object-কে ধরে রাখে — এটা নিয়মের অংশ, ভুল নয়। ভুল হয় তখন, যখন inner object বাইরের object-এর চেয়ে অনেক বেশি দিন বাঁচে: একটি static list-এ জমা থাকে, কোনো ব্যাকগ্রাউন্ড thread-এ অপেক্ষা করে, বা কোনো event-ব্যবস্থায় নিবন্ধিত থাকে। তখন বাইরের বড় object আর তার সব ডেটা garbage collection থেকে বেঁচে যায় — একটি memory leak. JDK 21-এর javac দিয়ে পরীক্ষা করলে দেখা যায়, inner class যদি বাইরের object একেবারেই ব্যবহার না করে, তাহলে compiler লুকানো field-টি আর বানায় না। তবে এটা compiler-এর একটি optimization, ভাষার কোনো নিশ্চয়তা নয় — তাই এর ওপর ভরসা না করে সরাসরি static লেখাই পরিষ্কার নকশা।

৪ · local class আর effectively final-এর নিয়ম

কখনো একটি সহকারী class শুধু একটি method-এর ভেতরেই লাগে। তখন তাকে সেই method-এর ভেতরেই লেখা যায় — এটা local class. local variable-এর মতোই এর scope শুধু সেই block; method-এর বাইরে এর নাম কেউ জানে না।

static String receipt(String shop, int[] prices) {
    class Line {                                  // local class
        final int no, price;
        Line(int no, int price) { this.no = no; this.price = price; }
        String show() { return shop + " item " + no + ": " + price + " Tk"; }  // shop পড়ছে
    }
    StringBuilder sb = new StringBuilder();
    for (int i = 0; i < prices.length; i++) sb.append(new Line(i + 1, prices[i]).show()).append('\n');
    return sb.toString();
}

Line-এর ভেতর থেকে shop পড়া হচ্ছে — shop হলো receipt method-এর parameter, অর্থাৎ একটি local variable. এখানেই একটি গুরুত্বপূর্ণ সমস্যা লুকিয়ে আছে। local variable থাকে method-এর stack frame-এ (পর্ব ১২), আর method শেষ হলেই সেই frame মুছে যায়। কিন্তু local বা anonymous class-এর object method শেষ হওয়ার পরও বেঁচে থাকতে পারে — যেমন সেটা ফেরত দেওয়া হলে। তখন সে মুছে যাওয়া ভ্যারিয়েবল পড়বে কীভাবে?

Java-র উত্তর: কপি রাখো। object তৈরির মুহূর্তে compiler ভ্যারিয়েবলের মানটি object-এর ভেতরের একটি লুকানো field-এ কপি করে দেয়। একে বলে variable capture. কপির ধারণাটা নিচের stepper-এ দেখুন:

1static Runnable greeter(String name) {2    String msg = "Hello, " + name;3    return new Runnable() {4        public void run() {5            System.out.println(msg);6        }7    };8}9Runnable r = greeter("Rahim");10r.run();11// যদি লাইন ৩-এর পরে লিখতেন: msg = "Bye";
মেমরিতে কী আছে
greeter-এর frame: msg
Main$1 object: val$msg
r
  1. method চলছে। greeter("Rahim") ডাকায় stack-এ একটি frame তৈরি হলো, যেখানে name আর msg থাকে। এরা local variable — method শেষ হলে মুছে যাবে।
  2. anonymous object তৈরি — সাথে কপি। new Runnable() {...} চলার মুহূর্তে compiler-এর বানানো constructor msg-এর মান object-এর ভেতরের একটি লুকানো field-এ কপি করে। JDK 21-এ reflection দিয়ে দেখলে এর নাম পাওয়া যায় val$msg. String হলে কপি হয় reference-টি, String object নিজে নয়।
  3. method শেষ, frame উধাও। greeter object-টি ফেরত দিল আর তার frame মুছে গেল। আসল msg ভ্যারিয়েবল আর কোথাও নেই। কিন্তু object-এর কাছে তার কপি আছে, তাই কোনো সমস্যা নেই।
  4. পরে চালানো। r.run() — msg লেখা থাকলেও আসলে পড়া হচ্ছে val$msg, অর্থাৎ কপিটি। ছাপা হলো Hello, Rahim.
  5. বদলানো গেলে কী হতো? ধরুন object তৈরির পর method-এর ভেতরে msg = "Bye" লেখা যেত। তখন method দেখত “Bye”, object দেখত “Hello, Rahim” — একই নামের দুটি ভ্যারিয়েবল দুই রকম মান, আর প্রোগ্রামার ভাবতেন দুটি একই। এই বিভ্রান্তি ঠেকাতে Java নিয়ম করেছে: ধরা ভ্যারিয়েবল কখনো বদলানো যাবে না। বদলালে compile-ই হবে না।

এই নিয়মের নাম effectively final: ভ্যারিয়েবলের সামনে final লেখা না থাকলেও, ঘোষণার পর কোথাও নতুন মান বসানো না হলে সে “কার্যত final”. Java 8-এর আগে final শব্দটি নিজে লিখতে হতো; Java 8 থেকে compiler নিজেই বুঝে নেয়। নিয়ম ভাঙলে আসল error দেখতে কেমন:

class Main {
    public static void main(String[] args) {
        int count = 0;
        Runnable r = new Runnable() {
            public void run() { System.out.println(count); }
        };
        count++;
        r.run();
    }
}
Main.java:5: error: local variables referenced from an inner class must be final or effectively final
            public void run() { System.out.println(count); }
                                                   ^
1 error

লক্ষ করুন, error দেখাচ্ছে লাইন ৫-এ, যেখানে count পড়া হচ্ছে — যদিও “দোষ” লাইন ৭-এর count++-এর। কারণ count++ একা বৈধ; সমস্যা হলো একটি বদলানো ভ্যারিয়েবলকে class-এর ভেতর থেকে ধরা। count++ লাইনটা মুছে দিলে error চলে যাবে।

সত্যিই যদি বাইরের একটি গণনা বদলাতে হয়? তাহলে বদলানো যায় এমন একটি object ধরুন — ভ্যারিয়েবল নিজে না বদলে তার ভেতরের অবস্থা বদলান। যেমন java.util.concurrent.atomic.AtomicInteger বা একটি field. কিন্তু আগে ভাবুন: প্রায়ই এর চেয়ে পরিষ্কার নকশা আছে, যেমন anonymous class-এর নিজের field-এ গণনা রাখা — পরের অংশের বোতামের উদাহরণে ঠিক এটাই করা হয়েছে।

Java 16 থেকে: local record, enum আর interface

Java 16-এ record স্থায়ী হওয়ার সাথে method-এর ভেতরে record, enum আর interface ঘোষণা করাও বৈধ হয়েছে। এগুলো নিজে থেকেই static — বাইরের object বা local variable ধরে না। একটি method-এর ভেতরে মধ্যবর্তী ফল গুছিয়ে রাখতে local record খুব কাজের:

static void topScorer(String[] names, int[] runs) {
    record Score(String name, int runs) { }      // শুধু এই method-এর জন্য
    Score best = new Score(names[0], runs[0]);
    for (int i = 1; i < names.length; i++)
        if (runs[i] > best.runs()) best = new Score(names[i], runs[i]);
    System.out.println(best);                    // Score[name=..., runs=...]
}

৫ · anonymous class — নাম ছাড়া, একবারের জন্য

অনেক সময় একটি interface-এর implementation একবারই লাগে, একটি জায়গাতেই। তার জন্য আলাদা নাম দিয়ে class লেখা, তারপর new করা — বাড়তি ঝামেলা। anonymous class দুটি কাজ এক ধাপে করে: class ঘোষণা আর object তৈরি।

Runnable task = new Runnable() {     // "Runnable implement করে এমন একটি নামহীন class-এর object"
    public void run() {
        System.out.println("Backup started");
    }
};                                   // শেষে সেমিকোলন — এটা একটি statement

new Runnable() দেখে ভাববেন না যে interface-এর object বানানো হচ্ছে — সেটা অসম্ভব (পর্ব ২১)। পরের { ... } অংশটিই একটি নতুন, নামহীন class-এর শরীর, যেটি Runnable implement করে। new সেই নামহীন class-এর object বানায়। একইভাবে একটি class বা abstract class-এর নামও বসানো যায় — তখন anonymous class হয় তার subclass, আর বন্ধনীর ভেতরের argument যায় parent-এর constructor-এ।

যেখানে anonymous class জন্মেছিল — event listener

পুরোনো Java GUI লাইব্রেরি (AWT, Swing) আর পুরোনো Android কোডে প্রতিটি বোতামের জন্য একটি event listener লাগত — “এই বোতাম চাপলে এটা করো।” প্রতিটি বোতামের কাজ আলাদা, প্রতিটি একবারের। Swing-এ লেখা হতো button.addActionListener(new ActionListener() { ... }), Android-এ setOnClickListener(new View.OnClickListener() { ... }). নিচের প্রোগ্রামে আমরা নিজেরাই একটি ছোট Button বানিয়ে সেই ধাঁচটি অনুকরণ করেছি, তারপর Comparator দিয়ে সাজানো — প্রথমে anonymous class-এ, তারপর lambda-তে:

import java.util.*;
interface ClickListener { void onClick(String label); }
class Button {
    private final String label;
    private ClickListener listener;
    Button(String label) { this.label = label; }
    void setOnClick(ClickListener l) { listener = l; }
    void click() { listener.onClick(label); }
}
class Main {
    public static void main(String[] args) {
        Button pay = new Button("Pay");
        pay.setOnClick(new ClickListener() {        // anonymous class
            private int count = 0;                   // নিজস্ব state
            public void onClick(String label) {
                System.out.println(label + " #" + (++count) + " " + getClass().getName());
            }
        });
        pay.click(); pay.click();
        List<String> names = new ArrayList<>(List.of("Nusrat", "Ali", "Rahim"));
        names.sort(new Comparator<String>() {       // পুরোনো ধাঁচ: ছোট নাম আগে
            public int compare(String a, String b) { return Integer.compare(a.length(), b.length()); }
        });
        System.out.println(names);
        names.sort((a, b) -> Integer.compare(b.length(), a.length()));  // lambda: বড় নাম আগে
        System.out.println(names);
    }
}

আউটপুট চার লাইন: Pay #1 Main$1, Pay #2 Main$1, [Ali, Rahim, Nusrat], [Nusrat, Rahim, Ali]. তিনটি জিনিস দেখুন। (১) listener-এর নিজস্ব field count আছে, আর দুটি ক্লিকের মাঝে সেটা মনে থাকে — এটা anonymous class-এর এমন ক্ষমতা যা lambda-র নেই। বাইরের local variable বদলাতে হয়নি, তাই effectively final-এর সমস্যাও নেই। (২) getClass().getName() ছাপল Main$1 — নামহীন class-কেও compiler একটি নাম দেয়: বাইরের class-এর নাম, $, আর ক্রমিক নম্বর। Comparator-টি হলো Main$2. (৩) সাজানোর দুটি সংস্করণ একই চুক্তি (compare) পূরণ করে; anonymous class-এ লাগল তিন লাইন, lambda-তে এক লাইন। Integer.compare ব্যবহার করা হয়েছে কারণ দুটি সংখ্যা বিয়োগ করে তুলনা করলে বড় মানে overflow হতে পারে।

anonymous class-এর সীমা

  • constructor লেখা যায় না — নামই নেই, constructor-এর নাম কী হবে? শুরুর কাজ করতে field-এ সরাসরি মান দিন বা instance initializer block ({ ... }) ব্যবহার করুন।
  • একটিই parent: একটি interface implement করে অথবা একটি class extend করে — দুটি একসাথে নয়, দুটি interface-ও নয়।
  • নতুন public method যোগ করলে বাইরে থেকে ডাকা কঠিন: ভ্যারিয়েবলের টাইপ হয় parent-এর (ClickListener), আর সেখানে নতুন method নেই। (var দিয়ে রাখলে টাইপটা নামহীন class-ই থাকে, তবে সেটা কদাচিৎ কাজে লাগে।)

৬ · anonymous class থেকে lambda — আর কখন anonymous class-ই লাগে

ওপরের Comparator-টা আবার দেখুন। তিন লাইনের মধ্যে আসল কাজের কথা শুধু একটি: “a আর b-এর দৈর্ঘ্য তুলনা করো।” বাকি সব — new Comparator<String>(), public int compare, বন্ধনী — compiler আগে থেকেই জানে, কারণ Comparator-এ একটিই abstract method. পর্ব ২১-এ দেখেছেন, এমন interface-কে বলে functional interface. Java 8 এই পুনরাবৃত্তি ছেঁটে ফেলার জন্য lambda এনেছে। রূপান্তরটা ধাপে ধাপে দেখুন:

  1. anonymousnew X() {..}পুরো class
  2. খোলস বাদ(params) bodyযা compiler জানে
  3. তীর->lambda হলো
  4. টাইপ বাদ(a, b)অনুমান করে
  5. এক expression-> exprreturn বাদ
ধাপ ১শুরু: anonymous class
names.sort(new Comparator<String>() {
    public int compare(String a, String b) {
        return Integer.compare(a.length(), b.length());
    }
});
ধাপ ২compiler যা জানে, তা মুছুন
names.sort(
    (String a, String b) {
        return Integer.compare(a.length(), b.length());
    }
);
// sort চায় Comparator — তার একটিই method, compare
ধাপ ৩parameter আর body-র মাঝে ->
names.sort((String a, String b) -> {
    return Integer.compare(a.length(), b.length());
});
ধাপ ৪parameter-এর টাইপ বাদ
names.sort((a, b) -> {
    return Integer.compare(a.length(), b.length());
});
// List<String> থেকে compiler জানে a, b হলো String
ধাপ ৫শরীর একটি expression হলে
names.sort((a, b) -> Integer.compare(a.length(), b.length()));
// আরও ছোট (পর্ব ৩৩-৩৪): names.sort(Comparator.comparingInt(String::length));
  1. শুরুর অবস্থা। পাঁচ লাইন, যার মধ্যে উজ্জ্বল অংশগুলো (new Comparator<String>() {, public int compare, শেষের বন্ধনী) শুধু আনুষ্ঠানিকতা। আসল তথ্য দুটি: parameter a, b আর তুলনার নিয়ম।
  2. যা অনুমান করা যায়, তা বাদ। sort-এর parameter-এর টাইপ Comparator, তাই কোন interface — compiler জানে। সেখানে abstract method একটিই, তাই কোন method — সেটাও জানে। এ দুটি লেখার দরকার নেই।
  3. তীর বসান। parameter তালিকা আর শরীরের মাঝে -> — এখন এটা বৈধ lambda. এই রূপ কাজ করে শুধু functional interface-এর জন্য; দুটি abstract method থাকলে compiler বুঝত না শরীরটা কোন method-এর।
  4. টাইপ বাদ। names হলো List<String>, তাই তার Comparator হবে Comparator<String>, আর compare-এর parameter দুটি String. compiler নিজেই অনুমান করে (type inference).
  5. শরীর একটি expression হলে { } আর return-ও বাদ। পাঁচ লাইন থেকে এক লাইন, অর্থ একই। ধূসর মন্তব্যে আরও ছোট রূপ — method reference আর Comparator-এর factory method — যেগুলোর বিস্তারিত পর্ব ৩৩ আর পর্ব ৩৪-এ।

তবু যেখানে anonymous class-ই লাগে

lambda শুধু functional interface-এর জন্য। তিনটি পরিস্থিতিতে anonymous class (বা নাম দেওয়া class) এখনো দরকার:

  • একাধিক abstract method: পুরোনো GUI-র MouseListener-এ পাঁচটি method (mouseClicked, mousePressed, …) — lambda একটির বেশি শরীর দিতে পারে না।
  • নিজস্ব state: ওপরের বোতামের count-এর মতো field. lambda-র নিজের field থাকে না।
  • abstract class বা সাধারণ class-এর subclass: lambda শুধু interface-এর জন্য। একটি abstract method-ওয়ালা abstract class হলেও না:
abstract class Task {
    abstract void run();
}

class Main {
    public static void main(String[] args) {
        Task t = () -> System.out.println("hi");
    }
}
Main.java:7: error: incompatible types: Task is not a functional interface
        Task t = () -> System.out.println("hi");
                 ^
1 error

this — দুটিতে দুই অর্থ

এই পার্থক্যটি সূক্ষ্ম, আর AI-ও প্রায়ই গুলিয়ে ফেলে। anonymous class একটি সত্যিকারের নতুন class, তাই তার ভেতরে this মানে সেই নামহীন object. lambda নতুন class নয়, নতুন scope-ও নয় — তার ভেতরে this মানে যে object-এর method-এ lambda লেখা হয়েছে, অর্থাৎ বাইরের object.

class Screen {
    String name = "Home";
    void show() {
        Runnable anon = new Runnable() {
            public void run() { System.out.println("anonymous: " + this.getClass().getName()); }
        };
        Runnable lam = () -> System.out.println("lambda:    " + this.getClass().getName() + " " + this.name);
        anon.run();
        lam.run();
    }
}
// new Screen().show();
// anonymous: Screen$1
// lambda:    Screen Home

anonymous class-এর ভেতর থেকে বাইরের Screen পেতে লিখতে হতো Screen.this; lambda-তে সাধারণ this-ই যথেষ্ট। একই কারণে lambda-র ভেতরে বাইরের কোনো local variable-এর নামে নতুন ভ্যারিয়েবল ঘোষণা করা যায় না (একই scope), কিন্তু anonymous class-এর ভেতরে যায় (নতুন scope, তাই বাইরেরটা ঢাকা পড়ে)।

বিষয়anonymous classlambda
কিসের জন্যযেকোনো interface বা classশুধু functional interface
thisনামহীন object নিজেবাইরের object
নিজস্ব fieldরাখা যায়যায় না
local variable ধরাeffectively final হতে হবেeffectively final হতে হবে
আলাদা .class ফাইলহ্যাঁ (Main$1.class)না (চলার সময় তৈরি)

lambda-র পুরো গল্প — java.util.function-এর interface-গুলো, method reference, আর lambda দিয়ে কোড সাজানোর কৌশল — পর্ব ৩৪-এ। এই পর্বের জন্য মনে রাখুন: lambda হলো “একটি method-ওয়ালা anonymous class”-এর সংক্ষিপ্ত, আর আরও কার্যকর বিকল্প।

৭ · পর্দার আড়ালে — class ফাইল, synthetic accessor আর nestmates

একটি চমকপ্রদ তথ্য: JVM “nested class” বলে কিছু চেনে না। পর্ব ৫-এ দেখেছেন, javac প্রতিটি class-এর জন্য একটি আলাদা class file বানায়। nested class-ও তার ব্যতিক্রম নয় — প্রতিটি পায় নিজের ফাইল, শুধু নামে $ দিয়ে বাইরের class-এর নাম জোড়া থাকে। একটি Outer.java compile করলে কী কী তৈরি হয়, দেখুন:

  1. Outer.javasourceপাঁচ ধরনের class
  2. সদস্য classOuter$Innerনাম দিয়ে
  3. নামহীন/localOuter$1ক্রমিক নম্বর
  4. lambdaফাইল নেইinvokedynamic
  5. nestJava 11private ভাগাভাগি
ধাপ ১Outer.java
class Outer {
    private int x = 1;
    class Inner { int get() { return x; } }
    static class Nested { }
    Runnable anon() { return new Runnable() { public void run() { } }; }
    Object local() { class Helper { } return new Helper(); }
    Runnable lam() { return () -> System.out.println(x); }
}
ধাপ ২javac Outer.java — প্রথম ভাগ
Outer.class
Outer$Inner.class
Outer$Nested.class
ধাপ ৩আরও দুটি
Outer.class
Outer$Inner.class
Outer$Nested.class
Outer$1.class          ← anonymous
Outer$1Helper.class    ← local class Helper
ধাপ ৪lambda কোথায়?
lambda-র শরীর → Outer.class-এর ভেতরে
একটি private synthetic method (যেমন lambda$lam$0)
আর তাকে জোড়ার জন্য একটি invokedynamic নির্দেশ
→ আলাদা .class ফাইল নেই
ধাপ ৫javap -v দিয়ে দেখলে
Outer.class:       NestMembers attribute
                   (Inner, Nested, 1, 1Helper)
Outer$Inner.class: NestHost attribute → Outer
JVM: একই nest-এর সবাই একে অন্যের private ব্যবহার করতে পারে
  1. একটি source ফাইল। এখানে আছে একটি inner class, একটি static nested class, একটি anonymous class, একটি local class আর একটি lambda. কোডের দিক থেকে সবাই Outer-এর ভেতরে।
  2. সদস্য class: Outer$Inner. নাম দেওয়া সদস্য class-এর ফাইলের নাম বাইরের$ভেতরের.class. static হোক বা না হোক, নিয়ম একই। ভেতরের ভেতরে থাকলে শিকল লম্বা হয়: Bank$Transaction$Builder.class.
  3. নামহীন আর local class: নম্বর। anonymous class-এর নাম নেই, তাই compiler দেয় ক্রমিক নম্বর: Outer$1, Outer$2, … local class পায় নম্বর আর নাম দুটিই (Outer$1Helper), কারণ দুটি আলাদা method-এ একই নামের local class থাকতে পারে। অংশ ৫-এর প্রোগ্রামে getClass().getName() ঠিক এমন নামই (Main$1) ছেপেছিল, আর অংশ ৩-এ ছেপেছিল সদস্য class-এর নাম Playlist$SongIterator.
  4. lambda — আলাদা ফাইল নেই। lambda-র শরীর বাইরের class-এর ভেতরে একটি লুকানো method হয়ে যায়। চলার সময় প্রথমবার invokedynamic নির্দেশ চললে JVM LambdaMetafactory-কে ডেকে interface-এর implementation তৈরি করে নেয়। তাই lambda-র জন্য ডিস্কে কোনো Outer$2.class জমা হয় না — প্রশ্ন ৫-এ এর কারণ আর সুফল।
  5. nest — Java 11-এর সমাধান। JVM-এর কাছে Outer আর Outer$Inner দুটি আলাদা class. তাহলে Inner কীভাবে Outer-এর private x পড়ে? Java 11 থেকে class ফাইলে লেখা থাকে কে কোন “nest”-এর সদস্য (NestHost, NestMembers), আর JVM একই nest-এর সবাইকে একে অন্যের private সদস্য ব্যবহার করতে দেয়। javap -v দিয়ে এই তথ্য দেখা যায়।

Java 11-এর আগে কী হতো? JVM-এর নিয়মে এক class আরেক class-এর private সদস্য ছুঁতে পারত না। তাই ভাষার প্রতিশ্রুতি (“inner class outer-এর private পড়তে পারে”) রাখতে javac চুপচাপ outer class-এ package-private synthetic accessor method বানাত — নাম হতো access$000, access$100 ধরনের — আর inner class সেগুলো ডাকত। এতে class ফাইল বড় হতো, stack trace-এ অদ্ভুত নাম দেখা যেত, আর reflection দিয়ে private সদস্য ব্যবহারের নিয়মে অসংগতি থাকত। JEP 181 (nest-based access control) এই কৌশলের প্রয়োজন মিটিয়ে দিয়েছে।

ইতিহাস: ১৯৯৬ সালের Java 1.0-তে nested class ছিল না — সব class ছিল top-level. ১৯৯৭ সালের JDK 1.1-এ “Inner Classes Specification”-এর মাধ্যমে চার ধরনের nested class একসাথে এসেছিল, মূলত AWT-র নতুন event ব্যবস্থার (delegation event model) জন্য — প্রতিটি বোতামের listener-এর জন্য আলাদা ফাইলে class লেখা অসহনীয় হয়ে উঠছিল। কিন্তু JVM-এ কোনো পরিবর্তন না এনে এটা করা হয়েছিল, তাই javac-কে লুকানো field আর accessor method দিয়ে সব সামলাতে হতো। সেই সমঝোতার শেষ চিহ্ন মুছতে লেগেছে প্রায় ২১ বছর — Java 11 (২০১৮)।

৮ · কোনটা কখন — সিদ্ধান্ত নেওয়ার নিয়ম

Joshua Bloch-এর বিখ্যাত বই Effective Java-র (তৃতীয় সংস্করণ) Item 24-এর শিরোনাম: “Favor static member classes over nonstatic” — অর্থাৎ inner class-এর চেয়ে static nested class বেছে নিন। যুক্তি: inner class প্রতিটি object-এ একটি বাড়তি reference রাখে, তৈরি করতে সময় ও মেমরি লাগে, আর সবচেয়ে বড় কথা — বাইরের object-কে অকারণে বাঁচিয়ে রাখতে পারে। তাই প্রশ্নগুলো এই ক্রমে করুন:

প্রশ্নউত্তর হ্যাঁ হলেউদাহরণ
সহকারীটি কি আরও অনেক জায়গায় লাগবে?আলাদা top-level classMoney, Address
শুধু এক class-এর সহকারী, বাইরের object-এর field লাগে না?static nested (ডিফল্ট)Bank.Transaction.Builder, Node
বাইরের নির্দিষ্ট object-এর state পড়তে বা বদলাতে হবে?inner classPlaylist-এর iterator
শুধু একটি method-এর ভেতরে লাগবে, নাম দরকার?local class বা local recordরসিদের Line, Score
একবারের implementation, একটি abstract method?lambdaComparator, Runnable
একবারের, কিন্তু একাধিক method, state বা abstract class?anonymous classবোতামের ক্লিক গোনা listener
ডিফল্ট: static nested.
বাইরের object সত্যিই লাগলে তবেই inner.

একটি সতর্কতা: nested class অতিরিক্ত ব্যবহার করলে একটি বিশাল ফাইল তৈরি হয়, যেখানে পাঁচ স্তর গভীরে class লুকানো। nested class ছোট রাখুন; যখন একটি nested class নিজেই বড় হয়ে ওঠে বা অন্য জায়গা থেকে ব্যবহার শুরু হয়, তাকে আলাদা ফাইলে সরিয়ে দিন।

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

C++-এর nested class আসলে Java-র static nested class-এর মতো — তার কাছে বাইরের object-এর কোনো লুকানো pointer থাকে না; Java-র inner class-এর মতো আচরণ চাইলে নিজে হাতে pointer রাখতে হয়। C++-এর lambda [=] দিয়ে কপি বা [&] দিয়ে reference ধরতে পারে, Java সবসময় কপি ধরে আর বদলানো নিষেধ করে। Python-এ nested function বাইরের ভ্যারিয়েবল ধরে রাখে (closure), আর nonlocal দিয়ে বদলানোও যায়। তুলনার জন্য দেখুন C++-এর lambda আর Python-এর scope ও closure পর্ব।

পরীক্ষা ও ইন্টারভিউতে
  • static nested আর inner class-এর পার্থক্য? — static nested-এর object বাইরের object ছাড়াই তৈরি হয়; inner-এর প্রতিটি object একটি বাইরের object-এর reference (Outer.this) ধরে রাখে।
  • বাইরে থেকে inner class-এর object কীভাবে বানাবেন? — Outer o = new Outer(); Outer.Inner i = o.new Inner();
  • anonymous class কেন বাইরের local variable বদলাতে পারে না? — সে মানের একটি কপি রাখে; বদলানো গেলে দুই কপি আলাদা হতো, তাই ভ্যারিয়েবল final বা effectively final হতে হয়।
  • anonymous class আর lambda-র পার্থক্য? — lambda শুধু functional interface-এর জন্য, নিজস্ব state নেই, this মানে বাইরের object, আর আলাদা .class ফাইল হয় না।
  • Shop.java-র প্রথম anonymous class-এর class ফাইলের নাম? — Shop$1.class.

৯ · AI-এর যুগে — লুকানো reference আর অপ্রয়োজনীয় দৈর্ঘ্য ধরুন

AI-কে একটি linked list বা একটি Builder লিখতে বললে প্রায়ই দেখবেন class Node বা class Builder — static ছাড়া। কোড চলে, টেস্টও পাস করে। কিন্তু প্রতিটি Node এখন অকারণে পুরো লিস্ট object-এর একটি লুকানো reference বহন করছে, আর main থেকে new Node() লিখতে গেলে সেই বিভ্রান্তিকর non-static variable this error আসছে, যার “সমাধান” হিসেবে AI আবার অন্য কোথাও static বসাচ্ছে। এমন ভুলগুলো আপনাকেই ধরতে হবে:

AI যা করেকেন সমস্যাআপনি যা করবেন
Node, Builder, Entry — static ছাড়াঅকারণ লুকানো reference, বেশি মেমরি, বাইরের object আটকে থাকার ঝুঁকিজিজ্ঞেস করুন: এই class কি বাইরের object-এর কোনো field ছোঁয়? না হলে static
Java 8+ কোডে একটি method-এর interface-এর জন্য পুরো anonymous classপাঁচ লাইনের আনুষ্ঠানিকতা, আলাদা class ফাইলlambda বা method reference চান
lambda-র ভেতরে this দিয়ে lambda-কে নিজেকে বোঝাতে চায়lambda-তে this মানে বাইরের objectনিজস্ব state বা নিজেকে বোঝানো লাগলে anonymous বা নাম দেওয়া class
effectively final error এড়াতে int[] count = {0} কৌশলerror চাপা পড়ে, নকশার সমস্যা থাকে; thread-এ ঝুঁকিstate-কে একটি class-এর field-এ রাখুন বা stream-এর মতো ফল-ফেরত-দেওয়া নকশা নিন
দীর্ঘজীবী listener বা callback হিসেবে inner/anonymous class নিবন্ধন, কিন্তু সরানোর কোড নেইবাইরের বড় object (স্ক্রিন, সেশন) ছাড়া পায় নানিবন্ধন সরানোর জায়গা দেখান, বা static nested + দুর্বল reference

AI-কে nested class-ওয়ালা কোড লিখতে বা রিভিউ করতে বলার সময় এই কাঠামো ব্যবহার করুন:

আমি Java 21 শিখছি, nested/inner/anonymous class আর lambda-র মূল ধারণা জানি। কাজ: [যেমন — একটি Playlist class, যার ভেতরে গান ঘোরার iterator আর একটি Builder থাকবে] শর্ত: ১. প্রতিটি nested class-এর পাশে এক লাইনে লেখো: static nested, inner, local না anonymous — আর কেন। ২. যে nested class বাইরের object-এর কোনো field বা method ব্যবহার করে না, সেটা অবশ্যই static হবে। ৩. একটি abstract method-ওয়ালা interface-এর জন্য anonymous class নয়, lambda বা method reference দেবে। ৪. anonymous class রাখলে কারণ বলো (একাধিক method, নিজস্ব state, বা abstract class). ৫. lambda আর anonymous class-এর ভেতরে this কাকে বোঝায়, যেখানে ব্যবহার করেছ সেখানে মন্তব্যে লেখো। ৬. কোনো listener বা callback নিবন্ধন করলে, সেটা কোথায় সরানো হবে দেখাও। ৭. effectively final এড়াতে array বা AtomicInteger-এর কৌশল ব্যবহার করবে না, দরকার হলে নকশা বদলাবে।

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

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

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

প্র ০১Java 1.0-তে nested class ছিল না। মাত্র এক বছর পর JDK 1.1-এ চার ধরনের nested class একসাথে কেন এলো, আর AWT-র event ব্যবস্থার সাথে এর কী সম্পর্ক?

১৯৯৬ সালের Java 1.0-এর GUI লাইব্রেরি AWT-তে event সামলানোর পদ্ধতি ছিল উত্তরাধিকারভিত্তিক। একটি বোতাম চাপলে সেই ঘটনা component-গুলোর স্তর বেয়ে ওপরে উঠত, আর প্রোগ্রামারকে কোনো একটি component-এর subclass বানিয়ে action() বা handleEvent() method override করতে হতো। একটি জানালায় দশটি বোতাম থাকলে সবগুলোর ঘটনা একটি বিশাল method-এ এসে পড়ত, যেখানে if-এর শিকল দিয়ে খুঁজতে হতো কোন বোতাম চাপা হয়েছে। কোড অগোছালো হতো, আর অপ্রয়োজনীয় ঘটনাও সব জায়গায় ঘুরে বেড়াত।

নতুন event মডেল। JDK 1.1 (১৯৯৭) “delegation event model” আনল: প্রতিটি component তার ঘটনা পাঠাবে শুধু নিবন্ধিত listener object-এর কাছে — button.addActionListener(listener). নকশা হিসেবে এটা পরিষ্কার, কিন্তু একটি নতুন সমস্যা তৈরি করল। প্রতিটি বোতামের জন্য একটি listener class লাগবে, আর সেই listener-কে প্রায়ই জানালার field (টেক্সট বক্স, লেবেল) পড়তে-বদলাতে হবে। top-level class দিয়ে করলে প্রতিটি listener-এর constructor-এ জানালার reference পাঠাতে হতো, জানালার field-গুলো private থেকে খুলে দিতে হতো, আর ফাইলের সংখ্যা বিস্ফোরিত হতো।

সমাধান: inner class. তাই একই রিলিজে Sun “Inner Classes Specification” প্রকাশ করল। inner class জানালার private field সরাসরি পড়তে পারে, কারণ সে স্বয়ংক্রিয়ভাবে বাইরের object-কে ধরে রাখে; anonymous class দিয়ে listener-টি ঠিক যেখানে নিবন্ধন হচ্ছে সেখানেই লেখা যায়; local class method-এর মান ব্যবহার করতে পারে। অর্থাৎ চার ধরনের nested class আসলে এক প্যাকেজ — event-চালিত GUI প্রোগ্রামিং সহজ করার জন্য।

JVM না বদলানোর সিদ্ধান্ত। গুরুত্বপূর্ণ বিষয় হলো, এটা পুরোপুরি compiler-এর স্তরে করা হয়েছিল। ইতিমধ্যে বহু browser আর যন্ত্রে Java 1.0-এর JVM ছড়িয়ে পড়েছিল; JVM বদলালে পুরোনো যন্ত্রে নতুন কোড চলত না। তাই javac nested class-কে সাধারণ top-level class-এ রূপান্তর করে (Outer$Inner), বাইরের object-এর জন্য লুকানো field যোগ করে, আর private ব্যবহারের জন্য accessor method বানায়। সেই সমঝোতার দাম — synthetic accessor — Java 11 পর্যন্ত টানতে হয়েছে (প্রশ্ন ৩)।

পরের ইতিহাস। Swing আর পরে Android-এর পুরো প্রজন্ম anonymous listener দিয়ে লেখা হয়েছে। দীর্ঘ ১৭ বছর পর Java 8 (২০১৪) lambda এনে সেই anonymous class-গুলোর বেশিরভাগকে এক লাইনে নামিয়ে আনল। এক অর্থে lambda হলো ১৯৯৭ সালের সেই সমস্যার দ্বিতীয়, আরও পরিচ্ছন্ন সমাধান।

মূল উপলব্ধি: ভাষার বৈশিষ্ট্য প্রায়ই একটি নির্দিষ্ট বাস্তব সমস্যা থেকে জন্মায়। inner class জন্মেছিল GUI event-এর জন্য; তার নকশার প্রতিটি সমঝোতা (লুকানো reference, accessor) বোঝা যায় “JVM বদলানো যাবে না” শর্তটি মনে রাখলে।

প্র ০২JavaScript-এ closure বাইরের ভ্যারিয়েবল বদলাতে পারে, Java-তে পারে না। Java কেন কপি নিয়ে “effectively final” নিয়ম বেছে নিল — এটা কি দুর্বলতা না সচেতন সিদ্ধান্ত?

JavaScript-এ একটি function বাইরের ভ্যারিয়েবল ব্যবহার করলে সে ভ্যারিয়েবলের মান নয়, ভ্যারিয়েবলটি নিজেই ধরে রাখে। ভেতর থেকে count++ করলে বাইরের count-ও বদলায়; function শেষ হয়ে গেলেও ভ্যারিয়েবলটি বেঁচে থাকে, কারণ engine তাকে stack থেকে সরিয়ে heap-এ একটি আলাদা জায়গায় রাখে। Python-এ nonlocal দিয়ে, C#-এ সরাসরি — একই ধরনের “ভ্যারিয়েবল ধরা” আছে।

Java-র পথ: মান কপি। Java-র local variable থাকে method-এর stack frame-এ, আর JVM-এর নকশায় একটি frame শুধু সেই thread-এরই, method শেষ হলে মুছে যায়। ভ্যারিয়েবলটিকে বাঁচিয়ে রাখতে হলে compiler-কে চুপচাপ তাকে heap-এর একটি object-এ সরাতে হতো, আর প্রতিটি ব্যবহার সেই object-এর মাধ্যমে করতে হতো। JDK 1.1-এর নকশাকারীরা সহজ পথ নিলেন: মানটি কপি করো, আর বিভ্রান্তি এড়াতে বদলানো নিষেধ করো। কপি আর মূল যদি কখনো বদলাতেই না পারে, তাহলে “কপি না মূল” প্রশ্নটাই অর্থহীন — দুটি সবসময় এক।

thread-এর যুক্তি। একটি বড় সুবিধা এখানে: anonymous class বা lambda প্রায়ই অন্য thread-এ চলে (একটি Runnable যেমন)। যদি দুই thread একই local variable বদলাতে পারত, local variable-ও data race-এর শিকার হতো — অথচ Java-তে এখন local variable স্বভাবতই thread-নিরাপদ, কারণ সেটা শুধু এক thread-এর stack-এ থাকে। Java 8-এ lambda ডিজাইনের সময় Brian Goetz-এর দল প্রকাশ্যে এই যুক্তি দিয়েছিল: বদলানো যায় এমন local variable ধরলে সমান্তরাল stream-এর মতো কোডে নীরব bug তৈরি হতো।

JavaScript-এর বিখ্যাত ফাঁদ। পুরোনো JavaScript-এ for (var i = 0; i < 3; i++) লুপে তিনটি callback বানালে তিনটিই শেষে ৩ দেখাত — কারণ সবাই একই i ধরেছিল, যেটা লুপ শেষে ৩ হয়ে গেছে। ES2015-এর let প্রতিটি ধাপে নতুন ভ্যারিয়েবল দিয়ে এটা ঠিক করেছে। Java-তে এই ফাঁদ শুরুতেই অসম্ভব: লুপের i বদলায়, তাই effectively final নয়, তাই compiler আপনাকে আগেই থামায়।

দাম। কখনো সত্যিই একটি গণনা ভাগ করতে হয়। তখন বদলানো যায় এমন object (field, AtomicInteger) লাগে — একটু বাড়তি কোড, কিন্তু স্পষ্ট: পাঠক দেখতে পায় যে এখানে ভাগ করা অবস্থা আছে। Java 8 শুধু “final লিখতেই হবে” থেকে “effectively final হলেই চলবে”-তে নিয়ম শিথিল করেছে, মূল নীতি একই রেখেছে।

মূল উপলব্ধি: effectively final একটি সচেতন বিনিময় — একটু কম নমনীয়তার বদলে সরল মেমরি মডেল, thread-নিরাপদ local variable আর এক শ্রেণির bug-এর সম্পূর্ণ বিলুপ্তি।

প্র ০৩Java 11-এর আগে inner class কীভাবে outer-এর private field পড়ত? “synthetic accessor” কী ছিল, আর JEP 181-এর nestmates কোন সমস্যা মেটাল?

Java ভাষার নিয়ম বলে: একই top-level class-এর ভেতরের সবাই একে অন্যের private সদস্য ব্যবহার করতে পারে। কিন্তু JVM-এর নিয়ম ভিন্ন। JVM প্রতিটি class ফাইলকে স্বাধীন class হিসেবে দেখে, আর তার কাছে private মানে “শুধু এই class ফাইলের ভেতর থেকে।” Outer$Inner আর Outer JVM-এর চোখে দুটি অপরিচিত class. ভাষা আর JVM-এর এই অমিল ১৯৯৭ থেকে ২০১৮ পর্যন্ত টিকে ছিল।

javac-এর কৌশল। অমিল ঢাকতে javac একটি সেতু বানাত। inner class যদি outer-এর private int x পড়ে, javac outer class-এ একটি লুকানো static method যোগ করত — যেমন static int access$000(Outer o) { return o.x; } — যার access package-private. inner class-এর x পড়ার কোড বদলে হতো Outer.access$000(this$0). এই method-গুলো “synthetic”, অর্থাৎ compiler-এর তৈরি, source-এ নেই।

সমস্যাগুলো। প্রথমত, encapsulation-এ ছিদ্র: package-private method মানে একই package-এর যেকোনো class (বা bytecode বদলানো কেউ) সেটা ডেকে private field পড়তে পারত। দ্বিতীয়ত, বাড়তি method — বড় কোডবেসে হাজার হাজার, যা class ফাইল আর মেমরি বাড়াত। Android-এ এটা বিশেষ যন্ত্রণার ছিল, কারণ পুরোনো DEX ফরম্যাটে একটি ফাইলে method-এর সংখ্যার সীমা (৬৫,৫৩৬) ছিল, আর accessor-গুলো সেই সীমা খেয়ে ফেলত। তৃতীয়ত, stack trace-এ অচেনা access$000 লাইন। চতুর্থত, reflection অসংগত: source-এ বৈধ private ব্যবহার reflection দিয়ে করতে গেলে IllegalAccessException আসত।

JEP 181 (Java 11). সমাধান হলো JVM-কে সরাসরি ভাষার ধারণা জানানো। প্রতিটি nest-এর একটি host (top-level class) থাকে; host-এর class ফাইলে NestMembers attribute-এ সব সদস্যের নাম, আর প্রতিটি সদস্যের ফাইলে NestHost attribute-এ host-এর নাম। JVM private access যাচাইয়ের সময় দেখে, দুটি class একই nest-এর কি না — হলে অনুমতি দেয়। ফলে javac আর accessor বানায় না; inner class সরাসরি private field পড়ে। reflection-এ Class.getNestHost() আর getNestMembers() যোগ হলো।

বৃহত্তর শিক্ষা। “compiler-এ করে ফেলি, VM বদলানোর দরকার নেই” — স্বল্পমেয়াদে চমৎকার সিদ্ধান্ত, কিন্তু তার জটিলতা জমতে থাকে। শেষ পর্যন্ত বিমূর্ততাটি যে স্তরের, সেই স্তরে নামিয়ে আনাই স্থায়ী সমাধান।

মূল উপলব্ধি: nestmates ভাষা আর JVM-এর একটি ২১ বছরের পুরোনো অমিল মেটাল — private এখন যা বলে, JVM-ও ঠিক তাই মানে।

প্র ০৪Android-এ “inner class থেকে memory leak” এত পরিচিত সমস্যা কেন? একটি Activity আর একটি Handler বা ব্যাকগ্রাউন্ড কাজের উদাহরণে ব্যাখ্যা করুন।

Android অ্যাপের প্রতিটি স্ক্রিন সাধারণত একটি Activity object. এটি হালকা নয়: এর কাছে পুরো view-এর গাছ, ছবি (bitmap), আর নানা resource-এর reference থাকে। আর এর জীবনকাল অপারেটিং সিস্টেম নিয়ন্ত্রণ করে — ব্যবহারকারী ফোন ঘোরালেই পুরোনো Activity ধ্বংস হয়ে নতুন একটি তৈরি হয়। ধ্বংস হওয়া Activity-কে কেউ ধরে রাখলে তার সব ভারী জিনিস মেমরিতে থেকে যায়।

সাধারণ চেহারা। পুরোনো Android কোডে একটি পরিচিত ধাঁচ ছিল: Activity-র ভেতরে একটি non-static inner class বা anonymous class, যা ব্যাকগ্রাউন্ডে কাজ করে বা পরে চালানোর জন্য একটি বার্তা পাঠায় — যেমন একটি Handler-এ দেরিতে চালানোর বার্তা, বা একটি দীর্ঘ নেটওয়ার্ক কাজ। inner object স্বয়ংক্রিয়ভাবে Activity.this ধরে রাখে। বার্তাটি সিস্টেমের বার্তা-সারিতে (message queue) বসে থাকে, বা কাজটি অন্য thread-এ চলতে থাকে; ততক্ষণ inner object বেঁচে, তাই Activity-ও বেঁচে। এর মধ্যে ব্যবহারকারী ফোন কয়েকবার ঘোরালে কয়েকটি ধ্বংস হওয়া Activity মেমরিতে জমে, শেষে OutOfMemoryError.

কেন এটা ধরা কঠিন। কোডে কোথাও Activity-কে স্পষ্টভাবে সংরক্ষণ করা হয়নি। reference-টি লুকানো — এই পর্বের প্রথম stepper-এর this$0. পরীক্ষার সময় অ্যাপ ঠিক চলে; সমস্যা দেখা যায় দীর্ঘ ব্যবহারে। garbage collector-এর দিক থেকে কোনো ভুল নেই: GC root (চলন্ত thread, বার্তা-সারি) থেকে Activity পর্যন্ত একটি বৈধ শিকল আছে, তাই সে সরাতে পারে না।

প্রতিকার। Android-এর Lint টুল এই ধাঁচের জন্য আলাদা সতর্কবার্তা দিত — Handler-কে static করতে বলত। প্রচলিত সমাধান ছিল: সহকারী class-কে static nested বানানো, আর Activity দরকার হলে WeakReference দিয়ে রাখা, যাতে GC চাইলে Activity সরাতে পারে; Activity ধ্বংসের সময় অপেক্ষমাণ বার্তা ও কাজ বাতিল করা। Square কোম্পানির LeakCanary-র মতো টুল অ্যাপ চলার সময় এই শিকল খুঁজে দেখায়। আধুনিক Android-এ lifecycle-সচেতন component আর Kotlin coroutine-এর scope এই কাজ অনেকটাই স্বয়ংক্রিয় করেছে — কিন্তু মূল নীতি একই।

Java সার্ভারেও। একই সমস্যা listener-এ দেখা যায়: একটি দীর্ঘজীবী event bus-এ anonymous listener নিবন্ধন করে কখনো সরানো হলো না — তার বাইরের object (একটি সেশন, একটি রিকোয়েস্ট handler) চিরকাল বেঁচে থাকে।

মূল উপলব্ধি: inner class-এর লুকানো reference তখনই বিপদ, যখন inner object বাইরের object-এর চেয়ে বেশি দিন বাঁচে। দীর্ঘজীবী কাজের জন্য static nested class আর স্পষ্ট reference — এটাই Effective Java Item 24-এর ব্যবহারিক রূপ।

প্র ০৫anonymous class compile হয়ে আলাদা .class ফাইল হয়, lambda হয় না। bytecode-এর স্তরে lambda কীভাবে কাজ করে, আর invokedynamic বেছে নেওয়ার সুবিধা কী?

Java 8-এর lambda ডিজাইনের সময় (Project Lambda, নেতৃত্বে Brian Goetz) সবচেয়ে সহজ পথ ছিল: প্রতিটি lambda-কে compiler-এর স্তরে একটি anonymous class বানিয়ে ফেলা — ঠিক যেভাবে Main$1.class হয়। অনেক বাইরের টুল (যেমন পুরোনো Android-এর জন্য retrolambda) পরে ঠিক এটাই করত। কিন্তু Java-র দল ইচ্ছা করে সেই পথ নেয়নি।

anonymous class-এর খরচ। প্রতিটি anonymous class একটি আলাদা class ফাইল — ডিস্কে, jar-এ, আর প্রোগ্রাম চলার সময় class loader-এ। একটি বড় অ্যাপে হাজার হাজার lambda থাকলে হাজার হাজার ছোট class লোড, যাচাই আর মেমরিতে রাখতে হতো, যা চালু হওয়ার সময় বাড়ায়। আরও বড় কথা: “lambda = anonymous class” — এই কৌশল একবার bytecode-এ বসে গেলে চিরকালের জন্য স্থির হয়ে যেত; পরে ভালো কৌশল এলেও পুরোনো compile করা কোড পুরোনো পথেই চলত।

invokedynamic-এর পথ। invokedynamic নির্দেশটি Java 7-এ এসেছিল, মূলত JVM-এ dynamic ভাষা (JRuby, Groovy) চালানোর জন্য। Java 8 এটাকে lambda-র জন্য কাজে লাগাল। javac lambda-র শরীরকে বাইরের class-এর ভেতরে একটি private synthetic method বানায় (যেমন lambda$main$0), আর lambda যেখানে লেখা সেখানে একটি invokedynamic নির্দেশ বসায়। নির্দেশটি প্রথমবার চলার সময় JVM একটি “bootstrap method” ডাকে — LambdaMetafactory.metafactory — যে চলার সময়েই functional interface-এর একটি implementation তৈরি করে, যেটা ওই private method-কে ডাকে। ফল একটি call site-এ বাঁধা থাকে, তাই পরের ডাকগুলোতে এই খরচ আর লাগে না। কোনো বাইরের local variable না ধরলে একই object বারবার ব্যবহার করা যায়।

সুবিধা। প্রথমত, ডিস্কে আলাদা class ফাইল নেই। দ্বিতীয়ত, কৌশলটি JDK-র ভেতরে, compile করা কোডে নয় — JDK আপডেট হলে LambdaMetafactory-র implementation বদলাতে পারে, আর পুরোনো jar বিনা recompile-এ উপকৃত হয়। বাস্তবেও তাই হয়েছে: যেমন Java 15-এ lambda-র implementation class বানানোর অভ্যন্তরীণ কৌশল বদলে “hidden class” (JEP 371) ব্যবহার শুরু হয়েছে। তৃতীয়ত, এই একই কৌশল পরে অন্য জায়গায় কাজে লেগেছে — Java 9 থেকে String-এর + জোড়া invokedynamic দিয়ে হয়, Java 16-এর record-এর toString/equals/hashCode-ও।

পার্থক্যের ছাপ। এই নকশার কারণেই lambda-র this বাইরের object (lambda-র শরীর বাইরের class-এরই একটি method), আর lambda-র নিজস্ব identity-র ওপর নির্ভর করা উচিত নয় — JVM সেটা পুনর্ব্যবহার করতে পারে।

মূল উপলব্ধি: lambda শুধু anonymous class-এর ছোট লেখা নয়; নির্দিষ্ট কৌশল bytecode-এ না বসিয়ে চলার সময়ে ছেড়ে দিয়ে Java ভবিষ্যতের উন্নতির দরজা খোলা রেখেছে।

অনুশীলন

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

  1. Error অনুমান করুন: অংশ ৩-এর চালানো যায় এমন প্রোগ্রামে private class SongIterator-কে private static class SongIterator করুন। না চালিয়ে বলুন compiler কোন কোন লাইনে কী বলবে। তারপর ভাবুন: static রেখেই কোডটি কীভাবে ঠিক করা যায়?

    তিনটি error: hasNext()-এর লাইনে non-static variable songs cannot be referenced from a static context, আর next()-এর লাইনে দুটি — Playlist.this-এর জন্য non-static variable this cannot be referenced from a static context আর songs-এর জন্য আবার একই songs-এর বার্তা। static nested class-এর কোনো বাইরের object নেই, তাই কোন Playlist-এর songs — প্রশ্নের উত্তর নেই। static রেখে ঠিক করতে বাইরের object-টি স্পষ্টভাবে দিন: SongIterator-এ একটি private final Playlist owner; field আর constructor SongIterator(Playlist p) { owner = p; }, ভেতরে owner.songs, owner.name, আর iterator()-এ new SongIterator(this). আপনি আসলে নিজে হাতে সেই কাজটি করলেন, যা inner class চুপচাপ করে — এখানে inner class-ই স্বাভাবিক পছন্দ, কারণ iterator সত্যিই একটি নির্দিষ্ট তালিকার।

  2. রূপান্তর করুন: অংশ ৫-এর প্রোগ্রামে listener-টিকে lambda বানানোর চেষ্টা করুন। কোন অংশটি lambda-তে নেওয়া যায় না, আর কেন? তারপর গণনা ছাড়া শুধু label + " clicked" ছাপে এমন listener-টি lambda-তে লিখুন।

    private int count field-টি lambda-তে নেওয়া যায় না — lambda-র নিজস্ব field থাকে না। বাইরের একটি local int count দিয়ে চেষ্টা করলে ++count-এর কারণে local variables referenced from a lambda expression must be final or effectively final error আসবে। আর getClass().getName()-এর অর্থও বদলে যাবে: static main-এ this-ই নেই। গণনা ছাড়া সংস্করণ: pay.setOnClick(label -> System.out.println(label + " clicked")); — ClickListener-এ একটিই abstract method, তাই এটা বৈধ। শিক্ষা: state লাগলে anonymous class (বা নাম দেওয়া class), না লাগলে lambda.

  3. ফাইল গুনুন: Shop.java-য় আছে: top-level class Shop, তার ভেতরে static class Item, class Cart, Cart-এর ভেতরে class Line, Shop-এর একটি method-এ দুটি anonymous Runnable, আর একটি lambda. javac Shop.java কয়টি আর কোন কোন .class ফাইল বানাবে?

    ছয়টি: Shop.class, Shop$Item.class, Shop$Cart.class, Shop$Cart$Line.class, Shop$1.class, Shop$2.class. lambda-র জন্য আলাদা ফাইল নেই — তার শরীর Shop.class-এর ভেতরে একটি private synthetic method হয়, আর চলার সময় invokedynamic আর LambdaMetafactory implementation তৈরি করে। খেয়াল করুন, Line হলো inner-এর ভেতরে inner, তাই Shop$Cart$Line object বানাতে একটি Cart object লাগবে, আর সেই Cart-এর জন্য একটি Shop.

  4. Leak খুঁজুন: একটি ReportScreen class-এ বড় একটি byte[] chart (কয়েক MB) আছে। তার constructor-এ লেখা: EventBus.listeners.add(new Listener() { public void onUpdate() { redraw(); } }); — যেখানে EventBus.listeners একটি static List. ব্যবহারকারী ১০০ বার স্ক্রিন খুলে বন্ধ করলেন। কী ঘটবে, আর দুটি সমাধান বলুন।

    প্রতিটি anonymous listener redraw() ডাকে, তাই তার কাছে ReportScreen.this-এর লুকানো reference আছে। static list একটি GC root থেকে পৌঁছানো যায়, তাই ১০০টি listener বেঁচে থাকে, আর সাথে ১০০টি ReportScreen ও তাদের কয়েক MB-র chart — স্ক্রিন বন্ধ হলেও। এটা ক্লাসিক memory leak; শেষে OutOfMemoryError আসতে পারে, আর বন্ধ স্ক্রিনগুলোও onUpdate-এ অকারণে আঁকতে থাকবে। সমাধান: (১) listener-কে একটি field-এ রাখুন আর স্ক্রিন বন্ধের সময় (close()) EventBus.listeners.remove(listener) করুন — নিবন্ধন আর বাতিল জোড়ায়। (২) EventBus নিজে listener-গুলো দুর্বল reference (WeakReference) দিয়ে রাখুক, যাতে স্ক্রিনের অন্য কোনো reference না থাকলে GC সরিয়ে দিতে পারে। শুধু listener-কে static nested বানালে হবে না, কারণ redraw()-এর জন্য তার স্ক্রিন লাগে — আসল সমস্যা নিবন্ধনের জীবনকাল।

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

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

ক্রমJava-তে একটি anonymous Comparator-কে lambda-তে রূপান্তরের ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)

  1. 🔍 নিশ্চিত হন interface-টিতে ঠিক একটি abstract method আছে
  2. ✂️ new Comparator<String>() { আর method-এর মাথা (public int compare) মুছে ফেলুন
  3. ➡️ parameter তালিকা আর শরীরের মাঝে -> বসান
  4. 🏷️ parameter-এর টাইপ বাদ দিন, compiler অনুমান করবে
  5. ✅ শরীর একটি expression হলে { } আর return বাদ দিন

Java-তে static nested class আর inner (non-static member) class-এর মূল পার্থক্য কী?

  • static nested class-এর মাত্র একটি object বানানো যায়
  • inner class-এর object বাইরের class-এর একটি object-এর সাথে বাঁধা থাকে, static nested-এর থাকে না
  • শুধু static nested class বাইরের class-এর private সদস্য দেখতে পায়
  • inner class-এর জন্য আলাদা .class ফাইল তৈরি হয় না

main-এ int count = 0; তারপর একটি anonymous Runnable-এর run() থেকে count ছাপা হয়, আর তার পরের লাইনে count++; লেখা। compile করলে কী হবে?

  • compile হবে, run() ডাকলে 1 ছাপবে
  • compile হবে, run() ডাকলে 0 ছাপবে
  • compile error: ভ্যারিয়েবলটি final বা effectively final নয়
  • চলার সময় IllegalStateException

একটি instance method-এর ভেতরে একটি anonymous class আর একটি lambda — দুটির শরীরেই this লেখা। this কাকে বোঝায়?

  • anonymous class-এ নামহীন object-টিকে, lambda-তে বাইরের object-কে
  • দুটিতেই নিজেকে (anonymous object বা lambda object)
  • দুটিতেই বাইরের object-কে
  • anonymous class-এ বাইরের object-কে, lambda-তে lambda-কে

class Outer { class Inner { } } — Outer-এর বাইরে, একটি static method থেকে Inner-এর object বানানোর সঠিক উপায় কোনটি?

  • Outer.Inner i = new Outer.Inner();
  • Outer.Inner i = Outer.new Inner();
  • Outer.Inner i = Outer.this.new Inner();
  • Outer o = new Outer(); Outer.Inner i = o.new Inner();

Shop.java ফাইলে Shop class-এর একটি method-এ প্রথম যে anonymous class লেখা, javac তার class ফাইলের নাম কী দেবে?

  • Shop$Anonymous.class
  • Shop$1.class
  • কোনো আলাদা ফাইল নয়, Shop.class-এর ভেতরে থাকে
  • Shop$0.class

নিচের কোন ক্ষেত্রে anonymous class-এর জায়গায় lambda লেখা সম্ভব?

  • দুটি abstract method-ওয়ালা একটি abstract class-এর subclass
  • নিজস্ব একটি গণনা-field রাখা দরকার এমন একটি listener
  • একটি Comparator, যা শুধু দুটি String-এর দৈর্ঘ্য তুলনা করে
  • পাঁচটি abstract method-ওয়ালা একটি listener interface

শব্দকোষ

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

মনে রাখুন

  1. যে class শুধু আরেকটির সহকারী, সে তার ভেতরে থাকে: সম্পর্কিত কোড একসাথে, private করে বাস্তবায়ন লুকানো, আর একে অন্যের private সদস্য ব্যবহারের সুযোগ।
  2. static nested = বাইরের object ছাড়া; inner = লুকানো Outer.this: বাইরে থেকে inner বানাতে outer.new Inner(); static context-এ new Inner() দিলে non-static variable this error.
  3. local ও anonymous class (আর lambda) বাইরের local variable-এর কপি রাখে: তাই ভ্যারিয়েবল final বা effectively final হতে হয়।
  4. একটি abstract method-এর interface → lambda; একাধিক method, state বা abstract class → anonymous class. anonymous class-এ this নিজে, lambda-তে বাইরের object.
  5. ডিফল্ট static nested (Effective Java Item 24): অকারণ inner class বাইরের object আটকে রাখতে পারে; compile হলে Outer$Inner.class, Outer$1.class, আর Java 11 থেকে nestmates.

পরের পর্বে: generics. এই পর্বে বারবার দেখা Iterator<String>, Comparator<String>, List<Transaction>-এর কৌণিক বন্ধনীর রহস্য — কীভাবে একটি class বা method যেকোনো টাইপের সাথে কাজ করে, অথচ ভুল টাইপ ঢোকালে compiler-ই আটকায়।

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

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