এই পর্বে যা শিখবেন
- Encapsulation আর invariant-এর ধারণা — কেন field
privateরাখা হয় - চারটি access level, কোনটি কোথা থেকে দেখা যায়, আর compiler-এর আসল error মেসেজ
- Package,
packagestatement,import, ফোল্ডার কাঠামো আর fully qualified name - Getter-setter কখন কাজের আর কখন শুধু আনুষ্ঠানিকতা; Tell-Don't-Ask; JavaBeans-এর নাম রাখার রীতি
- Defensive copy, immutability-র শুরু, module (JPMS) আর reflection দিয়ে
privateভাঙার কথা
১ · Encapsulation — object নিজের ডেটা নিজে পাহারা দেয়
পর্ব ১৫-এ class দিয়ে নিজের টাইপ বানিয়েছেন, পর্ব ১৬-এ constructor দিয়ে object-কে শুরু থেকেই ঠিক অবস্থায় তৈরি করেছেন। কিন্তু তৈরি হওয়ার পরে কী হবে? ধরুন একটি মোবাইল ওয়ালেটের Account class-এ balance নামে একটি field আছে। প্রোগ্রামের যেকোনো জায়গা থেকে যদি acc.balance = -5000; লেখা যায়, তাহলে constructor যত যত্ন করেই লেখা হোক, এক লাইনেই সব নিয়ম ভেঙে যায়।
পর্ব ০২-এ একটি শব্দ শিখেছিলেন: invariant — এমন নিয়ম যা একটি object-এর জন্য সবসময় সত্য থাকতে হবে। ওয়ালেটের invariant হতে পারে: “ব্যালান্স কখনো শূন্যের নিচে নামবে না” আর “এক দিনে ২৫,০০০ টাকার বেশি তোলা যাবে না।” প্রশ্ন হলো, এই নিয়ম কে রক্ষা করবে? উত্তরটাই এই পর্বের মূল কথা: object নিজেই।
bKash বা Nagad-এর কথা ভাবুন। আপনার ব্যালান্স কোম্পানির সার্ভারে একটি সংখ্যা হিসেবে আছে। কিন্তু আপনি সেই সংখ্যাটি সরাসরি এডিট করতে পারেন না — অ্যাপে শুধু কয়েকটি বোতাম আছে: “Send Money”, “Cash Out”, “Add Money”. প্রতিটি বোতাম চাপলে সিস্টেম আগে যাচাই করে: যথেষ্ট টাকা আছে? PIN ঠিক? আজকের সীমা পার হয়নি? তারপরই সংখ্যাটি বদলায়। সংখ্যাটি লুকানো, বোতামগুলো খোলা — আর প্রতিটি বোতামের পেছনে নিয়মের পাহারা। Encapsulation ঠিক এই নকশা।
Encapsulation শব্দের দুটি অংশ আছে। প্রথমত, ডেটা আর সেই ডেটার নিয়ম একই class-এ থাকে — ব্যালান্স আর “ব্যালান্স কীভাবে কমবে” একসাথে। দ্বিতীয়ত, বাইরের কোড ডেটা সরাসরি ছুঁতে পারে না; শুধু class-এর দেওয়া method দিয়ে অনুরোধ করতে পারে। দ্বিতীয় অংশটির আরেকটি নাম আছে — information hiding: class-এর ভেতরে কী আছে আর কীভাবে আছে, তা বাইরের কাউকে জানতে হবে না।
Java-তে এই পাহারার যন্ত্র হলো access modifier — private, protected, public, অথবা কিছু না লেখা। এরা compiler-কে বলে কোন নাম কোথা থেকে ব্যবহার করা যাবে। নিয়ম ভাঙলে প্রোগ্রাম চলার সময় নয়, compile-এর সময়েই আটকে যায়। অর্থাৎ ভুলটা কোনো গ্রাহকের টাকায় পৌঁছানোর অনেক আগে ধরা পড়ে।
Encapsulation-এর লক্ষ্য তথ্য গোপন করা নয়, নিয়ম রক্ষা করা। যদি একটি field বদলানোর একমাত্র পথ হয় একটি method, তাহলে নিয়মটি লিখতে হয় এক জায়গায়, পরীক্ষা করতে হয় এক জায়গায়, আর ভুল হলে খুঁজতেও হয় এক জায়গায়।
২ · Public field-এর বিপদ, আর private-এর সমাধান
একটি বড় অ্যাপে হাজারো লাইন কোড, দশজন প্রোগ্রামার। balance যদি public হয়, তাহলে যে কেউ যেকোনো জায়গা থেকে তাতে হাত দিতে পারে — রিপোর্টের স্ক্রিন, রিফান্ডের কোড, কোনো জরুরি bug fix. নিচের ডায়াগ্রামে দুটি নকশা পাশাপাশি দেখুন। “পরে” চাপুন:
class Account {
public long balance; // সবার জন্য খোলা
}
acc.balance = 1000;
ব্যালান্স: 1000 টাকা — এখনো সব ঠিক
// RefundScreen.java — তাড়াহুড়োয় লেখা acc.balance = acc.balance - 6000; ব্যালান্স: -5000 টাকা compiler চুপ, প্রোগ্রাম চুপ — কেউ থামায়নি
$ grep -rn "balance =" src/ ReportScreen.java:88 acc.balance = ... RefundScreen.java:41 acc.balance = ... AdminTool.java:203 acc.balance = ... ... আরও ৩৭টি জায়গা নিয়মটা কোথায় লেখা? কোথাও না — বা ৪০ জায়গায় ৪০ রকম
class Account {
private long balance;
String withdraw(long amount) {
if (amount > balance) return "REJECTED";
balance -= amount; return "OK";
}
}
acc.withdraw(6000); → "REJECTED"
ব্যালান্স: 1000 টাকাই থাকল
acc.balance = -5000; error: balance has private access in Account প্রোগ্রাম তৈরিই হলো না — ভুল গ্রাহক পর্যন্ত পৌঁছাল না
- Public field — সবার জন্য খোলা দরজা।
balance-এর সামনেpublic, তাই প্রোগ্রামের যেকোনো class সরাসরি পড়তে ও লিখতে পারে। শুরুতে এতে কোনো সমস্যা চোখে পড়ে না। - নিয়ম ভাঙল, কেউ টের পেল না। অন্য একজন প্রোগ্রামার রিফান্ডের কোডে সরাসরি বিয়োগ করলেন। ব্যালান্স -5000. Java-র দিক থেকে এটা পুরোপুরি বৈধ কোড —
long-এ ঋণাত্মক সংখ্যা রাখা যায়। Invariant ভেঙেছে, কিন্তু কোনো error নেই। - দোষী খোঁজা প্রায় অসম্ভব। Field-টি ৪০ জায়গা থেকে বদলায়। কোনটি ভুল করেছে, জানতে প্রতিটি জায়গা পড়তে হবে। আর “ব্যালান্স ঋণাত্মক হবে না” — এই নিয়ম লেখা আছে কোথায়? কোথাও না, অথবা কিছু জায়গায় আছে, কিছু জায়গায় নেই।
- Private field, একটিমাত্র দরজা। এখন
balanceবদলানোর একমাত্র পথwithdraw. নিয়মটি লেখা আছে ঠিক সেখানে, একবার। 6000 টাকা তোলার অনুরোধ নাকচ হলো, ব্যালান্স অক্ষত। - পেছনের দরজা compiler বন্ধ রাখে। কেউ সরাসরি লিখতে চাইলে javac থামিয়ে দেয়:
has private access in Account. ভুলটা ধরা পড়ল আপনার কম্পিউটারে, গ্রাহকের ফোনে নয়।
এবার একটি পূর্ণ ওয়ালেট class. এতে দুটি নিয়ম আছে — ব্যালান্সের বেশি তোলা যাবে না, আর দিনে ২৫,০০০ টাকার বেশি তোলা যাবে না। চালিয়ে দেখুন কোন অনুরোধ পাস হয় আর কোনটি নাকচ হয়:
class Wallet {
private static final long DAILY_LIMIT = 25_000; // static final: পর্ব ১৮
private long balance;
private long withdrawnToday;
Wallet(long openingBalance) { balance = openingBalance; }
String withdraw(long amount) {
if (amount <= 0) return "REJECTED: invalid amount";
if (amount > balance) return "REJECTED: not enough balance";
if (withdrawnToday + amount > DAILY_LIMIT) return "REJECTED: daily limit";
balance -= amount;
withdrawnToday += amount;
return "OK, balance " + balance;
}
long getBalance() { return balance; }
}
class Main {
public static void main(String[] args) {
Wallet w = new Wallet(30_000);
long[] tries = {5_000, -200, 40_000, 22_000, 20_000};
for (long t : tries) System.out.println(t + " -> " + w.withdraw(t));
System.out.println("Final: " + w.getBalance());
// w.balance = -5000; // এই লাইন চালু করলে compile error
}
}আউটপুট: 5000 -> OK, balance 25000, -200 -> REJECTED: invalid amount, 40000 -> REJECTED: not enough balance, 22000 -> REJECTED: daily limit, 20000 -> OK, balance 5000, শেষে Final: 5000. চতুর্থ অনুরোধটি খেয়াল করুন: ব্যালান্সে ২৫,০০০ ছিল, তাই ২২,০০০ তোলা যেত — কিন্তু আজ আগেই ৫,০০০ তোলা হয়েছে, মোট ২৭,০০০ সীমা ছাড়িয়ে যায়। পঞ্চম অনুরোধে মোট ঠিক ২৫,০০০, তাই পাস। তিনটি নিয়মই লেখা আছে এক method-এ, আর balance বা withdrawnToday বদলানোর অন্য কোনো পথ নেই। শেষ লাইনের comment সরিয়ে দিলে javac বলবে error: balance has private access in Wallet — Wandbox-এর OpenJDK 21-এ যাচাই করা। (বাস্তব অ্যাপে নাকচের জন্য String-এর বদলে exception ছোড়া হয় — পর্ব ২৯; আর টাকার হিসাব সাধারণত পয়সায় long বা BigDecimal-এ রাখা হয়।)
একটি জিনিস লক্ষ করুন: Main আর Wallet একই ফাইলে, একই package-এ আছে। তবুও Main থেকে balance দেখা যায় না, কারণ private মানে “শুধু এই class-এর ভেতরে” — একই ফাইল বা একই package-এ থাকলেও না। অনলাইন runner-এ সব class একটি ফাইলে, একটি package-এ থাকে; তাই এখানে আপনি যে access level-টি সত্যিই পরীক্ষা করতে পারবেন, সেটি হলো private. বাকি তিনটির আচরণ পরের অংশে compiler-এর আসল মেসেজ দিয়ে দেখাচ্ছি।
৩ · চারটি access level — কে কোথা থেকে দেখতে পায়
Java-তে একটি field, method বা constructor-এর সামনে চারটির যেকোনো একটি access level বসে। কিছু না লিখলেও একটি level থাকে — তার নাম package-private (অনেকে বলেন “default access”). সবচেয়ে কড়া থেকে সবচেয়ে খোলা পর্যন্ত:
| লেখা হয় | নিজের class | একই package | অন্য package-এর subclass | বাকি সবাই (world) |
|---|---|---|---|---|
private | ✔ | ✗ | ✗ | ✗ |
| (কিছু না) package-private | ✔ | ✔ | ✗ | ✗ |
protected | ✔ | ✔ | ✔ | ✗ |
public | ✔ | ✔ | ✔ | ✔ |
দুটি জিনিস প্রায়ই ভুল বোঝা হয়। প্রথমত, protected package-private-এর চেয়ে বেশি খোলা, কম নয় — একই package-এর সবাই তো পায়ই, সাথে অন্য package-এর subclass-ও পায়। দ্বিতীয়ত, public class-এর ভেতরের member দেখতে হলে class-টিও দেখা যেতে হবে; package-private class-এর public method অন্য package থেকে কাজে আসে না। Subclass আর protected-এর পুরো গল্প (বিশেষ করে অন্য package থেকে কোন reference দিয়ে ঢোকা যায়) আসবে পর্ব ১৯-এ inheritance শেখার সময়।
নিচের ডায়াগ্রামে একটিই Wallet class, চারটি field, চারটি access level. চারটি ভিন্ন জায়গা থেকে এগুলো ছোঁয়ার চেষ্টা করা হচ্ছে। প্রতিটি ধাপে কোন লাইন হাইলাইট হয়েছে (মানে সেখান থেকে দেখা যায়), আর compiler কী বলছে, দেখুন:
1package com.abcltech.bank;2public class Wallet {3 private long balance;4 long dailyLimit;5 protected long fee;6 public String owner;7}
| জায়গা | |
| balance (private) | |
| dailyLimit (package) | |
| fee (protected) | |
| owner (public) |
- Class-এর নিজের ভেতর থেকে — সব খোলা।
Wallet-এর যেকোনো method চারটি field-ই পড়তে-লিখতে পারে।privateকাউকে বাদ দেয় না, শুধু বাইরের সবাইকে। - একই package-এর অন্য class.
com.abcltech.bank.WalletServiceএকই “পাড়ার” বাসিন্দা। সে package-private, protected আর public পায়, কিন্তুbalanceনয় — javac বলেhas private access in Wallet. - অন্য package-এর subclass.
com.abcltech.promo.StudentWalletলিখেছেextends Wallet(পর্ব ১৯)। সে উত্তরাধিকার সূত্রেfeeপায়,ownerতো পায়ই। কিন্তুdailyLimit-এর সামনে কিছু লেখা নেই, তাই সেটি package-এর বাইরে অদৃশ্য — মেসেজটি একটু অন্যরকম:is not public in Wallet; cannot be accessed from outside package. - অন্য package-এর সাধারণ class.
com.abcltech.app.Main-এর কোনো সম্পর্ক নেই। সে শুধুownerপায়।feeছুঁতে গেলে:has protected access in Wallet. বাস্তব নকশায় এখানেowner-ও public field না হয়ে private হতো, সাথে একটি method — টেবিলটা শুধু নিয়ম দেখানোর জন্য।
Private মানে “এই class-এর”, “এই object-এর” নয়
ইন্টারভিউর একটি প্রিয় প্রশ্ন: একটি Wallet object কি আরেকটি Wallet object-এর private field পড়তে পারে? উত্তর — হ্যাঁ। Java-র access নিয়ম class ধরে কাজ করে, object ধরে নয়। নিচের transferTo method নিজের balance-এর সাথে other.balance-ও বদলায়, আর এটা বৈধ:
class Wallet {
private long balance;
boolean transferTo(Wallet other, long amount) {
if (amount <= 0 || amount > balance) return false;
balance -= amount;
other.balance += amount; // বৈধ: একই class-এর ভেতরের কোড
return true;
}
}কারণটা যুক্তিসঙ্গত: Wallet class-এর কোড যিনি লিখছেন, তিনি দুটি object-এরই নিয়ম জানেন। পাহারা দরকার অন্য class-এর কোড থেকে।
Top-level class-এর জন্য মাত্র দুটি পছন্দ
একটি ফাইলের সরাসরি ভেতরের (top-level) class হয় public, নয়তো কিছু না লেখা (package-private). private class Wallet বা protected class Wallet top-level-এ লেখা যায় না — modifier private not allowed here error আসে। একটি ফাইলে বড়জোর একটি public top-level class থাকতে পারে, আর তার নাম ফাইলের নামের সাথে হুবহু মিলতে হয় (public class Wallet থাকবে Wallet.java-তে)। এজন্যই এই কোর্সের runnable কোডে Main class-এর সামনে public নেই — অনলাইন runner ফাইলটিকে অন্য নামে compile করে। নিজের প্রজেক্টে সাধারণত public class Main থাকে Main.java-তে। Class-এর ভেতরের (nested) class private হতে পারে — পর্ব ২৭-এ দেখবেন।
৪ · Package — নাম, ফোল্ডার আর import
package-private আর protected বুঝতে হলে package বুঝতে হবে। Package হলো সম্পর্কিত class-গুলোর একটি নামযুক্ত দল। Java-র নিজের library-ও package-এ সাজানো: java.util-এ collection, java.time-এ তারিখ-সময়, java.io-তে ফাইল। Package দুটি কাজ করে: নামের সংঘর্ষ ঠেকায় (দুটি কোম্পানির দুটি Wallet class আলাদা package-এ নিশ্চিন্তে থাকতে পারে), আর encapsulation-এর একটি স্তর তৈরি করে — package-এর ভেতরের সাহায্যকারী class-গুলো বাইরে লুকিয়ে রাখা যায়।
package statement আর ফোল্ডার
একটি ফাইল কোন package-এর, তা লেখা হয় ফাইলের একদম প্রথম statement-এ: package com.abcltech.bank;. নামের রীতি হলো কোম্পানির ডোমেইন উল্টো করে লেখা — abcltech.com থেকে com.abcltech, তারপর প্রজেক্টের অংশ। এতে সারা পৃথিবীতে package-এর নাম আলাদা থাকে। সব অক্ষর ছোট হাতের। আর package-এর নাম মেলে ফোল্ডারের সাথে:
src/
└── com/
└── abcltech/
├── bank/
│ ├── Wallet.java (package com.abcltech.bank;)
│ └── WalletService.java (package com.abcltech.bank;)
└── app/
└── Main.java (package com.abcltech.app;)package লাইন না লিখলে class যায় একটি নামহীন package-এ (unnamed package). ছোট পরীক্ষার জন্য চলে, কিন্তু অন্য কোনো package থেকে সেই class import করাই যায় না — তাই আসল প্রজেক্টে সবসময় package থাকে। Maven বা Gradle প্রজেক্টে (পর্ব ৪৬) এই কাঠামো থাকে src/main/java/-এর নিচে।
Fully qualified name আর import
Package-সহ একটি class-এর পুরো নামকে বলে fully qualified name: com.abcltech.bank.Wallet, java.util.Scanner, java.time.LocalDate. JVM-এর কাছে এটাই class-এর আসল নাম। প্রতিবার পুরো নাম লেখা ক্লান্তিকর, তাই আছে import:
package com.abcltech.app;
import com.abcltech.bank.Wallet; // শুধু এই class
import java.util.*; // java.util-এর সব public class (sub-package নয়)
public class Main {
public static void main(String[] args) {
Wallet w = new Wallet(1000); // import থাকায় ছোট নাম
java.time.LocalDate d = java.time.LocalDate.of(2026, 10, 18); // import ছাড়া পুরো নাম
List<String> names = new ArrayList<>(); // java.util.* থেকে
}
}তিনটি জিনিস মনে রাখুন। (১) import কোনো কোড কপি করে না, কিছু লোডও করে না — শুধু compiler-কে বলে “Wallet লিখলে com.abcltech.bank.Wallet বুঝবে।” C-র #include-এর সাথে এর কোনো মিল নেই। (২) java.lang package (String, Math, System) সবসময় নিজে থেকেই import করা থাকে। (৩) java.util.* শুধু ওই package-এর class আনে, java.util.function-এর মতো sub-package নয় — Java-তে “sub-package” আসলে সম্পূর্ণ আলাদা package, নামের মিলটা শুধু গোছানোর জন্য। এর মানে com.abcltech.bank.internal-এর class com.abcltech.bank-এর package-private জিনিস দেখতে পায় না।
একই সরল নামের দুটি class লাগলে একটি import করুন, অন্যটি পুরো নামে লিখুন — যেমন java.util.Date আর java.sql.Date. দুটিকে নাম ধরে import করলে দ্বিতীয় import লাইনেই error আসে: a type with the same simple name is already defined by the single-type-import of Date. আর java.util.* ও java.sql.* দুটিই import করে শুধু Date লিখলে compiler বলে reference to Date is ambiguous.
Package-এর বাইরে থেকে ঢোকার চেষ্টা — compiler যা বলে
ওপরের ফোল্ডার কাঠামোয় ধরুন Wallet-এ একটি package-private method void audit() আছে, আর WalletService class-টির সামনে public লেখা নেই। com.abcltech.app.Main থেকে এগুলো ব্যবহার করতে গেলে javac এই ধরনের মেসেজ দেয়:
Main.java:4: error: WalletService is not public in com.abcltech.bank; cannot be accessed from outside package
import com.abcltech.bank.WalletService;
^
Main.java:9: error: audit() is not public in Wallet; cannot be accessed from outside package
w.audit();
^
মেসেজ পড়ার কৌশল: has private access মানে member-টি private; has protected access মানে protected; is not public ... outside package মানে সামনে কিছু লেখা নেই (package-private). সবগুলো মেসেজ OpenJDK 21-এ একাধিক package-এর ফাইল compile করে যাচাই করা। এই তিনটি চিনলেই access-সংক্রান্ত প্রায় সব compile error নিজে ঠিক করতে পারবেন।
৫ · Getter আর setter — কখন কাজের, কখন শুধু আনুষ্ঠানিকতা
Field private করলে বাইরের কোড পড়বে কীভাবে? প্রচলিত উত্তর: getter আর setter. Getter একটি মান ফেরত দেয় (getBalance()), setter একটি মান বসায় (setBalance(long)). কিন্তু সাবধান: প্রতিটি field-এর জন্য একটি getter আর একটি setter লিখে দিলে আপনি আসলে public field-ই বানালেন, শুধু লম্বা পথে। setBalance(-5000) আর balance = -5000-এর মধ্যে কোনো পার্থক্য নেই, যদি setter কিছুই যাচাই না করে।
Getter-setter সত্যিই কাজে আসে তিনটি ক্ষেত্রে:
১ · যাচাই (validation)
Setter শুধু বৈধ মান নেয়। একজন শিক্ষার্থীর GPA ০.০ থেকে ৫.০-এর বাইরে যেতে পারবে না।
নিয়মসহ setter২ · শুধু পড়া (read-only)
Getter আছে, setter নেই। অ্যাকাউন্ট নম্বর দেখা যায়, কিন্তু তৈরির পরে বদলানো যায় না।
setter-ই নেই৩ · হিসাব করা মান
Getter কোনো field ফেরত দেয় না, হিসাব করে দেয়। তাপমাত্রা রাখা আছে সেলসিয়াসে, ফারেনহাইট চাইলে হিসাব।
field-ই নেইclass Student {
private double gpa;
void setGpa(double gpa) {
if (gpa < 0.0 || gpa > 5.0)
throw new IllegalArgumentException("GPA must be 0.0-5.0: " + gpa);
this.gpa = gpa;
}
double getGpa() { return gpa; }
}
class Temperature {
private final double celsius; // তৈরির পরে বদলায় না
Temperature(double celsius) { this.celsius = celsius; }
double getCelsius() { return celsius; }
double getFahrenheit() { return celsius * 9 / 5 + 32; } // field নেই, হিসাব
}Temperature-এ getFahrenheit() দেখে বাইরের কেউ বুঝতে পারে না ভেতরে ফারেনহাইট রাখা আছে নাকি হিসাব হচ্ছে — আর বোঝার দরকারও নেই। কাল যদি আপনি ভেতরে কেলভিনে রাখার সিদ্ধান্ত নেন, শুধু এই class বদলাবে; যারা getFahrenheit() ডাকে, তাদের একটি লাইনও বদলাতে হবে না। Public field থাকলে সেটা অসম্ভব হতো — এটাই information hiding-এর আসল লাভ। (IllegalArgumentException ছোড়া মানে ভুল মান সরাসরি প্রত্যাখ্যান করা; exception-এর বিস্তারিত পর্ব ২৯-এ।)
যখন getter-setter শুধু আনুষ্ঠানিকতা
পর্ব ০২-এ anemic class-এর কথা বলেছিলাম — যে class শুধু ডেটা আর getter-setter রাখে, আর সব নিয়ম লেখা থাকে অন্য কোথাও। এমন class-এ private লেখা থাকলেও encapsulation নেই, কারণ নিয়মের পাহারাদার class নিজে নয়। দেখার সহজ পরীক্ষা: কোনো setter কি কখনো “না” বলে? কোনো field কি এমন আছে যা বাইরে থেকে বদলানোই উচিত নয় (id, তৈরির তারিখ, ব্যালান্স), অথচ তার setter আছে? থাকলে নকশাটি নিয়ে আবার ভাবুন।
আবার উল্টোটাও সত্য: কিছু class-এর কাজই শুধু ডেটা বহন করা — যেমন API-তে পাঠানোর একটি ফর্মের তথ্য। এমন ক্ষেত্রে হাতে লেখা getter-setter-এর বদলে Java 16 থেকে আছে record (পর্ব ২৫), যা এক লাইনে immutable ডেটা-class বানায়। নিয়ম-ওয়ালা object আর শুধু-ডেটা object — দুটিকে আলাদা করে চেনাই আসল দক্ষতা।
JavaBeans-এর নাম রাখার রীতি — কেন getX, isX, setX
নামগুলো এলোমেলো নয়। ১৯৯০-এর দশকের শেষে Sun Microsystems JavaBeans নামে একটি নির্দেশিকা দেয়: একটি “bean” class-এ একটি খালি constructor থাকবে, আর প্রতিটি property-র জন্য getName() / setName(...); boolean property-র ক্ষেত্রে getter হতে পারে isActive(). এর উদ্দেশ্য ছিল টুলগুলো যেন method-এর নাম দেখে নিজে থেকে property খুঁজে নিতে পারে।
আজও বহু framework এই রীতির ওপর দাঁড়িয়ে। JSON লাইব্রেরি Jackson একটি object-কে JSON বানানোর সময় সাধারণত getName() দেখে "name" নামের ঘর বানায়; Spring ফর্ম বা configuration থেকে মান বসানোর সময় setName খোঁজে। তাই getname() বা fetchName() লিখলে কোড compile হবে, কিন্তু framework হয়তো property-টি দেখতেই পাবে না — নীরব bug. (নতুন সংস্করণের Jackson record-ও সরাসরি বোঝে।) এই লাইব্রেরিগুলো অনলাইন runner-এ নেই, তাই নিচের কোড শুধু দেখার জন্য:
public class Customer {
private String name;
private boolean active;
public Customer() {} // খালি constructor
public String getName() { return name; }
public void setName(String name) { this.name = name; }
public boolean isActive() { return active; } // boolean-এ isX
public void setActive(boolean a) { this.active = a; }
}
// Jackson দিয়ে JSON বানালে (প্রত্যাশিত): {"name":"Rahim","active":true}৬ · Tell, Don't Ask — জিজ্ঞেস না করে কাজটা করতে বলুন
Getter-setter-এর নেশা কাটানোর সবচেয়ে কাজের নীতিটির নাম Tell, Don't Ask. দুটি কোড তুলনা করুন:
// ডাকার জায়গায় নিয়ম — প্রতিটি caller-কে মনে রাখতে হবে
if (acc.getBalance() >= 500) {
acc.setBalance(acc.getBalance() - 500);
}// নিয়ম object-এর ভেতরে — caller শুধু চায়
acc.withdraw(500);প্রথম সংস্করণে নিয়মটি (“যথেষ্ট টাকা থাকলে তবেই”) লেখা হয়েছে ডাকার জায়গায়। দশ জায়গা থেকে টাকা তোলা হলে নিয়মটি দশবার লিখতে হবে, আর একবার ভুলে গেলেই ব্যালান্স ঋণাত্মক। দৈনিক সীমা যোগ করতে হলে দশ জায়গায় যেতে হবে। দ্বিতীয় সংস্করণে caller শুধু বলে “৫০০ টাকা তোলো” — কীভাবে, কোন নিয়মে, তা Account-এর দায়িত্ব। পর্ব ০২-এর ভাষায়: object-কে বার্তা পাঠান, তার পেট থেকে ডেটা বের করে নিজে কাজ করবেন না।
রেস্টুরেন্টে আপনি ওয়েটারকে বলেন “একটা কাচ্চি দিন।” আপনি রান্নাঘরে ঢুকে চাল কত আছে দেখে, হাঁড়ি নামিয়ে, নিজে রান্না শুরু করেন না। রান্নাঘর জানে মজুদ কত, কোন হাঁড়ি খালি, কোন অর্ডার আগে। Tell, Don't Ask ঠিক এই ভাগাভাগি — চাওয়া আপনার, কীভাবে হবে সেটা যার দায়িত্ব তার।
তবে এটি অন্ধ নিয়ম নয়। রিপোর্ট দেখানো, স্ক্রিনে ব্যালান্স ছাপা, JSON বানানো — এসব কাজে পড়ার জন্য getter স্বাভাবিক। সমস্যা হয় যখন caller getter দিয়ে ডেটা বের করে, সিদ্ধান্ত নেয়, তারপর setter দিয়ে ফেরত বসায়। সিদ্ধান্ত যেখানে, নিয়মও সেখানে থাকা উচিত।
৭ · ভেতরের ডেটা যেন ফাঁস না হয় — defensive copy
এবার এমন একটি ফাঁক, যা অভিজ্ঞ প্রোগ্রামারও প্রায়ই রেখে দেন। পর্ব ১২-১৩-এ দেখেছেন: array একটি object, ভ্যারিয়েবলে থাকে তার reference. ধরুন Student-এর ভেতরে private int[] marks আছে। Field private, কিন্তু getter যদি return marks; করে, তাহলে caller পায় একই array-র reference — আর marks[0] = 100 লিখে ভেতরের নম্বর বদলে ফেলতে পারে। দরজায় তালা, কিন্তু চাবির কপি সবার হাতে। একই কাণ্ড ঘটে উল্টো দিকেও: constructor যদি বাইরে থেকে আসা array সরাসরি রেখে দেয়, তাহলে যে array পাঠিয়েছে সে পরে সেটা বদলালে object-ও বদলে যায়। দুটি reference একই object দেখার এই অবস্থাকে বলে aliasing.
সমাধানের নাম defensive copy: mutable কিছু ঢোকার সময় কপি রাখুন, বের হওয়ার সময় কপি দিন। ডায়াগ্রামে stack আর heap-এ কী ঘটে দেখুন:
stack heap
main: input ──────┐
├──▶ [ 70, 80, 90 ]
Student: marks ──────┘
দুটি reference, একটিই array
stack heap
main: input ──────┐
├──▶ [ 0, 80, 90 ]
Student: marks ──────┘
Student-কে কেউ ছোঁয়নি, তবু তার নম্বর বদলে গেল
stack heap main: input ─────────▶ [ 0, 80, 90 ] (বাইরের) Student: marks ─────────▶ [ 70, 80, 90 ] (নিজের কপি) input বদলালেও Student-এর কপি অক্ষত
stack heap
Student: marks ──────┐
├──▶ [ 70, 80, 0 ]
main: leak ──────┘
leak[2] = 0;
getter ভেতরের array-ই দিয়ে দিয়েছে — বাইরে থেকে বদল
stack heap Student: marks ─────────▶ [ 70, 80, 90 ] main: copy ─────────▶ [ 70, 0, 90 ] copy[1] = 0; caller তার নিজের কপি বদলাল — Student টের পেল না
- কপি ছাড়া রেখে দেওয়া। Constructor-এ
this.marks = marks;মানে array-র ঠিকানাটি রাখা হলো।main-এরinputআর Student-এরmarksএখন একই array দেখে। - বাইরের বদল ভেতরে ঢুকে পড়ল।
mainনিজের array-তেinput[0] = 0লিখল — হয়তো পরের শিক্ষার্থীর ডেটা ভরার জন্য array-টি আবার ব্যবহার করছে। Student-এর নম্বরও শূন্য হয়ে গেল, অথচ Student-এর কোনো method ডাকাই হয়নি। - ঢোকার পথে কপি।
marks.clone()একটি নতুন array বানিয়ে মানগুলো কপি করে। এখন Student-এর হাতে নিজের array; বাইরে যা খুশি হোক, ভেতরে প্রভাব নেই। - বের হওয়ার পথে ফাঁস। Getter
return marks;করলে caller ভেতরের array-র ঠিকানাই পায়।leak[2] = 0সরাসরি Student-এর নম্বর বদলে দেয় —privateলেখা থাকলেও।privateশুধু field-এর নাম লুকায়, যে object-এর দিকে সে তাকিয়ে আছে তাকে নয়। - বের হওয়ার পথেও কপি।
return marks.clone();দিলে caller পায় নিজের একটি কপি। সে সেটাকে বদলাতে পারে, সাজাতে পারে — Student-এর আসল নম্বর অক্ষত থাকে।
এবার নিজে চালিয়ে দেখুন। এই Student-এর constructor কপি রাখে, getMarks() কপি দেয়, কিন্তু ইচ্ছা করে একটি ভুল getter getMarksLeaky()-ও রাখা হয়েছে:
import java.util.Arrays;
class Student {
private final String name;
private final int[] marks;
Student(String name, int[] marks) {
this.name = name;
this.marks = marks.clone(); // ঢোকার পথে কপি
}
int[] getMarksLeaky() { return marks; } // ভুল: ভেতরের array-ই দিল
int[] getMarks() { return marks.clone(); } // ঠিক: কপি দিল
String report() { return name + " " + Arrays.toString(marks); }
}
class Main {
public static void main(String[] args) {
int[] input = {70, 80, 90};
Student s = new Student("Nadia", input);
input[0] = 0;
System.out.println("1: " + s.report());
s.getMarks()[1] = 0;
System.out.println("2: " + s.report());
s.getMarksLeaky()[2] = 0;
System.out.println("3: " + s.report());
}
}আউটপুট: 1: Nadia [70, 80, 90], 2: Nadia [70, 80, 90], 3: Nadia [70, 80, 0]. প্রথম লাইনে input[0] = 0-এর কোনো প্রভাব নেই, কারণ constructor কপি রেখেছিল। দ্বিতীয় লাইনে getMarks()-এর ফেরত দেওয়া কপিতে বদল — Student অক্ষত। তৃতীয় লাইনে ফাঁস হওয়া getter দিয়ে ভেতরের array-ই বদলে গেছে। আরেকটি শিক্ষা লুকিয়ে আছে: marks field final, তবু ভেতরের মান বদলাল। final শুধু বলে field-টি অন্য array-র দিকে ঘুরবে না; array-র ভেতরের ঘরগুলো আগের মতোই বদলানো যায়। Arrays.toString array সুন্দর করে ছাপে (পর্ব ১৩)।
List হলে কী করবেন, আর immutability-র দিকে এক পা
List শিখবেন পর্ব ৩০-এ, কিন্তু নিয়মটি একই। ভেতরের ArrayList সরাসরি ফেরত দিলে caller add বা clear করে ফেলতে পারে। নিরাপদ উপায় হলো List.copyOf(items) ফেরত দেওয়া (Java 10 থেকে আছে) — এটি একটি অপরিবর্তনীয় কপি, যাতে add করলে UnsupportedOperationException আসে। List.of(...)-ও একইভাবে অপরিবর্তনীয় list বানায়। খেয়াল রাখবেন, clone() বা copyOf হলো shallow copy — ভেতরের element-গুলো নিজেরা mutable object হলে সেগুলো কপি হয় না।
সবচেয়ে সহজ পাহারা হলো এমন object, যা বদলানোই যায় না: সব field private final, কোনো setter নেই, mutable field থাকলে ঢোকা-বেরোনোর পথে কপি। এমন object-কে বলে immutable — String এর সবচেয়ে পরিচিত উদাহরণ (পর্ব ০৮)। ওপরের Temperature ইতিমধ্যে immutable. Immutable object-এর invariant একবার constructor-এ যাচাই করলেই চিরকাল সত্য, আর একাধিক thread একসাথে নিরাপদে পড়তে পারে। final-এর পুরো ব্যবহার আসবে পরের পর্বে, আর immutable ডেটার সবচেয়ে সংক্ষিপ্ত রূপ record পর্ব ২৫-এ।
৮ · ওপরের আর নিচের স্তর — module আর reflection
Module — package-এর ওপরে আরেকটি দেয়াল
Package-এর একটি দুর্বলতা আছে। ধরুন আপনার লাইব্রেরিতে দুটি package: com.abcltech.bank (ব্যবহারকারীদের জন্য) আর com.abcltech.bank.internal (শুধু নিজের কাজে)। internal-এর class-গুলো bank থেকে ব্যবহার করতে হলে তাদের public করতে হয় — কারণ দুটি আলাদা package. কিন্তু public করা মানে সারা পৃথিবীর জন্য খোলা। Java 9 (২০১৭) এই সমস্যার সমাধানে আনে module system (JPMS, Project Jigsaw). একটি module-এর মূলে থাকে module-info.java ফাইল:
module com.abcltech.bank {
exports com.abcltech.bank; // শুধু এই package বাইরে দেখা যাবে
requires java.sql; // এই module-এর ওপর নির্ভর করি
// com.abcltech.bank.internal export করা হয়নি:
// সেখানকার public class-ও অন্য module থেকে অদৃশ্য
}অর্থাৎ module-এ এসে public-এর মানে দাঁড়ায় “export করা package-এর ভেতরে public.” JDK নিজেই কয়েক ডজন module-এ ভাগ করা (java.base, java.sql…), আর তার ভেতরের sun.* ধরনের অভ্যন্তরীণ package এখন বাইরে বন্ধ (ভাবনার প্রশ্ন ৩)। ছোট প্রজেক্ট আর বেশিরভাগ Spring Boot অ্যাপ module ছাড়াই চলে — তখন সব কোড থাকে একটি “unnamed module”-এ। আপাতত এটুকু জানাই যথেষ্ট যে encapsulation-এর সিঁড়িটা এরকম: private → package → module.
Reflection — private-এর তালাও খোলা যায়
Java-তে চলার সময় একটি class-এর field, method খুঁজে বের করে ব্যবহার করার একটি API আছে — reflection (পর্ব ৪৫)। এটি দিয়ে private field-ও পড়া-লেখা যায়:
Field f = Wallet.class.getDeclaredField("balance"); // java.lang.reflect.Field
f.setAccessible(true); // private-এর তালা খোলা
f.setLong(w, -5000); // invariant ভাঙা হলো
System.out.println(w.getBalance()); // -5000এজন্য মনে রাখবেন: private নিরাপত্তা-ব্যবস্থা নয়, নকশার চুক্তি। এটি ভুল আর অসাবধানতা ঠেকায়, ইচ্ছাকৃত আক্রমণ নয়। Framework-গুলো (JPA, কিছু ক্ষেত্রে Jackson, test টুল) ঠিক এই পথে private field-এ মান বসায়। Module system এখানেও দেয়াল তোলে: অন্য module-এর যে package opens করা হয়নি, তার ভেতরে setAccessible(true) করলে InaccessibleObjectException আসে। পাসওয়ার্ড বা গোপন key রক্ষা করতে হয় অন্য উপায়ে — encryption, আলাদা প্রক্রিয়া, সার্ভারের অনুমতি-ব্যবস্থা।
C-তে access modifier নেই; লুকানোর উপায় হলো header-এ শুধু struct-এর নাম দেওয়া (opaque pointer) আর ফাইল-স্তরে static. C++-এ public/private/protected আছে, কিন্তু package-private নেই, class-এ default private আর struct-এ public, আর friend দিয়ে নির্দিষ্ট কাউকে ঢুকতে দেওয়া যায় — Java-তে friend নেই, তার কাছাকাছি কাজ করে package. Python কিছুই আটকায় না: _balance শুধু একটি রীতি, __balance নাম বদলে দেয় মাত্র, আর যাচাই-সহ getter/setter-এর কাজ করে @property. বিস্তারিত দেখুন C++-এর class ও object আর Python-এর property ও dataclass পর্ব।
- Java-র চারটি access level কী, কিছু না লিখলে কোনটি? — private, package-private, protected, public; কিছু না লিখলে package-private, অর্থাৎ শুধু একই package.
- protected আর default-এর পার্থক্য? — দুটিই একই package-এ খোলা; protected অতিরিক্ত অন্য package-এর subclass-কেও উত্তরাধিকার সূত্রে access দেয়।
- Top-level class কি private হতে পারে? — না; top-level class শুধু public বা package-private, nested class private হতে পারে।
- একটি object কি একই class-এর আরেকটি object-এর private field পড়তে পারে? — হ্যাঁ; Java-র access নিয়ম class-ভিত্তিক, object-ভিত্তিক নয়।
- Encapsulation আর abstraction-এর পার্থক্য? — Abstraction ঠিক করে বাইরে কী দেখানো হবে (কী করে); encapsulation ভেতরের ডেটা আর তার নিয়ম লুকিয়ে পাহারা দেয় (কীভাবে করে, তা গোপন)।
৯ · AI-এর যুগে — কোন setter থাকা উচিতই নয়, তা জানা আপনার কাজ
AI-কে “একটি BankAccount class লিখে দাও” বললে প্রায় নিশ্চিতভাবে পাবেন: সব field private, আর প্রতিটির জন্য একটি getter ও একটি setter — setBalance, setAccountNumber সহ। দেখতে “encapsulated”, কিন্তু আসলে public field-এর লম্বা সংস্করণ। Spring প্রজেক্টে AI প্রায়ই সরাসরি Lombok-এর @Data বসিয়ে দেয়:
@Data // Lombok: compile-এর সময় কোড তৈরি করে
public class Account {
private String accountNumber;
private long balance;
}
// তৈরি হয়: সব field-এর getter, সব non-final field-এর setter,
// equals, hashCode, toString আর একটি constructor
// ফলে acc.setBalance(-5000) বৈধ!@Data শুধু-ডেটা class-এর জন্য সুবিধাজনক, আর boilerplate কমায়; কিন্তু নিয়ম-ওয়ালা domain object-এ এটি invariant-এর দরজা খুলে দেয়। AI-এর লেখা কোডে যে ভুলগুলো বারবার দেখা যায়:
| AI যা করে | কেন সমস্যা | আপনি যা চাইবেন |
|---|---|---|
| প্রতিটি field-এ setter, কোনো যাচাই নেই | Invariant রক্ষার কেউ নেই | কাজের নামে method — withdraw, deposit; setter শুধু যেখানে সত্যিই দরকার |
| id বা account number-এর setter | তৈরির পরে পরিচয় বদলে ফেলা যায় | private final, constructor-এ একবার |
ছোট উদাহরণে public field | যেকোনো জায়গা থেকে বদল, পরে ভেতরের নকশা বদলানো যায় না | সব field private |
| ভেতরের list বা array সরাসরি ফেরত দেয় | বাইরে থেকে add, clear, ঘর বদল | List.copyOf বা clone() ফেরত |
সব কিছুর সামনে public | Package-এর ভেতরের সাহায্যকারী class-ও সবার কাছে খোলা | যতটা কম access দিয়ে চলে, ততটা |
AI-কে class লিখতে বা রিভিউ করতে বলার সময় নিয়মগুলো আগেই বলে দিন। নিচের কাঠামোটি কপি করে নিজের প্রয়োজনে বদলে নিন:
AI-এর লেখা class হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১“Information hiding” ধারণাটি এলো ১৯৭২ সালের একটি ছোট গবেষণাপত্র থেকে। David Parnas কী বলেছিলেন, আর Java-র private-এর সাথে তার সম্পর্ক কী?
১৯৭২ সালের ডিসেম্বরে Communications of the ACM পত্রিকায় David L. Parnas-এর একটি প্রবন্ধ ছাপা হয়: “On the Criteria To Be Used in Decomposing Systems into Modules.” শিরোনামটি নিরস, কিন্তু সফটওয়্যার নকশার ইতিহাসে এটি সবচেয়ে বেশি উদ্ধৃত লেখাগুলোর একটি। প্রশ্নটি ছিল সরল: একটি বড় প্রোগ্রামকে কোন নিয়মে ছোট ছোট module-এ ভাগ করব?
দুটি ভাগ, একই প্রোগ্রাম। Parnas একটি ছোট সিস্টেম নিলেন — KWIC index, যা শিরোনামের তালিকা নিয়ে প্রতিটি শব্দ ঘুরিয়ে বর্ণানুক্রমে সাজায়। তিনি দুইভাবে module ভাগ করলেন। প্রথম ভাগটি প্রচলিত, flowchart-এর ধাপ ধরে: ইনপুট পড়া, ঘোরানো, সাজানো, ছাপা। দ্বিতীয় ভাগটি নতুন: প্রতিটি module একটি করে নকশা-সিদ্ধান্ত লুকিয়ে রাখে — যেমন লেখাগুলো মেমরিতে কীভাবে রাখা আছে, তা শুধু একটি module জানে; বাকিরা তাকে শুধু প্রশ্ন করে। দুটি ভাগেই প্রোগ্রাম একই কাজ করে। পার্থক্য দেখা যায় যখন কিছু বদলাতে হয়: স্টোরেজের ধরন বদলালে প্রথম ভাগে প্রায় সব module বদলাতে হয়, দ্বিতীয় ভাগে মাত্র একটি।
মূল নীতি। Parnas-এর পরামর্শ ছিল: যে সিদ্ধান্তগুলো কঠিন বা ভবিষ্যতে বদলাতে পারে, সেগুলো খুঁজে বের করুন, আর প্রতিটিকে একটি module-এর ভেতরে লুকিয়ে রাখুন, যাতে বাকি সিস্টেম সেই সিদ্ধান্তের ওপর নির্ভর না করে। এটাই information hiding. খেয়াল করুন, এখানে “লুকানো” মানে গোপনীয়তা নয় — নির্ভরতা কমানো।
Java-তে প্রতিফলন। এই পর্বের Temperature ঠিক এই নীতি মানে: ভেতরে সেলসিয়াস না কেলভিন, তা একটি লুকানো সিদ্ধান্ত; বাইরের সবাই শুধু getFahrenheit() জানে। private হলো এই নীতিকে compiler দিয়ে বাধ্যতামূলক করার উপায় — Parnas যা নকশার শৃঙ্খলা হিসেবে বলেছিলেন, Java তাকে ভাষার নিয়ম বানিয়েছে। পরের দশকগুলোতে abstract data type, Modula-2-এর module, C++ আর Java-র class, এমনকি আজকের microservice — সবগুলোর পেছনেই এই চিন্তা কাজ করে: প্রতিটি অংশের একটি ছোট, স্থির interface, আর তার পেছনে বদলানোর স্বাধীনতা।
একটি সতর্কতা। Parnas-এর নীতি “সব field-এ getter দাও” বলে না। Getter যদি ভেতরের প্রতিটি সিদ্ধান্ত বাইরে জানিয়ে দেয় (যেমন getCelsiusArray() যা ভেতরের রাখার পদ্ধতিই প্রকাশ করে), তাহলে লুকানো আর কিছু থাকে না।
মূল উপলব্ধি: Module ভাগ করুন বদলের সম্ভাবনা ধরে, কাজের ধাপ ধরে নয়। প্রতিটি class একটি সিদ্ধান্ত লুকাবে — private সেই লুকানোর তালা, আর public method সেই সিদ্ধান্তের ওপর একটি স্থির জানালা।
প্র ০২২০০৩ সালে Allen Holub লিখেছিলেন “Why getter and setter methods are evil.” Getter-setter কি সত্যিই খারাপ? আজকের Java-তে উত্তর কী?
২০০৩ সালে JavaWorld ওয়েবসাইটে Allen Holub-এর প্রবন্ধটি প্রকাশিত হলে Java দুনিয়ায় বেশ হইচই পড়ে। তখন প্রায় প্রতিটি Java class-এ প্রতিটি field-এর জন্য getter আর setter লেখা হতো — IDE এক ক্লিকে বানিয়ে দিত, JavaBeans আর enterprise framework সেটাই চাইত। Holub বললেন, এই অভ্যাস object-oriented নকশার মূল কথাকেই উল্টে দেয়।
Holub-এর যুক্তি। একটি object-এর কাছে তথ্য চাওয়া উচিত নয়, কাজ চাওয়া উচিত — “ask for help, not for information.” Getter বাইরে জানিয়ে দেয় ভেতরে কী ধরনের ডেটা আছে; সবাই সেই ধরনের ওপর নির্ভর করতে শুরু করে। ধরুন getBalance() একটি int ফেরত দেয়, আর সেটা একশো জায়গায় ব্যবহার হয়েছে। এখন বড় অঙ্কের জন্য long বা BigDecimal লাগলে একশো জায়গাই বদলাতে হবে — অর্থাৎ ভেতরের সিদ্ধান্ত আর লুকানো নেই। Setter আরও খারাপ: নিয়ম চলে যায় caller-এর কাছে। প্রায় একই সময়ে Martin Fowler “AnemicDomainModel” নামে একটি লেখায় এমন নকশাকে anti-pattern বলেন, যেখানে domain object শুধু getter-setter-এর বস্তা আর সব যুক্তি থাকে আলাদা “service” class-এ।
বিপক্ষের যুক্তি। বাস্তবে কিছু তথ্য বাইরে দেখাতেই হয় — স্ক্রিনে নাম, রিপোর্টে ব্যালান্স, JSON-এ order-এর বিবরণ। প্রতিটির জন্য object-এর ভেতরে “নিজেকে ছাপো” method রাখলে domain class-এ UI আর JSON-এর দায়িত্ব ঢুকে যায়, যা নিজেই খারাপ নকশা। আর framework (Hibernate, Jackson, Spring) দীর্ঘদিন JavaBeans রীতির ওপর নির্ভর করেছে, তাই সম্পূর্ণ এড়ানো বাস্তবসম্মত ছিল না।
আজকের ভারসাম্য। আধুনিক Java দুই ধরনের object আলাদা করে দেখে। আচরণ-ওয়ালা object (Account, Order) — এখানে setter প্রায় নেই, অবস্থা বদলায় অর্থপূর্ণ method দিয়ে, getter শুধু যা দেখানো দরকার। শুধু-ডেটা object (API-র request/response, DTO) — এখানে Java 16 থেকে record আছে, যার accessor আছে কিন্তু setter নেই, সব immutable. ফলে Holub-এর মূল অভিযোগ — অবাধ setter — আধুনিক কোডে অনেকটাই কমে এসেছে, আর getter টিকে আছে সীমিত, সচেতন ব্যবহারে।
মূল উপলব্ধি: Getter-setter নিজে খারাপ নয়; খারাপ হলো না ভেবে প্রতিটি field-এ বসানো। প্রতিটি setter-কে প্রশ্ন করুন: বাইরের কেউ কেন সরাসরি এটা বদলাবে? উত্তর না থাকলে সেটি একটি অর্থপূর্ণ method হওয়া উচিত, অথবা থাকাই উচিত নয়।
প্র ০৩Java 9-এর module system আর Java 17-এর JEP 403 — JDK নিজের ভেতরের class (যেমন sun.misc.Unsafe) কেন বাইরে বন্ধ করল, আর তাতে কার কী অসুবিধা হলো?
Java-র শুরু থেকেই JDK-র ভেতরে কিছু class ছিল শুধু JDK-র নিজের ব্যবহারের জন্য — sun.* আর com.sun.* package-এ। Sun বারবার সতর্ক করেছিল যে এগুলো সমর্থিত API নয়, যেকোনো সংস্করণে বদলাতে পারে। কিন্তু তখন সেগুলোকে আটকানোর কোনো ভাষাগত উপায় ছিল না: class যেহেতু public, যে কেউ import করতে পারত, আর reflection দিয়ে private member-ও ছোঁয়া যেত।
sun.misc.Unsafe-এর গল্প। এই class দিয়ে JVM-এর নিরাপত্তা এড়িয়ে সরাসরি মেমরি পড়া-লেখা, heap-এর বাইরে মেমরি নেওয়া, constructor ছাড়াই object বানানো আর খুব দ্রুত atomic অপারেশন করা যেত। নাম “Unsafe” হলেও উচ্চ-গতির লাইব্রেরিগুলো একে প্রায় অপরিহার্য বানিয়ে ফেলেছিল — নেটওয়ার্কিং, serialization, concurrency আর mocking-এর বহু জনপ্রিয় লাইব্রেরি এর ওপর নির্ভর করত। ফলে JDK-র ভেতরের একটি “গোপন” সিদ্ধান্ত বদলালেই বাইরের অর্ধেক ইকোসিস্টেম ভাঙার ঝুঁকি — Parnas-এর ভাষায়, information hiding ব্যর্থ।
ধাপে ধাপে দেয়াল। Java 9 (২০১৭)-এ JDK module-এ ভাগ হলো, আর অভ্যন্তরীণ package-গুলো export করা হলো না। কিন্তু এক ধাক্কায় বন্ধ করলে বহু অ্যাপ চলত না, তাই একটি অস্থায়ী ছাড় রাখা হলো: --illegal-access=permit ছিল default, শুধু সতর্কবার্তা দেখাত। Java 16-এ (JEP 396) default হলো “deny”. Java 17-এ JEP 403 সেই option-টিকেই অকার্যকর করে দিল (দিলে শুধু একটি সতর্কবার্তা আসে) — JDK-র অভ্যন্তরীণ অংশ এখন কড়াভাবে encapsulated, শুধু --add-opens-এর মতো স্পষ্ট command-line flag দিয়ে নির্দিষ্ট package খোলা যায়। ব্যতিক্রম রাখা হলো কয়েকটি “critical internal API”-র জন্য, যার মধ্যে sun.misc.Unsafe — এটি jdk.unsupported module দিয়ে এখনো পাওয়া যায়, কারণ বিকল্প তখনো সম্পূর্ণ তৈরি হয়নি।
বিকল্প তৈরি করা। JDK শুধু দরজা বন্ধ করেনি, সমর্থিত দরজাও বানিয়েছে: Java 9-এ VarHandle (atomic অপারেশনের জন্য), আর Java 22-এ স্থায়ী হওয়া Foreign Function & Memory API (heap-এর বাইরের মেমরির জন্য)। এরপর Java 23-এ (JEP 471) Unsafe-এর মেমরি-অ্যাক্সেস method-গুলো ভবিষ্যতে সরানোর জন্য deprecated ঘোষণা করা হয়েছে।
অসুবিধা কার? পুরোনো লাইব্রেরি আর অ্যাপের — Java 8 থেকে নতুন সংস্করণে যেতে অনেক দলকে লাইব্রেরি আপডেট করতে বা --add-opens যোগ করতে হয়েছে, যা Java 8-এ দীর্ঘদিন আটকে থাকার একটি কারণ। লাভ JDK-র: এখন ভেতরের কোড নির্ভয়ে বদলানো যায়, নিরাপত্তা-ঝুঁকি কমেছে।
মূল উপলব্ধি: যে encapsulation শুধু “দয়া করে ব্যবহার করবেন না” লেখা সতর্কবার্তার ওপর নির্ভর করে, বড় ইকোসিস্টেমে তা টেকে না — লোকে ব্যবহার করবেই। তালা দিতে হয় ভাষা বা runtime দিয়ে, আর তালা দেওয়ার আগে একটি সমর্থিত বিকল্প দিতে হয়।
প্র ০৪কিছু না লিখলে Java সবচেয়ে কড়া private না দিয়ে package-private দেয় কেন? এই default কি ভালো সিদ্ধান্ত ছিল?
প্রতিটি ভাষাকে ঠিক করতে হয়: প্রোগ্রামার কিছু না লিখলে কী হবে? উত্তরটি দেখায় ভাষা কোন অবস্থাকে “স্বাভাবিক” মনে করে। Java বেছে নিয়েছে মাঝামাঝি একটি স্তর — একই package-এর সবাই দেখবে, বাইরের কেউ নয়।
পক্ষের যুক্তি। Java-র নকশায় package শুধু ফোল্ডার নয়, সহযোগিতার একক। একই package-এর class-গুলো সাধারণত একই দল লেখে, একসাথে বদলায়, একসাথে ছাড়া হয়। তাদের মধ্যে সাহায্যকারী method আর class ভাগাভাগি করা স্বাভাবিক — প্রতিটির জন্য public করতে হলে বাইরের জগতের কাছেও খুলে যেত। C++-এ যেখানে নির্দিষ্ট class-কে ঢুকতে দিতে friend লিখতে হয়, Java সেই প্রয়োজন package দিয়ে মেটায়। ইউনিট টেস্টও এর সুবিধা পায়: টেস্ট class একই package-এ রাখলে package-private method পরীক্ষা করা যায় (পর্ব ৪৭)।
বিপক্ষের যুক্তি। প্রথমত, default-এর কোনো keyword নেই। কোড পড়ে বোঝা যায় না, লেখক ইচ্ছা করে package-private রেখেছেন নাকি private লিখতে ভুলে গেছেন। দ্বিতীয়ত, package-এর দেয়াল দুর্বল: যে কেউ অন্য jar-এ একই নামের package বানিয়ে ভেতরে ঢুকতে পারত (“split package”) — named module-এর ক্ষেত্রে module system এটা বন্ধ করেছে। তৃতীয়ত, “সবচেয়ে কম access দিয়ে শুরু করুন” — নিরাপদ নকশার এই নীতির সাথে default-টা মেলে না। Joshua Bloch-এর Effective Java বইয়ের একটি বিখ্যাত item-এর শিরোনামই হলো “Minimize the accessibility of classes and members”: প্রতিটি member যতটা সম্ভব কম দৃশ্যমান রাখুন।
অন্য ভাষাগুলো কী করেছে। C#-এ class-এর member-এর default private, আর top-level type-এর default internal (একই assembly). Kotlin-এর default উল্টো — public; তবে Kotlin একটি internal modifier দিয়েছে, যা একটি module-এর ভেতরে সীমিত। Scala-তেও default public. অর্থাৎ কোনো সর্বসম্মত উত্তর নেই; প্রতিটি ভাষা ঠিক করেছে কোন ভুলটা বেশি সহ্যযোগ্য — বেশি খোলা রাখা, নাকি প্রতিবার লিখতে বাধ্য করা।
ব্যবহারিক পরামর্শ। Field-এ সবসময় স্পষ্টভাবে private লিখুন। Package-private ব্যবহার করুন সচেতনভাবে — যেমন package-এর ভেতরের helper class — আর দরকার হলে একটি ছোট comment দিন। Public করার আগে ভাবুন: এটা একবার public হলে বাইরের কেউ ব্যবহার শুরু করবে, আর তখন বদলানো কঠিন।
মূল উপলব্ধি: Java-র default package-কে নকশার একক হিসেবে মানে, কিন্তু নিরাপদ অভ্যাস হলো উল্টো দিক থেকে ভাবা — সব কিছু private দিয়ে শুরু, তারপর যতটুকু দরকার ততটুকু খোলা।
প্র ০৫Python বলে “we are all consenting adults” — কিছুই আটকায় না। Java compiler দিয়ে বাধ্য করে। কোন পথ ভালো?
Python-এ private keyword নেই। প্রচলিত রীতি হলো নামের আগে একটি underscore — self._balance মানে “এটি ভেতরের জিনিস, বাইরে থেকে ছোঁবেন না।” কিন্তু ছুঁলে কেউ থামায় না। দুটি underscore (__balance) দিলে Python নামটি বদলে _Account__balance করে দেয় (name mangling) — এর উদ্দেশ্য subclass-এর সাথে নামের সংঘর্ষ ঠেকানো, লুকানো নয়; বদলানো নামে এখনো ঢোকা যায়। Python সম্প্রদায়ে এই দর্শনকে বলা হয় “we are all consenting adults here” — আমরা সবাই প্রাপ্তবয়স্ক, রীতি জানি; কেউ ইচ্ছা করে ভাঙলে দায়িত্ব তার।
Python-পথের সুবিধা। Debugging আর পরীক্ষায় ভেতরে উঁকি দেওয়া সহজ; জরুরি অবস্থায় কোনো লাইব্রেরির ভেতরের অংশ ব্যবহার করা যায়; কোড ছোট থাকে। আর @property থাকায় প্রথমে সাধারণ attribute রেখে পরে প্রয়োজন হলে যাচাই-সহ getter/setter বসানো যায়, caller-এর কোড না বদলেই — তাই Python-এ “আগেভাগে সব getter লিখে রাখো” দরকারই হয় না।
Java-পথের সুবিধা। বড় দলে, বহু বছর ধরে চলা কোডবেসে “রীতি” ভাঙে — নতুন সদস্য জানেন না, তাড়াহুড়োয় কেউ শর্টকাট নেন, আর AI রীতির তোয়াক্কা করে না। Compiler-এর তালা এই ভুলগুলো চলার আগেই ধরে, আর IDE-কে নিশ্চিতভাবে বলতে দেয় কোন method কোথায় ব্যবহার হতে পারে — ফলে private method নির্ভয়ে বদলানো বা মুছে ফেলা যায়। Library-লেখকের জন্য এটি স্পষ্ট চুক্তি: public যা, তার দায়িত্ব নেব; বাকিটা বদলাতে পারি।
দুটি পথই নিরাপত্তা-ব্যবস্থা নয়। Java-তেও reflection দিয়ে private ভাঙা যায় (অংশ ৮), শুধু module system সেটা কঠিন করেছে। এক সময় Java-র Security Manager দিয়ে এমন কাজ আটকানোর চেষ্টা হতো, কিন্তু জটিলতা আর কম ব্যবহারের কারণে Java 17-এ (JEP 411) সেটি ভবিষ্যতে সরানোর জন্য deprecated করা হয়, আর Java 24-এ (JEP 486) স্থায়ীভাবে নিষ্ক্রিয় করা হয়েছে। অর্থাৎ দুই ভাষাতেই access modifier আসলে যোগাযোগ: কোডের লেখক পাঠককে জানাচ্ছেন কোনটা স্থির চুক্তি আর কোনটা ভেতরের ব্যাপার। পার্থক্য শুধু সেই বার্তা কে বলবৎ করে — মানুষ, না compiler.
বাস্তব প্রবণতা। দুই দিক একে অপরের কাছে আসছে। Python-এ type hint আর mypy-র মতো টুল এখন Final বা protocol যাচাই করে; Java-তে record আর অপরিবর্তনীয় collection নিয়ম কমিয়ে সরলতা বাড়াচ্ছে।
মূল উপলব্ধি: প্রশ্নটা “কে ঠিক” নয়, “কোন পরিবেশে কোন ভুল বেশি দামি।” ছোট দল আর দ্রুত পরীক্ষায় রীতিই যথেষ্ট; বড়, দীর্ঘস্থায়ী, বহু-দলের সিস্টেমে compiler-এর তালা সস্তা বীমা।
অনুশীলন
বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — ওপরের কোড বদলে দেখা আর ভাবাই আসল কাজ।
-
Compiler-কে পরীক্ষা করুন: অংশ ২-এর ওয়ালেট প্রোগ্রামে শেষ লাইনের
//সরিয়ে দিন, তারপর চালান। কোন error আসে? এরপরWallet-এbalance-এর সামনে থেকেprivateমুছে আবার চালান — এবার কী হয়, আর ফলাফল কী ছাপে?প্রথমবার:
error: balance has private access in Wallet— প্রোগ্রাম চলেই না। দ্বিতীয়বারbalanceহয়ে যায় package-private, আর অনলাইন runner-এMainএকই package-এ, তাই কোড compile হয়। লুপের পরে ব্যালান্স ছিল 5000, কিন্তুw.balance = -5000;চলেছেFinalছাপার পরে, তাই আউটপুট আগের মতোই দেখায় — অথচ object-টি এখন ঋণাত্মক ব্যালান্সে আছে। লাইনটিprintln-এর আগে সরালেFinal: -5000ছাপবে। শিক্ষা:privateসরিয়ে দিলে withdraw-এর তিনটি নিয়মই এক লাইনে অর্থহীন। -
Setter-এর ফাঁক খুঁজুন: অংশ ৫-এর
Student.setGpaযাচাই করেgpa < 0.0 || gpa > 5.0. ভাবুন:setGpa(Double.NaN)ডাকলে কী হবে? (NaNমানে “not a number”, যেমন0.0 / 0.0-এর ফল।) কীভাবে ঠিক করবেন?NaN-এর পরিচয় পেয়েছেন পর্ব ০৭-এ। এর সাথে<,>,<=,>=,==— সব তুলনাইfalseদেয়। তাইNaN < 0.0মিথ্যা,NaN > 5.0-ও মিথ্যা — শর্ত পার হয়ে যায়, আর GPA হয়ে যায়NaN. Invariant ভাঙল, কোনো error ছাড়াই। সমাধান: শর্তটি উল্টে “ভালো মান কোনটা” লিখুন —if (!(gpa >= 0.0 && gpa <= 5.0)) throw ...; এতে NaN-ও আটকায়। অথবা আলাদা করেDouble.isNaN(gpa)যাচাই করুন। শিক্ষা: যাচাইয়ের নিয়ম লিখুন “কী অনুমোদিত” ধরে, “কী নিষিদ্ধ” ধরে নয়। -
আউটপুট অনুমান করুন: অংশ ৭-এর
Studentপ্রোগ্রামে constructor-এর লাইনটি বদলেthis.marks = marks;করুন (কপি ছাড়া)। না চালিয়ে তিনটি লাইনের আউটপুট লিখুন, তারপর চালিয়ে মিলিয়ে নিন।1: Nadia [0, 80, 90],2: Nadia [0, 80, 90],3: Nadia [0, 80, 0]. এখনinputআর Student-এরmarksএকই array, তাইinput[0] = 0সরাসরি Student-এ পৌঁছায়। দ্বিতীয় লাইন বদলায় না, কারণgetMarks()এখনো কপি দেয়। তৃতীয় লাইনে ফাঁস হওয়া getter আবার ভেতরে বদল ঘটায়। অর্থাৎ নিরাপদ হতে হলে দুটি দরজাই বন্ধ রাখতে হয় — ঢোকার পথ আর বের হওয়ার পথ। -
Access level বেছে নিন: একটি মোবাইল ব্যাংকিং লাইব্রেরি, package
com.abcltech.bank. নিচের প্রতিটির জন্য সবচেয়ে কম কিন্তু যথেষ্ট access level বলুন: (ক)Account-এরbalancefield; (খ)Account.withdraw(long), যা অ্যাপের অন্য package থেকে ডাকা হবে; (গ)PinHasherclass, যা শুধু এই package-এরAccountআরLoginServiceব্যবহার করে; (ঘ)Account.applyMonthlyFee(), যা অন্য package-এরStudentAccount extends Accountনিজের মতো করে বদলাতে চায়।(ক)
private— invariant-এর মূল ডেটা, শুধুAccount-এর method বদলাবে। (খ)public— অন্য package থেকে ডাকতে হবে, আর ভেতরে নিয়ম যাচাই আছে বলে খোলা রাখা নিরাপদ। (গ) package-private (class-এর সামনে কিছু না লেখা) — বাইরের কেউ এর অস্তিত্বই জানবে না, ভবিষ্যতে hashing-এর পদ্ধতি বদলালে বাইরের কিছু ভাঙবে না। (ঘ)protected— অন্য package-এর subclass পর্যন্ত পৌঁছাতে হবে, কিন্তু সাধারণ caller-এর এটা ডাকার কথা নয় (subclass-এ বদলানো মানে overriding — পর্ব ১৯–২০)। মূল নীতি: সবচেয়ে কড়াটি দিয়ে শুরু করুন, প্রয়োজন প্রমাণ হলে তবেই খুলুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমJava-তে একটি encapsulated class নকশা করার ধাপগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📜 Class-এর invariant লিখে ফেলা — কোন নিয়ম সবসময় সত্য থাকবে
- 🔒 সব field private করা, যেগুলো বদলায় না সেগুলো final
- 🏗️ Constructor-এ শুরুর মান যাচাই করা, mutable input কপি করা
- ⚙️ অবস্থা বদলানোর জন্য কাজের নামে method, যারা নিয়ম যাচাই করে
- 👀 শুধু দরকারি getter দেওয়া, mutable ডেটা হলে কপি ফেরত
Java-তে কোনো member-এর সামনে কোনো access modifier না লিখলে সেটি কোথা থেকে ব্যবহার করা যায়?
- সব জায়গা থেকে
- শুধু একই package-এর ভেতর থেকে
- শুধু নিজের class থেকে
- একই package আর অন্য package-এর subclass থেকে
class Wallet { private long balance; boolean richerThan(Wallet o) { return balance > o.balance; } } — এই কোড সম্পর্কে কোনটি সত্য?
- ঠিকঠাক compile হয়
- compile error: balance has private access in Wallet
- compile হয়, কিন্তু o.balance-এর জায়গায় getter বাধ্যতামূলক বলে সতর্কবার্তা আসে
- চলার সময় IllegalAccessException আসে
একটি .java ফাইলের top-level class-এর সামনে নিচের কোনটি বৈধভাবে লেখা যায়?
- শুধু private
- protected অথবা public
- public অথবা কিছুই না
- চারটির যেকোনোটি
private int[] marks; field-এর জন্য int[] getMarks() { return marks; } লেখা হলো। এর প্রধান সমস্যা কী?
- compile error হয়, কারণ private field ফেরত দেওয়া যায় না
- কোনো সমস্যা নেই, Java নিজেই array কপি করে ফেরত দেয়
- প্রতিবার বিশাল array কপি হয় বলে প্রোগ্রাম ধীর হয়
- caller ভেতরের array বদলে ফেলতে পারে, private থাকা সত্ত্বেও
JavaBeans রীতি অনুযায়ী private boolean active; field-এর getter-এর প্রচলিত নাম কী?
- active()
- isActive()
- hasActive()
- getactive()
private final int[] marks = {70, 80, 90}; — class-এর ভেতরে final এখানে ঠিক কী নিশ্চিত করে?
- array-র কোনো ঘরের মান আর বদলানো যাবে না
- field-টি বাইরের class থেকে অদৃশ্য থাকবে
- marks-এ আর অন্য কোনো array বসানো যাবে না, কিন্তু ঘরের মান বদলানো যাবে
- getter ডাকলে array-র কপি নিজে থেকে ফেরত যাবে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- Encapsulation = ডেটা + নিয়ম এক জায়গায়, আর ডেটায় সরাসরি হাত নয়। Field private, অবস্থা বদলায় শুধু method দিয়ে, যারা invariant যাচাই করে।
- চারটি level:
private→ package-private (কিছু না লেখা) →protected(package + subclass) →public. Top-level class শুধু public বা package-private; private নিয়ম class-ভিত্তিক, object-ভিত্তিক নয়। - Package = নাম + ফোল্ডার (
com.abcltech.bank→com/abcltech/bank/);importশুধু নাম ছোট করে, কোড আনে না; sub-package আলাদা package. - Getter-setter শুধু যাচাই, read-only বা হিসাব করা মানের জন্য; বাকিটা Tell, Don't Ask —
acc.withdraw(500). JavaBeans নাম (getX/isX/setX) framework-এর জন্য জরুরি। - Mutable ডেটা ঢোকা আর বেরোনোর পথে কপি করুন;
finalreference স্থির করে, ভেতরের মান নয়। Module আরেক স্তরের দেয়াল; reflection private ভাঙতে পারে — private নকশার চুক্তি, নিরাপত্তা নয়।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ১৮ · static আর finalপরবর্তী পর্বclass-এর নিজের ডেটা, constant আর অপরিবর্তনীয় field.
- Java প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- Python · property আর dataclassপাশের ট্র্যাকরীতি-নির্ভর encapsulation আর @property দিয়ে যাচাই।
- C++ · class ও objectগভীরে যানpublic-private দিয়ে invariant রক্ষা, friend আর const member function.