এই পর্বে যা শিখবেন
- কেন int ও String constant বিপজ্জনক, আর enum কীভাবে compiler-কে পাহারাদার বানায়
- enum ঘোষণা,
==দিয়ে তুলনা, switch expression আর exhaustiveness values,valueOf,name,ordinal,compareTo— আর ordinal সংরক্ষণের ফাঁদ- Field-constructor-method-সহ enum, প্রতিটি মানের নিজস্ব আচরণ, interface implement করা
- EnumSet, EnumMap, enum singleton আর enum দিয়ে state machine; কখন enum নয় বরং sealed/record
১ · কেন enum — int আর String constant-এর ফাঁদ
একটি অনলাইন শপের অর্ডার ভাবুন। প্রতিটি অর্ডার কোনো এক সময়ে ঠিক একটি অবস্থায় থাকে: অপেক্ষমাণ, টাকা পরিশোধিত, পাঠানো হয়েছে, পৌঁছে গেছে। পঞ্চম কোনো অবস্থা নেই। একই রকম আরও অনেক জিনিস আছে — সপ্তাহের সাত দিন, রক্তের আটটি গ্রুপ, ট্রাফিক সিগন্যালের তিনটি বাতি। এদের সবার একটি মিল: মানটি কয়েকটি আগে থেকে জানা পছন্দের একটি, এর বাইরে কিছু হতে পারে না।
পুরোনো Java কোডে (আর এখনো AI-এর লেখা অনেক কোডে) এমন মান লেখা হতো সংখ্যা দিয়ে — পর্ব ১৮-এর constant ব্যবহার করে:
static final int STATUS_PENDING = 0;
static final int STATUS_PAID = 1;
static final int STATUS_SHIPPED = 2;
static final int STATUS_DELIVERED = 3;
void updateStatus(int status) { ... }
updateStatus(STATUS_PAID); // ঠিক
updateStatus(42); // এটাও compile হয়! 42 কোন অবস্থা?
updateStatus(7 * 3 - 20); // 1 — কাকতালীয়ভাবে PAIDনাম দেওয়ায় কোড পড়া একটু সহজ হয়েছে, কিন্তু টাইপ এখনো int. Compiler-এর চোখে STATUS_PAID আর একটি magic number 42-এর কোনো পার্থক্য নেই — দুটোই int. সমস্যাগুলো একে একে দেখুন:
- যেকোনো সংখ্যা চলে যায়।
updateStatus(42)compile হয়; ভুল ধরা পড়ে (যদি পড়ে) চলার সময়, হয়তো গ্রাহকের অভিযোগে। - আলাদা ধারণা মিশে যায়।
BLOOD_A_POS = 1আরSTATUS_PAID = 1—updateStatus(BLOOD_A_POS)লিখলেও compiler চুপ। - ছাপালে অর্থহীন। log-এ দেখবেন
status=2; 2 মানে কী, খুঁজতে কোডে যেতে হবে। - সব মান গোনা যায় না। “সব অবস্থার তালিকা দেখাও” — এর জন্য আলাদা array নিজে বানিয়ে রাখতে হয়, আর নতুন অবস্থা যোগ হলে সেটা হালনাগাদ করতে ভুলে যাওয়া সহজ।
অনেকে String দিয়ে সমাধান খোঁজেন: order.setStatus("SHIPPED"). ছাপালে অর্থ বোঝা যায়, কিন্তু নতুন বিপদ: if (status.equals("SHIPED")) — একটি P কম। এটাও নির্ভুলভাবে compile হয়, আর শর্তটি কখনো সত্য হয় না। কেউ লিখবেন "shipped", কেউ "Shipped" — তিনটিই বৈধ String, তিনটিই আলাদা।
হাসপাতালের রক্তদান ফর্ম দুইভাবে বানানো যায়। একটিতে ফাঁকা ঘর: “রক্তের গ্রুপ: ______” — কেউ লিখবেন “B+”, কেউ “বি পজিটিভ”, কেউ ভুল করে “B++”. অন্যটিতে আটটি টিক-বক্স: A+, A−, B+, B−, AB+, AB−, O+, O− — এর বাইরে কিছু লেখার উপায়ই নেই। enum হলো সেই টিক-বক্সের ফর্ম। কোন কোন পছন্দ আছে, তা টাইপ নিজেই বলে দেয়, আর বাইরের কিছু ঢোকানোর চেষ্টা compiler ফেরত পাঠায়।
Java-র উত্তর enum (enumeration — গুনে গুনে বলা তালিকা)। পর্ব ১০-এ ভাড়ার হিসাবে Passenger enum এক ঝলক দেখেছিলেন; এবার পুরোটা। এই পর্বের মূল ধারণা একটি বাক্যে: enum এমন একটি টাইপ বানায়, যার সম্ভাব্য সব মান আপনি ঘোষণার সময়েই লিখে দেন — আর compiler নিশ্চিত করে এর বাইরে কোনো মান কোথাও আসবে না। একে বলে type safety.
১৯৯৬ সালের Java 1.0-তে enum ছিল না — C-এর enum আসলে শুধু int, তাই Java-র নকশাকারীরা সেটা বাদ দিয়েছিলেন। ফলে প্রায় আট বছর Java প্রোগ্রামাররা int constant লিখেছেন, অথবা নিজে হাতে একটি নিরাপদ কৌশল বানিয়েছেন, যার নাম typesafe enum pattern (ভাবনার প্রশ্ন ১)। ২০০৪ সালে Java 5 সেই কৌশলটিকেই ভাষার অংশ বানিয়ে enum keyword নিয়ে আসে।
২ · প্রথম enum — ঘোষণা, ব্যবহার আর ==
ঘোষণা এক লাইনের। class-এর জায়গায় enum, আর ভেতরে কমা দিয়ে সব সম্ভাব্য মানের নাম:
enum OrderStatus { PENDING, PAID, SHIPPED, DELIVERED }
class Order {
private OrderStatus status = OrderStatus.PENDING; // টাইপ এখন OrderStatus
void updateStatus(OrderStatus s) { status = s; }
boolean isDone() { return status == OrderStatus.DELIVERED; }
}ভেতরের প্রতিটি নামকে বলে enum constant. নিয়ম অনুযায়ী এগুলো সব বড় হাতে, শব্দের মাঝে underscore — UPPER_SNAKE_CASE, ঠিক অন্য constant-এর মতো। এখন সেই পুরোনো ভুলটি আবার করে দেখুন — updateStatus(42):
Main.java:6: error: incompatible types: int cannot be converted to OrderStatus
updateStatus(42);
^
Note: Some messages have been simplified; recompile with -Xdiags:verbose to get full output
1 error
এটাই পুরো পার্থক্য। OrderStatus টাইপের জায়গায় শুধু চারটি জিনিস বসতে পারে: PENDING, PAID, SHIPPED, DELIVERED (আর reference টাইপ বলে null — সে কথায় পরে আসছি)। কোনো সংখ্যা নয়, কোনো String নয়, অন্য enum-এর মানও নয়। ভুলটি ধরা পড়ল চালানোর আগেই — আপনার কম্পিউটারে, গ্রাহকের ফোনে নয়।
enum আসলে কী — javac-এর ভেতরে এক নজর
C-তে enum মানে নাম দেওয়া int. Java-তে সম্পূর্ণ আলাদা: enum একটি class, আর প্রতিটি constant সেই class-এর একটি object. javac আপনার এক লাইনের ঘোষণা থেকে কী বানায়, ধাপে ধাপে দেখুন (OpenJDK 21-এ reflection দিয়ে যাচাই করা):
enum OrderStatus { PENDING, PAID, SHIPPED, DELIVERED }
এক লাইন। কোনো class, field, constructor চোখে পড়ে না।
final class OrderStatus extends java.lang.Enum<OrderStatus> { public static final OrderStatus PENDING; public static final OrderStatus PAID; public static final OrderStatus SHIPPED; public static final OrderStatus DELIVERED; private static final OrderStatus[] $VALUES; ... } প্রতিটি নাম = একটি public static final field
static { // compiler-এর লেখা
PENDING = new OrderStatus("PENDING", 0);
PAID = new OrderStatus("PAID", 1);
SHIPPED = new OrderStatus("SHIPPED", 2);
DELIVERED = new OrderStatus("DELIVERED", 3);
$VALUES = $values(); // চারটির array
}
heap-এ ঠিক ৪টি object — এর বেশি কখনো নয়
public static OrderStatus[] values() // $VALUES-এর কপি public static OrderStatus valueOf(String name) private OrderStatus(String name, int ordinal) new OrderStatus() → error: enum classes may not be instantiated বাইরের কেউ নতুন object বানাতে পারে না
Order a: status ──┐ Order b: status ──┼──► [ PAID | name="PAID", ordinal=1 ] Order c: status ──┘ a.status == b.status → true হাজার অর্ডার, কিন্তু PAID object একটিই
- সংক্ষিপ্ত ঘোষণা। আপনি শুধু নামগুলো লেখেন। বাকি সব javac নিজে তৈরি করে — এজন্যই enum এত ছোট দেখায়, অথচ ভেতরে পুরো একটি class.
- একটি final class. javac একটি class বানায়, যা java.lang.Enum extends করে আর
final— তাই কেউextends OrderStatusলিখে নতুন মান ঢোকাতে পারে না। প্রতিটি constant একটিpublic static finalfield, যার টাইপ OrderStatus নিজেই।$VALUESহলো compiler-এর বানানো একটি লুকানো array. - Object তৈরি হয় একবারই। Class যখন প্রথম ব্যবহার হয় (class initialization), compiler-এর লেখা static initializer ঘোষণার ক্রমে চারটি object বানায়। প্রত্যেকে জানে নিজের নাম আর ক্রমিক নম্বর (ordinal) — ০ থেকে শুরু। এরপর পুরো প্রোগ্রামে আর কখনো নতুন OrderStatus object তৈরি হয় না।
- Method পাওয়া যায়, constructor নয়।
values()আরvalueOf()compiler যোগ করে। Constructorprivate, তাই বাইরে থেকেnew OrderStatus()লিখলে javac বলেenum classes may not be instantiated(OpenJDK 21-এর আসল মেসেজ)। Reflection দিয়েও জোর করে বানানো যায় না (ভাবনার প্রশ্ন ৩)। - তাই
==নিরাপদ। যেহেতু প্রতিটি মানের ঠিক একটিই object, “একই মান” আর “একই object” এখানে একই কথা। পর্ব ০৭-এ শিখেছিলেন object তুলনায়==বিপজ্জনক (reference equality) — enum সেই নিয়মের ব্যতিক্রম। এখানে==-ই সঠিক পছন্দ।
== না equals?
দুটোই কাজ করে, কারণ Enum-এর equals ভেতরে ==-ই করে। তবু == ভালো, দুটি কারণে। এক, status == OrderStatus.PAID-এ status null হলে শুধু false আসে; status.equals(OrderStatus.PAID)-এ আসে NullPointerException. দুই, ==-এ compiler টাইপ মিলিয়ে দেখে — status == BloodGroup.A_POS লিখলে compile error (incomparable types: OrderStatus and BloodGroup), অথচ equals যেকোনো Object নেয়, তাই চুপচাপ false দেয়।
C-তে enum শুধু নাম দেওয়া int — enum Status s = 42; চলে, আর নামগুলো বাইরের scope-এ ছড়িয়ে পড়ে। C++11-এর enum class scoped, নিজে থেকে int-এ বদলায় না, কিন্তু ভেতরে তবু একটি পূর্ণসংখ্যা — field বা method রাখা যায় না। Python-এর class Status(Enum) Java-র কাছাকাছি: প্রতিটি member একটি object, আছে .name আর .value. Java-র enum এদের মধ্যে সবচেয়ে “class-এর মতো” — constructor, field, method, interface সবই চলে। তুলনা করতে দেখুন C-র struct, union ও enum আর C++-এর enum class.
৩ · enum আর switch — সব মান ঢাকা হলো কি না compiler দেখে
পর্ব ১০-এ switch expression শিখেছেন। enum-এর সাথে এটি সবচেয়ে শক্তিশালী জুটি। ঢাকার একটি মোড়ের ট্রাফিক সিগন্যাল ভাবুন:
enum Signal { RED, YELLOW, GREEN }
static int seconds(Signal s) {
return switch (s) {
case RED -> 60;
case YELLOW -> 5;
case GREEN -> 45;
}; // default নেই — দরকারও নেই
}দুটি জিনিস খেয়াল করুন। এক, case-এ শুধু নাম — case RED, Signal.RED লিখতে হয় না, কারণ switch জানে s কোন টাইপের। দুই, কোনো default নেই। int-এর switch expression-এ default বাধ্যতামূলক, কারণ সম্ভাব্য int অসংখ্য। কিন্তু Signal-এর সম্ভাব্য মান মাত্র তিনটি, আর তিনটিরই case আছে — compiler নিজে গুনে নিশ্চিত হয়। একে বলে exhaustiveness. এখন কেউ YELLOW-এর লাইনটি ভুলে মুছে দিলে:
Main.java:5: error: the switch expression does not cover all possible input values
return switch (s) {
^
1 error
আরও গুরুত্বপূর্ণ ঘটনা ঘটে ভবিষ্যতে। ধরুন ছয় মাস পরে কেউ enum-এ FLASHING_YELLOW যোগ করলেন। পুরো প্রজেক্টে যতগুলো switch expression Signal-এর ওপর চলে, প্রতিটি সাথে সাথে compile error দেবে — আর ঠিক সেই জায়গাগুলোই দেখিয়ে দেবে, যেখানে নতুন মানের নিয়ম লিখতে হবে। এজন্য enum-এর switch expression-এ ইচ্ছা করে default রাখবেন না: default থাকলে নতুন মানটি চুপচাপ default-এ চলে যেত।
পুরোনো switch statement এই পাহারা দেয় না। switch (s) { case RED: ...; break; case GREEN: ...; } — কোলন দেওয়া পুরোনো statement-এ একটি মান বাদ পড়লে কোনো error আসে না; সেই মানে কিছুই চলে না। নতুন কোডে arrow (->) দেওয়া switch expression লিখুন, আর মান ফেরত দেওয়ার দরকার না থাকলেও পারলে expression আকারে লিখুন।
৪ · প্রতিটি enum-এর সাথে পাওয়া method — values, valueOf, name, ordinal
enum লিখলেই কয়েকটি method বিনা খরচে পাওয়া যায় — কিছু আসে java.lang.Enum থেকে, কিছু compiler যোগ করে:
| Method | কী দেয় | উদাহরণ (OrderStatus) |
|---|---|---|
values() | ঘোষণার ক্রমে সব constant-এর একটি নতুন array | [PENDING, PAID, SHIPPED, DELIVERED] |
valueOf(String) | হুবহু একই নামের constant; না মিললে exception | valueOf("PAID") → PAID |
name() | constant-এর নাম, ঠিক যেমন লেখা | PAID.name() → "PAID" |
toString() | ডিফল্টে name()-এর মতোই, তবে override করা যায় | "PAID" |
ordinal() | ঘোষণায় অবস্থান, ০ থেকে | SHIPPED.ordinal() → 2 |
compareTo(e) | ঘোষণার ক্রম অনুযায়ী তুলনা | SHIPPED.compareTo(PENDING) → 2 |
এবার সব একসাথে চালিয়ে দেখুন — বাংলায় সপ্তাহের দিন দিয়ে। এখানে enum-এ একটি field আর constructor-ও আছে; সেটার বিস্তারিত অংশ ৫-এ, আপাতত শুধু চোখ বুলিয়ে নিন:
enum Day {
SATURDAY("শনিবার"), SUNDAY("রবিবার"), MONDAY("সোমবার"), TUESDAY("মঙ্গলবার"),
WEDNESDAY("বুধবার"), THURSDAY("বৃহস্পতিবার"), FRIDAY("শুক্রবার");
private final String bn; // প্রতিটি দিনের নিজের field
Day(String bn) { this.bn = bn; } // constructor — নিজে থেকেই private
String bn() { return bn; }
boolean isWeekend() { return this == FRIDAY || this == SATURDAY; }
}
class Main {
public static void main(String[] args) {
for (Day d : Day.values())
System.out.println(d.ordinal() + " " + d.name() + " " + d.bn()
+ (d.isWeekend() ? " (ছুটি)" : ""));
Day f = Day.valueOf("FRIDAY"); // লেখা থেকে enum
System.out.println(f == Day.FRIDAY);
System.out.println(Day.SUNDAY.compareTo(Day.FRIDAY));
try { Day.valueOf("Friday"); } // বানান হুবহু না মিললে?
catch (IllegalArgumentException e) { System.out.println(e.getMessage()); }
}
}আউটপুটের প্রথম সাত লাইন: 0 SATURDAY শনিবার (ছুটি) থেকে 6 FRIDAY শুক্রবার (ছুটি) — values() ঘোষণার ক্রমেই দেয়, আর ordinal ০ থেকে গোনে। তারপর true: লেখা "FRIDAY" থেকে valueOf সেই একমাত্র FRIDAY object-টিই ফেরত দিয়েছে, তাই == সত্য। তারপর -5: SUNDAY-এর ordinal 1, FRIDAY-এর 6, আর Enum-এর compareTo ordinal-এর বিয়োগফল দেয় (Javadoc শুধু চিহ্নের নিশ্চয়তা দেয় — ঋণাত্মক মানে আগে)। শেষ লাইন No enum constant Day.Friday — valueOf বড়-ছোট হাতের পার্থক্যও মানে, “Friday” আর “FRIDAY” আলাদা। এই উদাহরণে সপ্তাহ শুরু শনিবারে আর ছুটি শুক্র-শনি; Java-র নিজের java.time.DayOfWeek enum কিন্তু শুরু করে MONDAY থেকে (ISO নিয়মে)। সব enum আর class public ছাড়া লেখা, কারণ অনলাইন runner একটি ফাইলেই সব compile করে।
valueOf — বাইরের লেখা থেকে enum, আর তার exception
valueOf দরকার হয় যখন মান আসে বাইরে থেকে — ফর্মের ইনপুট, ফাইল, JSON, ডেটাবেস। নাম না মিললে সে IllegalArgumentException ছোড়ে। ধরা না হলে প্রোগ্রাম থেমে যায় এই trace দিয়ে (OpenJDK 21):
Exception in thread "main" java.lang.IllegalArgumentException: No enum constant OrderStatus.CANCELLED at java.base/java.lang.Enum.valueOf(Enum.java:293) at OrderStatus.valueOf(prog.java:1) at Main.main(prog.java:5)
নিচ থেকে পড়ুন: main ডেকেছে OrderStatus.valueOf (compiler-এর বানানো method, তাই লাইন নম্বর enum-এর ঘোষণার লাইন), সে ডেকেছে Enum.valueOf, যে নামটি খুঁজে পায়নি। মেসেজটি পরিষ্কার: “OrderStatus-এ CANCELLED নামে কোনো constant নেই।” আর null পাঠালে আসে NullPointerException: Name is null. তাই বাইরের ইনপুট enum-এ বদলানোর সময় সবসময় ভাবুন — ভুল লেখা এলে কী করব? exception ধরে ব্যবহারকারীকে পরিষ্কার বার্তা দেবেন, নাকি একটি নিজস্ব from(String) method লিখবেন যা Optional ফেরত দেয় (পর্ব ৩৬)?
ordinal — আছে, কিন্তু সংরক্ষণ করবেন না
ordinal দেখতে খুব সুবিধাজনক: একটি ছোট সংখ্যা, ডেটাবেসে রাখতে কম জায়গা। কিন্তু ordinal হলো ঘোষণায় অবস্থান — আপনার সোর্স কোডের একটি বিবরণ, ব্যবসার কোনো সত্য নয়। কেউ constant-গুলোর ক্রম বদলালে বা মাঝখানে একটি নতুন constant ঢোকালে সব ordinal সরে যায়। ডেটাবেসে আগে থেকে রাখা সংখ্যাগুলো তখন অন্য অর্থ পায় — কোনো error ছাড়াই:
enum OrderStatus { PENDING, PAID, SHIPPED, DELIVERED }
0 1 2 3
orders টেবিল status (int)
#101 রহিমের ফোন 1 → PAID
#102 করিমের বই 2 → SHIPPED
#103 নীলার শাড়ি 3 → DELIVERED
enum OrderStatus {
PENDING, PAID, PROCESSING, SHIPPED, DELIVERED
0 1 2 3 4
}
যৌক্তিক জায়গায় ঢোকানো হলো — PAID-এর পরে, SHIPPED-এর আগে।
compile হয়, সব test-ও পাস করে।
orders টেবিল status নতুন কোড পড়ে #101 রহিমের ফোন 1 → PAID ✔ #102 করিমের বই 2 → PROCESSING ✘ আসলে পাঠানো হয়ে গেছে #103 নীলার শাড়ি 3 → SHIPPED ✘ আসলে পৌঁছে গেছে কোনো exception নেই। নীলা আবার “পথে আছে” মেসেজ পেলেন।
orders টেবিল status (text)
#101 রহিমের ফোন "PAID" → valueOf → PAID ✔
#102 করিমের বই "SHIPPED" → valueOf → SHIPPED ✔
#103 নীলার শাড়ি "DELIVERED" → valueOf → DELIVERED ✔
ক্রম বদলালে বা নতুন constant এলে কিছু যায় আসে না।
সাবধান: constant-এর নাম বদলানো এখন ডেটা-বদলের সমান।
- শুরুতে সব ঠিক। ডেভেলপার ভাবলেন int রাখলে জায়গা কম লাগবে, তাই
status.ordinal()ডেটাবেসে লিখলেন, আর পড়ার সময়OrderStatus.values()[n]. মাসের পর মাস কোনো সমস্যা নেই। - নিরীহ একটি বদল। ব্যবসায় নতুন ধাপ এলো — পরিশোধের পরে, পাঠানোর আগে, গুদামে প্যাক করা। ঘোষণায় যৌক্তিক জায়গাটি PAID আর SHIPPED-এর মাঝখানে। কোডের দিক থেকে সম্পূর্ণ সঠিক বদল, compiler খুশি।
- নীরব দুর্নীতি। ডেটাবেসের সংখ্যাগুলো বদলায়নি, কিন্তু তাদের অর্থ বদলে গেছে। 2 এখন PROCESSING, 3 এখন SHIPPED. হাজার অর্ডারের অবস্থা একসাথে এক ধাপ পিছিয়ে গেল — অথচ কোথাও কোনো error নেই। এ ধরনের bug সবচেয়ে বিপজ্জনক, কারণ ধরা পড়ে গ্রাহকের অভিযোগে, আর পুরোনো ডেটা ঠিক করতে আলাদা migration লাগে।
- নাম রাখুন।
name()লিখুন,valueOf()দিয়ে পড়ুন — ক্রম আর নতুন constant-এর প্রভাব শূন্য। নতুন ঝুঁকি একটিই: constant-এর নাম বদলানো (যেমন SHIPPED → DISPATCHED) এখন পুরোনো সারিগুলোকেvalueOf-এ অচল করে দেবে। নাম ও সংরক্ষিত কোড আলাদা রাখতে চাইলে enum-এ একটি স্থিরcodefield রাখুন (অংশ ৫ আর ৯)।
ordinal() আছে মূলত JDK-র ভেতরের কাজের জন্য — EnumSet আর EnumMap এটা দিয়ে চলে (অংশ ৭)। আপনার নিজের কোডে ordinal-এর ওপর কোনো অর্থ চাপাবেন না: সংরক্ষণে নয়, হিসাবে নয় (“ordinal + 1 = পরের ধাপ” নয়), array-র index হিসেবেও নয়। দরকারি সংখ্যা থাকলে সেটা নিজের field হিসেবে রাখুন।
৫ · field, constructor আর method — enum একটি পূর্ণ class
enum যেহেতু class, এতে field, constructor আর method রাখা যায় — ঠিক পর্ব ১৫-১৬-এর মতো। Day-এর উদাহরণে প্রতিটি দিনের সাথে একটি বাংলা নাম জুড়ে দেওয়া হয়েছিল। নিয়মগুলো:
- Constant-গুলো সবার আগে লিখতে হয়; প্রতিটির পরে বন্ধনীতে constructor-এর argument —
SATURDAY("শনিবার"). - শেষ constant-এর পরে সেমিকোলন, তারপর field, constructor, method.
- Constructor নিজে থেকেই
private.publicলিখলে javac বলেmodifier public not allowed here. বাইরের কেউ ডাকতে পারে না — constant তৈরির সময় শুধু JVM ডাকে। - Field সাধারণত
private final— প্রতিটি constant সব thread-এ ভাগ হয়, তাই enum immutable রাখাই নিরাপদ।
উদাহরণ: মোবাইল নম্বরের prefix থেকে অপারেটর
বাংলাদেশের মোবাইল নম্বরের প্রথম তিন অঙ্ক দেখে অপারেটর বোঝার চেষ্টা অনেক অ্যাপ করে। নিচের টেবিল শুধু উদাহরণের জন্য একটি সরল তালিকা — বাস্তবে নম্বর এক অপারেটর থেকে আরেকটিতে পোর্ট করা যায়, তাই prefix দেখে অপারেটর নিশ্চিত বলা যায় না:
| enum constant | বাংলা নাম | উদাহরণের prefix |
|---|---|---|
GRAMEENPHONE | গ্রামীণফোন | 017, 013 |
ROBI | রবি | 018 |
AIRTEL | এয়ারটেল | 016 |
BANGLALINK | বাংলালিংক | 019, 014 |
TELETALK | টেলিটক | 015 |
enum MobileOperator {
GRAMEENPHONE("গ্রামীণফোন", "017", "013"),
ROBI("রবি", "018"),
AIRTEL("এয়ারটেল", "016"),
BANGLALINK("বাংলালিংক", "019", "014"),
TELETALK("টেলিটক", "015"); // ← সেমিকোলন
private final String bn;
private final List<String> prefixes;
MobileOperator(String bn, String... prefixes) { // varargs, পর্ব ১২
this.bn = bn;
this.prefixes = List.of(prefixes);
}
String bn() { return bn; }
static Optional<MobileOperator> fromNumber(String number) { // "01712345678"
String p = number.substring(0, 3);
for (MobileOperator op : values())
if (op.prefixes.contains(p)) return Optional.of(op);
return Optional.empty();
}
}MobileOperator.fromNumber("01912345678") দেয় Optional[BANGLALINK], আর "01112345678" দেয় Optional.empty. লক্ষ করুন, তথ্য (prefix) আর আচরণ (খোঁজা) দুটোই enum-এর ভেতরে — অন্য কোথাও if (p.equals("017") || p.equals("013")) ছড়িয়ে থাকে না। নতুন prefix যোগ হলে বদলাতে হবে এক জায়গায়।
উদাহরণ: কে কাকে রক্ত দিতে পারে
লোহিত কণিকার (red cell) রক্তদানের সাধারণ নিয়ম: দাতার রক্তে যে antigen (A, B, Rh) আছে, গ্রহীতার রক্তেও তা থাকতে হবে। এই নিয়ম আটটি গ্রুপের জন্য আলাদা আলাদা if-else-এ না লিখে, প্রতিটি গ্রুপকে তার antigen জানিয়ে দিন:
enum BloodGroup {
O_NEG(false, false, false), O_POS(false, false, true),
A_NEG(true, false, false), A_POS(true, false, true),
B_NEG(false, true, false), B_POS(false, true, true),
AB_NEG(true, true, false), AB_POS(true, true, true);
private final boolean a, b, rh; // কোন antigen আছে
BloodGroup(boolean a, boolean b, boolean rh) { this.a = a; this.b = b; this.rh = rh; }
boolean canDonateTo(BloodGroup r) { // দাতার প্রতিটি antigen গ্রহীতার আছে কি?
return (!a || r.a) && (!b || r.b) && (!rh || r.rh);
}
}ফল: O_NEG.canDonateTo(AB_POS) → true (O− সবাইকে দিতে পারে), A_POS.canDonateTo(A_NEG) → false, B_POS.canDonateTo(A_POS) → false. আটটি গ্রুপ, চৌষট্টিটি জোড়া — অথচ একটিই লাইনের নিয়ম। (এটা প্রোগ্রামিংয়ের উদাহরণ; বাস্তব রক্তদানে আরও অনেক পরীক্ষা লাগে।)
এখানে একটি নকশার শিক্ষা আছে: প্রশ্নটি বস্তুকে জিজ্ঞেস করুন — donor.canDonateTo(patient). পর্ব ১৭-এর “tell, don't ask” নীতির মতোই, enum-এর তথ্য দিয়ে যে সিদ্ধান্ত হয়, সেটা enum-এর method-এ থাকাই স্বাভাবিক।
৬ · প্রতিটি মানের নিজস্ব আচরণ — constant-specific body, lambda আর interface
কখনো প্রতিটি constant-এর কাজটাই আলাদা — শুধু তথ্য নয়। একটি ক্যালকুলেটরের যোগ, বিয়োগ, গুণ ভাবুন। প্রথম চিন্তা আসে switch:
int apply(Operation op, int a, int b) {
return switch (op) {
case PLUS -> a + b;
case MINUS -> a - b;
};
}এটা চলে, আর exhaustiveness-এর পাহারাও আছে। কিন্তু এমন switch যদি প্রজেক্টের দশ জায়গায় থাকে, “Operation কী করে” তা ছড়িয়ে পড়ে দশ ফাইলে। Java-তে প্রতিটি constant নিজের method-এর শরীর রাখতে পারে — একে বলে constant-specific body:
enum Operation {
PLUS("+") { int apply(int a, int b) { return a + b; } },
MINUS("-") { int apply(int a, int b) { return a - b; } };
private final String symbol;
Operation(String symbol) { this.symbol = symbol; }
String symbol() { return symbol; }
abstract int apply(int a, int b); // প্রতিটি constant-কে লিখতেই হবে
}enum-এ একটি abstract method, আর প্রতিটি constant-এর পরে কোঁকড়া বন্ধনীতে তার নিজের সংস্করণ। নতুন constant TIMES("*") যোগ করে তার apply না লিখলে javac থামায়: Operation is abstract; cannot be instantiated — exhaustiveness-এর মতোই পাহারা। ভেতরে javac প্রতিটি শরীরের জন্য একটি নামহীন subclass বানায় (anonymous class, পর্ব ২৭): OpenJDK 21-এ Operation.PLUS.getClass() ছাপে class Operation$1, আর getDeclaringClass() ছাপে class Operation. তাই এমন enum আর final থাকে না — Java 17 থেকে সেটা sealed, যার অনুমোদিত subclass শুধু ওই নামহীন class-গুলো।
আরও পরিষ্কার বিকল্প: lambda field
প্রতিটি constant-এর শরীর যদি এক লাইনের হয়, Java 8-এর lambda দিয়ে আরও সংক্ষেপে লেখা যায় — আচরণটি constructor-এর argument হিসেবে:
enum Operation {
PLUS("+", (a, b) -> a + b),
MINUS("-", (a, b) -> a - b),
TIMES("*", (a, b) -> a * b);
private final String symbol;
private final IntBinaryOperator op; // java.util.function
Operation(String symbol, IntBinaryOperator op) { this.symbol = symbol; this.op = op; }
int apply(int a, int b) { return op.applyAsInt(a, b); }
}(a, b) -> a + b হলো দুটি int নিয়ে একটি int ফেরত দেওয়া ছোট একটি ফাংশন; IntBinaryOperator তার টাইপ (একটি functional interface). lambda-র পুরো গল্প পর্ব ৩৪-এ। কখন কোনটা? শরীর ছোট আর একই আকারের হলে lambda; প্রতিটি constant-এর আচরণ কয়েক লাইনের বা একাধিক method আলাদা হলে constant-specific body.
enum-ও interface implement করে
enum অন্য class extends করতে পারে না (সে আগেই java.lang.Enum extends করেছে), কিন্তু যত খুশি interface implement করতে পারে। এর একটি সুন্দর ব্যবহার: enum নিজে বন্ধ, নতুন constant বাইরে থেকে যোগ করা যায় না — কিন্তু interface দিয়ে “খোলা” রাখা যায়:
interface Op { int apply(int a, int b); }
enum BasicOp implements Op {
PLUS { public int apply(int a, int b) { return a + b; } },
MINUS { public int apply(int a, int b) { return a - b; } }
}
enum ExtraOp implements Op { // অন্য দল, অন্য library
POWER { public int apply(int a, int b) { return (int) Math.pow(a, b); } }
}
static int calc(Op op, int a, int b) { return op.apply(a, b); } // দুই enum-ই চলেcalc Op চায়, তাই BasicOp আর ExtraOp দুটোরই মান নেয়। Joshua Bloch-এর Effective Java-তে এর নাম “interface দিয়ে extensible enum-এর অনুকরণ”। দাম একটি: Op-এর ওপর switch-এ exhaustiveness আর পাওয়া যায় না, কারণ কতগুলো implementation আছে compiler জানে না।
৭ · EnumSet, EnumMap আর enum singleton
enum-এর মান দিয়ে Set বা Map বানাতে HashSet/HashMap চলে, কিন্তু Java enum-এর জন্য দুটি বিশেষ collection দিয়েছে, যারা দ্রুত, কম জায়গা নেয়, আর ক্রমও সুন্দর।
EnumSet — একসারি সুইচ
EnumSet ভেতরে আসলে একটি bit vector: প্রতিটি constant-এর জন্য একটি bit, ordinal অনুযায়ী জায়গায়। ৬৪টি বা তার কম constant হলে পুরো set একটি মাত্র long সংখ্যা (JDK-তে এর নাম RegularEnumSet). ফলে contains মানে একটি bit দেখা, union মানে একটি OR — HashSet-এর hash হিসাবের চেয়ে অনেক হালকা। ঘরের সুইচবোর্ড ভাবুন: প্রতিটি পাখা-বাতির নিজের সুইচ, আর বোর্ডের দিকে তাকালেই বোঝা যায় কোনগুলো চালু।
EnumSet<Day> weekend = EnumSet.of(Day.FRIDAY, Day.SATURDAY);
EnumSet<Day> workdays = EnumSet.complementOf(weekend); // বাকি সব
EnumSet<OrderStatus> open = EnumSet.range(OrderStatus.PENDING, OrderStatus.SHIPPED);
EnumSet<OrderStatus> none = EnumSet.noneOf(OrderStatus.class);ছাপালে ক্রম সবসময় ঘোষণার ক্রম: EnumSet.of(FRIDAY, SATURDAY) ছাপে [SATURDAY, FRIDAY], কারণ Day-এ SATURDAY আগে ঘোষিত। পুরোনো C-ধাঁচের কোডে “bit flag” (READ = 1, WRITE = 2, EXEC = 4, আর | দিয়ে মেলানো) এর কাজ করত; EnumSet একই গতি দেয়, কিন্তু নিরাপদ টাইপ আর পড়ার মতো আউটপুটসহ।
EnumMap — ordinal দিয়ে সাজানো তাক
EnumMap হলো এমন Map, যার key একটি enum. ভেতরে একটি সাধারণ array, যার index হলো key-এর ordinal — hash হিসাব নেই, collision নেই। দোকানের তাক ভাবুন, প্রতিটি তাকের নম্বর আগে থেকে ঠিক করা।
var count = new EnumMap<OrderStatus, Integer>(OrderStatus.class);
count.put(OrderStatus.SHIPPED, 2);
count.put(OrderStatus.PENDING, 7);
count.put(OrderStatus.DELIVERED, 11);
System.out.println(count); // {PENDING=7, SHIPPED=2, DELIVERED=11}যে ক্রমেই put করুন, ছাপা হয় ঘোষণার ক্রমে (OpenJDK 21-এ যাচাই করা) — ড্যাশবোর্ডে অর্ডারের অবস্থাগুলো স্বাভাবিক ক্রমে দেখাতে আলাদা করে সাজাতে হয় না। HashMap এই নিশ্চয়তা দেয় না। Set আর Map-এর বিস্তারিত পর্ব ৩১-এ।
Key বা উপাদান enum হলে প্রায় সবসময় EnumSet/EnumMap নিন। ordinal-এর ওপর যে কাজ আপনি নিজে করবেন না (অংশ ৪), সেটা এরা আপনার হয়ে নিরাপদে করে — ordinal কখনো ডেটাবেসে যায় না, শুধু মেমরির ভেতরে array-র index হয়।
enum singleton — এক constant-এর enum
পর্ব ১৮-এ singleton দেখেছেন — পুরো প্রোগ্রামে একটিই object, private constructor আর getInstance() দিয়ে। enum স্বভাবগতভাবেই “নির্দিষ্ট সংখ্যক object”; সেই সংখ্যা এক হলেই singleton:
enum AppConfig {
INSTANCE;
private int timeoutSeconds = 30;
int timeout() { return timeoutSeconds; }
}
int t = AppConfig.INSTANCE.timeout();Bloch-এর Effective Java (তৃতীয় সংস্করণ, Item 3) একে বলেছে singleton বানানোর প্রায়ই সবচেয়ে ভালো উপায়। কারণ JVM নিজে নিশ্চয়তা দেয়: object তৈরি হয় একবার, thread-safe ভাবে; serialization-এর পরেও একই object ফেরে; আর reflection দিয়ে জোর করে বানাতে গেলে আসে IllegalArgumentException: Cannot reflectively create enum objects (ভাবনার প্রশ্ন ৩)। তবে পর্ব ১৮-এর সতর্কতা এখানেও খাটে: singleton মানে global state, আর test-এ বদলানো কঠিন। বদলযোগ্য অবস্থা রাখা singleton-এর আগে দুবার ভাবুন।
৮ · enum দিয়ে state machine — অর্ডারের যাত্রা
এবার এই পর্বের সবকিছু একসাথে। একটি অর্ডার যেকোনো অবস্থা থেকে যেকোনো অবস্থায় যেতে পারে না। পৌঁছে যাওয়া অর্ডার আবার “অপেক্ষমাণ” হয় না; টাকা না দিয়ে পাঠানো হয় না। এমন নিয়মবদ্ধ যাত্রাকে বলে state machine: কয়েকটি অবস্থা (state), আর কোন অবস্থা থেকে কোনটিতে যাওয়া বৈধ (transition).
অর্ডারের বৈধ পথ
Allowed transitions of an e-commerce order
PENDING ──► PAID ──► SHIPPED ──► DELIVERED
│ │
└────┬─────┘
▼
CANCELLED (DELIVERED আর CANCELLED থেকে আর কোথাও যাওয়া যায় না)
সাধারণ কোডে এই নিয়ম ছড়িয়ে থাকে বহু if-এ: “যদি status SHIPPED হয় আর নতুন DELIVERED হয়, তবে…”. enum-এ নিয়মটি প্রতিটি অবস্থার নিজের কাছে রাখা যায় — “আমার পরে কারা আসতে পারে”:
import java.util.*;
enum OrderStatus {
PENDING, PAID, SHIPPED, DELIVERED, CANCELLED;
Set<OrderStatus> next() { // এই অবস্থা থেকে কোথায় যাওয়া যায়
return switch (this) {
case PENDING -> EnumSet.of(PAID, CANCELLED);
case PAID -> EnumSet.of(SHIPPED, CANCELLED);
case SHIPPED -> EnumSet.of(DELIVERED);
case DELIVERED, CANCELLED -> EnumSet.noneOf(OrderStatus.class);
};
}
boolean canMoveTo(OrderStatus target) { return next().contains(target); }
}
class Main {
public static void main(String[] args) {
var status = OrderStatus.PENDING;
for (String req : "PAID DELIVERED SHIPPED DELIVERED PENDING".split(" ")) {
var target = OrderStatus.valueOf(req); // লেখা থেকে enum
boolean ok = status.canMoveTo(target);
System.out.println(status + " -> " + target
+ (ok ? " : OK" : " : REJECTED, allowed " + status.next()));
if (ok) status = target;
}
}
}আউটপুট পাঁচ লাইন: PENDING -> PAID : OK, PAID -> DELIVERED : REJECTED, allowed [SHIPPED, CANCELLED], PAID -> SHIPPED : OK, SHIPPED -> DELIVERED : OK, DELIVERED -> PENDING : REJECTED, allowed []. অনুরোধগুলো এসেছে লেখা হিসেবে (যেমন অ্যাপ থেকে), তাই valueOf দিয়ে enum-এ বদলানো হয়েছে। next()-এর ভেতরে switch (this) — enum নিজের মান দেখে সিদ্ধান্ত নিচ্ছে, আর default নেই, তাই নতুন অবস্থা (যেমন RETURNED) যোগ করে নিয়ম না লিখলে compile error আসবে। বৈধ পরের অবস্থাগুলো EnumSet-এ, তাই ছাপা হয় ঘোষণার ক্রমে। নিচের ডায়াগ্রামে লুপটি এক এক করে দেখুন।
1var status = PENDING;2for (String req : requests) {3 var target = OrderStatus.valueOf(req);4 boolean ok = status.canMoveTo(target);5 println(status -> target : OK / REJECTED);6 if (ok) status = target;7}
| status (আগে) | |
| target | |
| status.next() | |
| canMoveTo | |
| status (পরে) |
- “PAID” — বৈধ।
valueOf("PAID")সেই একমাত্র PAID object দেয়। PENDING-এরnext()হলো {PAID, CANCELLED}; PAID আছে, তাই অবস্থা বদলে PAID. (আউটপুটে->, এখানে তীরচিহ্ন দিয়ে দেখানো।) - “DELIVERED” — একধাপ লাফ। টাকা দেওয়া অর্ডার পাঠানোর আগেই “পৌঁছে গেছে” — হয়তো কোনো কর্মীর ভুল ক্লিক। PAID-এর next() হলো {SHIPPED, CANCELLED}, DELIVERED নেই। অনুরোধ বাতিল, অবস্থা বদলায় না, আর বার্তায় বৈধ বিকল্পগুলোও বলা হয় — ভালো error মেসেজের অভ্যাস।
- “SHIPPED” — বৈধ। এবার ঠিক ক্রমে। অবস্থা SHIPPED.
- “DELIVERED” — বৈধ। SHIPPED-এর একমাত্র পরের ধাপ DELIVERED. যাত্রা সম্পূর্ণ।
- “PENDING” — শেষ অবস্থা থেকে ফেরা নিষেধ। DELIVERED-এর next() খালি EnumSet — terminal state. যেকোনো অনুরোধই বাতিল। পুরো নিয়মটি কোডের একটি জায়গায় (
next()), তাই ব্যবসার নিয়ম বদলালে (যেমন DELIVERED থেকে RETURNED) এক লাইনে বদলাবেন, আর compiler বাকি switch-গুলো দেখিয়ে দেবে।
বাস্তব সিস্টেমে এর ওপর আরও স্তর আসে — প্রতিটি transition-এর সময় লেখা, কে বদলাল, ডেটাবেসে একই সাথে দুজন বদলালে কী হবে। কিন্তু মূল কাঠামো এটাই: অবস্থা একটি enum, আর বৈধ পথ enum-এর নিজের জ্ঞান। একই ধাঁচে লেখা যায় ট্রাফিক সিগন্যাল (RED → GREEN → YELLOW → RED), পরীক্ষার আবেদনের অবস্থা, বা bKash-এর মতো লেনদেনের অবস্থা (শুরু, যাচাই, সম্পন্ন, ব্যর্থ)।
৯ · enum কখন নয় — sealed/record, আর JSON ও ডেটাবেসে enum
প্রতিটি মানের সাথে আলাদা ডেটা লাগলে
enum-এর প্রতিটি constant একটিই object, আর তার field সবার জন্য এক। “PAID” সব অর্ডারের জন্য একই PAID. কিন্তু যদি প্রতিটি পেমেন্ট-ফলাফলের সাথে নিজস্ব তথ্য লাগে — সফল হলে transaction ID আর টাকার পরিমাণ, ব্যর্থ হলে কারণ? সেটা enum পারে না, কারণ enum-এর মান আগে থেকে নির্দিষ্ট সংখ্যক, চলার সময় নতুন তৈরি হয় না। এর জন্য আধুনিক Java-র উত্তর sealed interface আর record:
// enum: শুধু “কোন ধরন”, ডেটা নেই
enum PaymentState { SUCCESS, FAILED }
// sealed + record: “কোন ধরন” + সেই ধরনের নিজের ডেটা (পর্ব ২৫, ২৬)
sealed interface PaymentResult permits Success, Failed {}
record Success(String txId, long amountPaisa) implements PaymentResult {}
record Failed(String reason) implements PaymentResult {}দুটোরই মান সীমিত সংখ্যক “ধরন”-এর একটি, আর দুটোতেই switch-এর exhaustiveness কাজ করে। পার্থক্য: enum-এ প্রতিটি ধরনের ঠিক একটি object; sealed+record-এ প্রতিটি ধরনের অসংখ্য object, প্রত্যেকে নিজের ডেটাসহ। সহজ নিয়ম: মানগুলো শুধু নাম হলে (আর বড়জোর সবার জন্য একই স্থির তথ্য) — enum; প্রতিটি মানের সাথে আলাদা আলাদা ডেটা লাগলে — sealed interface আর record. পর্ব ২৫-এ record, পর্ব ২৬-এ sealed আর pattern matching.
বাইরের তালিকা যদি বদলায়
enum-এর মান বদলাতে কোড বদলে নতুন করে deploy করতে হয়। তাই যে তালিকা ব্যবহারকারী বা অ্যাডমিন চালানোর সময় বদলায় — দোকানের পণ্যের ক্যাটাগরি, শহরের তালিকা যা নিয়মিত বাড়ে, ডিসকাউন্ট কুপন — সেগুলো enum নয়, ডেটাবেসের টেবিল। enum তাদের জন্য, যাদের তালিকা বদলানো মানে প্রোগ্রামের আচরণ বদলানো: অর্ডারের অবস্থা, পেমেন্টের ধরন, রক্তের গ্রুপ।
JSON আর ডেটাবেসে enum
অংশ ৪-এর শিক্ষাই এখানে নিয়ম: বাইরে লিখুন name, ordinal নয়। সুখবর হলো জনপ্রিয় টুলগুলো সাধারণত সেটাই করে — Jackson-এর মতো JSON library ডিফল্টে enum-কে তার নাম হিসেবে লেখে ("status": "SHIPPED", পর্ব ৩৯)। কিন্তু একটি বিখ্যাত ফাঁদ আছে: JPA/Hibernate-এ enum field-এর @Enumerated-এর ডিফল্ট হলো EnumType.ORDINAL. তাই সেখানে প্রতিবার স্পষ্ট লিখতে হয়:
@Enumerated(EnumType.STRING) // ordinal নয়, নাম সংরক্ষণ — JPA (পর্ব ৪৯-৫০)
private OrderStatus status;আর অন্য সিস্টেমের সাথে চুক্তি (API, পুরোনো ডেটাবেস) যদি নির্দিষ্ট কোড চায় — যেমন "P", "S" — তাহলে enum-এ একটি স্থির code field রাখুন আর একটি fromCode(String) static method লিখুন। তখন constant-এর নাম বদলালেও বাইরের চুক্তি ভাঙে না। এই lookup-এর জন্য একটি static Map বানাতে চাইলে সেটা constructor-এ ভরা যায় না — কেন, সেটা ভাবনার প্রশ্ন ৪-এর গল্প।
- Java-তে enum কি class? — হ্যাঁ; javac একে
java.lang.Enum-এর একটি final (constant-specific body থাকলে sealed) subclass বানায়, প্রতিটি constant একটি public static final object. - enum কি অন্য class extends করতে পারে? — না, সে আগেই java.lang.Enum extends করেছে; তবে interface implement করতে পারে।
- enum-এর constructor public করা যায়? — না; constructor নিজে থেকেই private, public লিখলে compile error,
newদিয়ে enum বানানো যায় না। - enum তুলনায় == না equals? —
==; প্রতিটি মানের একটিই object, তাই নিরাপদ, null-এ exception দেয় না, আর compile-এর সময় টাইপ মেলায়। - ordinal() সংরক্ষণ করা উচিত? — না; ক্রম বদলালে বা নতুন constant ঢোকালে মান সরে যায়, তাই name() বা একটি স্থির code field রাখুন।
১০ · AI-এর যুগে — String constant, ordinal আর বিশাল switch ধরুন
AI-কে “একটি অর্ডার ম্যানেজমেন্ট সিস্টেম বানাও” বললে প্রায়ই চলনসই কোড আসে, কিন্তু অবস্থাগুলো থাকে String হিসেবে ("pending", "Pending" দুটোই কোথাও কোথাও), অথবা পুরোনো টিউটোরিয়ালের মতো int constant হিসেবে। enum দিলেও আচরণ থাকে বাইরে — পাঁচ ফাইলে পাঁচটি বিশাল switch. এগুলো চোখে না পড়লে প্রথম নতুন অবস্থা যোগ করার দিনেই সমস্যা।
| AI যা করে | কেন সমস্যা | আপনি যা চাইবেন |
|---|---|---|
| অবস্থা String বা int constant দিয়ে | বানান ভুল ও ভুল সংখ্যা compile হয়, ছাপালে অর্থহীন | নির্দিষ্ট মানের তালিকা হলে enum |
ordinal() ডেটাবেসে, বা JPA-তে @Enumerated ছাড়া | নতুন constant ঢোকালে পুরোনো ডেটার অর্থ বদলায় | EnumType.STRING বা স্থির code field |
| বহু জায়গায় একই enum-এর বড় switch | আচরণ ছড়িয়ে থাকে, নতুন মানে সব খুঁজে বদলাতে হয় | method/field enum-এর ভেতরে; switch থাকলে default ছাড়া |
enum-এর switch-এ default | নতুন মান চুপচাপ default-এ যায়, compiler সতর্ক করে না | সব case লেখা switch expression |
বাইরের ইনপুটে সরাসরি valueOf | ভুল লেখায় অপ্রত্যাশিত exception, প্রোগ্রাম থামে | যাচাই করা from(String), পরিষ্কার error |
| enum-এ বদলযোগ্য (non-final) field | একটি constant সবার ভাগের — এক জায়গার বদল সবখানে লাগে | সব field private final |
AI-কে enum-ভিত্তিক নকশা লিখতে বা রিভিউ করতে বলার সময় এই কাঠামো ব্যবহার করুন:
AI-এর লেখা enum কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন। এগুলোই আপনাকে কোড-লেখক থেকে সিস্টেম-চিন্তক বানাবে।
প্র ০১Java-তে enum এলো ২০০৪ সালে, Java 5-এ — প্রথম আট বছর প্রোগ্রামাররা কী করতেন? “typesafe enum pattern” কী, আর ভাষার enum তার চেয়ে কী বেশি দিল?
১৯৯৬ সালের Java 1.0 ইচ্ছা করেই ছোট ভাষা ছিল। C-র অনেক জিনিস বাদ পড়ে — struct, union, pointer, preprocessor — আর enum-ও। C-র enum আসলে নাম দেওয়া int, তাই নকশাকারীদের চোখে এটা নতুন কিছু দিত না। ফলে প্রথম দিকের Java-তে, এমনকি JDK-র নিজের API-তেও, সর্বত্র int constant: Calendar.JANUARY একটি int, Swing-এর SwingConstants.LEFT একটি int. অংশ ১-এর সব সমস্যা — ভুল সংখ্যা, মিশে যাওয়া ধারণা, অর্থহীন ছাপা — বছরের পর বছর Java কোডে ছিল।
Typesafe enum pattern. সমাধানটি জনপ্রিয় করেন Joshua Bloch, তাঁর Effective Java-র প্রথম সংস্করণে (২০০১), “Replace enum constructs with classes” শিরোনামের অধ্যায়ে। ধারণাটি: একটি class, যার constructor private, আর প্রতিটি সম্ভাব্য মান একটি public static final object — public static final Suit CLUBS = new Suit("clubs");. বাইরের কেউ নতুন object বানাতে পারে না, তাই টাইপটির মান সীমিত; আর টাইপ আলাদা বলে int-এর মতো মিশে যায় না। অংশ ২-এর স্টেপারে javac যা বানায়, সেটা প্রায় হুবহু এই pattern.
Pattern-এর দুর্বলতা। প্রথমত, অনেক boilerplate — প্রতিটি enum-এর জন্য নিজে constructor, toString, তালিকা লিখতে হতো। দ্বিতীয়ত, switch-এ ব্যবহার করা যেত না, কারণ switch তখন শুধু int-জাতীয় মান নিত। তৃতীয়ত, সবচেয়ে সূক্ষ্ম: serialization. একটি object serialize করে আবার পড়লে Java নতুন object তৈরি করে — তাই CLUBS-এর দ্বিতীয় কপি তৈরি হয়ে == ভেঙে যেত, যদি না প্রোগ্রামার মনে করে readResolve() method লিখতেন।
Java 5-এর সমাধান। ২০০৪ সালে Java 5 (JSR 201-এর অংশ হিসেবে, একই সাথে autoboxing, enhanced for আর static import) enum keyword আনে। এটা মূলত typesafe enum pattern-কেই ভাষার অংশ বানাল, সব দুর্বলতা দূর করে: compiler নিজে boilerplate লেখে; switch enum সমর্থন করে; serialization নাম দিয়ে হয় বলে কপি তৈরি হয় না; সাথে values(), valueOf(), EnumSet, EnumMap. তবে পুরোনো API বদলানো যায়নি — Calendar.JANUARY আজও int. Java 8-এর java.time-এ এসে Month আর DayOfWeek enum হলো।
মূল উপলব্ধি: ভালো ভাষা-বৈশিষ্ট্য প্রায়ই জন্ম নেয় প্রোগ্রামারদের হাতে বানানো pattern থেকে। Pattern-টি জনপ্রিয় ও প্রমাণিত হলে ভাষা তাকে নিজের করে নেয় — আর তখন compiler সেই নিয়মগুলো পাহারা দেয়, যা আগে মানুষকে মনে রাখতে হতো।
প্র ০২C-র enum শুধু int, আর Java-র enum পূর্ণ object — এই পার্থক্যের দাম আর লাভ কী? মেমরি বা গতির দিক থেকে Java কি বেশি খরচ করে?
C-র মডেল। C-তে enum Color { RED, GREEN } লিখলে RED মানে 0, GREEN মানে 1 — compile-এর পর শুধু সংখ্যা। enum Color c = 42; বৈধ, RED + 1 বৈধ, আর নামগুলো বাইরের scope-এ ছড়িয়ে পড়ে, তাই দুটি enum-এ একই নাম রাখা যায় না। সুবিধা: শূন্য খরচ — একটি int যতটুকু, ততটুকুই। C++11-এর enum class নাম আর রূপান্তরের সমস্যা মেটায় (scoped, নিজে থেকে int-এ বদলায় না), কিন্তু তখনো ভেতরে পূর্ণসংখ্যা; method বা প্রতিটি মানের field রাখা যায় না।
Java-র মডেল। Java-তে enum ভ্যারিয়েবল আসলে একটি reference, যা কয়েকটি নির্দিষ্ট object-এর একটিকে দেখায়। লাভ বিশাল: সম্পূর্ণ type safety, প্রতিটি মানের সাথে তথ্য আর আচরণ, interface implement করা, ছাপালে নাম, values() দিয়ে তালিকা। খরচ: প্রতিটি constant-এর জন্য একটি ছোট object (একবারই, তাই নগণ্য), আর ভ্যারিয়েবলটি int নয়, reference — যা অনেক JVM-এ একটি int-এর সমান জায়গা নেয় (compressed reference থাকলে), তবে একটি byte-এর চেয়ে বেশি।
গতি। enum-এর == হলো reference তুলনা — int তুলনার মতোই একটি CPU নির্দেশ। enum-এর switch-এ javac ভেতরে ordinal ব্যবহার করে int switch বানায়। EnumSet আর EnumMap ordinal দিয়ে bit আর array-তে কাজ করে, তাই HashSet/HashMap-এর চেয়ে দ্রুত। বাস্তবে সাধারণ প্রোগ্রামে enum-এর জন্য কোনো মাপার মতো ধীরগতি হয় না।
Android-এর গল্প। একসময় Android-এর অফিসিয়াল পারফরম্যান্স পরামর্শে মেমরি বাঁচাতে enum এড়িয়ে int constant ব্যবহার করতে বলা হতো, আর এর জন্য @IntDef annotation দেওয়া হয়েছিল, যাতে lint অন্তত ভুল সংখ্যা ধরতে পারে। তখনকার ফোনে কয়েক KB-ও গুরুত্বপূর্ণ ছিল। পরে runtime উন্নত হয়, আর সেই পরামর্শ অফিসিয়াল ডকুমেন্টেশন থেকে সরে যায়; আরও পরে build টুল R8 অনেক সরল enum-কে নিজেই int-এ বদলে দিতে শেখে। শিক্ষাটি হলো: “X ধীর” জাতীয় পরামর্শের একটি মেয়াদ থাকে — যন্ত্র আর টুল বদলালে নতুন করে মেপে দেখতে হয়।
মূল উপলব্ধি: C বেছে নিয়েছে শূন্য খরচ, Java বেছে নিয়েছে পূর্ণ নিরাপত্তা আর প্রকাশক্ষমতা। প্রায় সব ক্ষেত্রে Java-র দামটি সামান্য আর লাভটি বিশাল — তবে খুব সীমিত যন্ত্রে ট্রেড-অফটি মাপার মতো, অনুমান করার মতো নয়।
প্র ০৩Effective Java বলে single-element enum প্রায়ই singleton-এর সবচেয়ে ভালো রূপ। সাধারণ private-constructor singleton serialization আর reflection-এ কীভাবে ভাঙে, আর enum কেন ভাঙে না?
পর্ব ১৮-এর ক্লাসিক singleton: private constructor, একটি private static final instance, আর getInstance(). সাধারণ কোডের জন্য যথেষ্ট, কিন্তু দুটি দরজা দিয়ে দ্বিতীয় object ঢুকে পড়তে পারে।
দরজা ১: serialization. Class Serializable হলে object-কে byte-এ লিখে ফাইলে বা নেটওয়ার্কে পাঠানো যায়। পড়ার সময় Java constructor না ডেকেই একটি নতুন object তৈরি করে আর তাতে field বসায়। ফলে একটি “singleton”-এর দুটি object. প্রতিকার হলো readResolve() method লেখা, যা নতুন object ফেলে দিয়ে আসলটি ফেরত দেয় — আর সব instance field transient রাখা, কারণ না হলে চতুর আক্রমণে সেই ফাঁকেও আসল object-এর বাইরে একটি reference চুরি করা যায়। সবই মনে রাখার দায়িত্ব প্রোগ্রামারের।
দরজা ২: reflection. Constructor.setAccessible(true) দিয়ে private constructor-কেও ডাকা যায় (module-এর সীমা না থাকলে)। প্রতিকার: constructor-এর ভেতরে একটি পাহারা — “instance আগে থেকেই থাকলে exception”. আবার প্রোগ্রামারের দায়িত্ব।
enum কেন নিরাপদ। Java Object Serialization Specification enum-এর জন্য আলাদা নিয়ম রেখেছে: enum constant serialize হয় শুধু তার নাম দিয়ে, আর পড়ার সময় Enum.valueOf দিয়ে সেই একই object খুঁজে আনা হয় — নতুন object তৈরিই হয় না, আর enum-এর নিজের লেখা readObject বা readResolve উপেক্ষিত হয়। Reflection-এর দরজা বন্ধ করেছে JDK নিজে: Constructor.newInstance enum-এর জন্য ডাকলে ছোড়ে IllegalArgumentException: Cannot reflectively create enum objects — এই মেসেজ OpenJDK 21-এ চালিয়ে যাচাই করা। আর class initialization JVM-এর নিয়মে একবারই, thread-safe ভাবে হয়, তাই double-checked locking-এর মতো জটিল কৌশলও লাগে না।
সীমাবদ্ধতা। enum কোনো class extends করতে পারে না, তাই singleton-কে কোনো superclass থেকে আসতে হলে এটা চলে না। object তৈরি হয় class প্রথম ব্যবহারের সময় — বেশিরভাগ ক্ষেত্রে সমস্যা নয়। আর সবচেয়ে বড় কথা, singleton-এর নকশাগত সমালোচনা (global state, test-এ বদলানো কঠিন) enum দিয়ে দূর হয় না। আধুনিক অ্যাপ্লিকেশনে প্রায়ই dependency injection (যেমন Spring, পর্ব ৫০) একটিই object বানিয়ে প্রয়োজনমতো পাঠিয়ে দেয় — singleton-এর সুবিধা, global state-এর অসুবিধা ছাড়া।
মূল উপলব্ধি: যে নিশ্চয়তা আপনি নিজে কোড লিখে রক্ষা করেন, তা ভুলে যাওয়া যায়; যে নিশ্চয়তা ভাষা আর JVM দেয়, তা ভাঙে না। enum singleton “একটিই object” প্রতিশ্রুতিটিকে প্রোগ্রামারের দায়িত্ব থেকে প্ল্যাটফর্মের দায়িত্বে সরিয়ে দেয়।
প্র ০৪enum-এর constructor থেকে একটি static field ছুঁতে গেলে কেন compile error হয়? তাহলে prefix থেকে অপারেটর খোঁজার একটি static Map বানাবেন কীভাবে?
ধরুন আপনি গুনতে চান কয়টি constant তৈরি হলো:
enum Color {
RED, GREEN, BLUE;
static int count = 0;
Color() { count++; }
}
Main.java:4: error: illegal reference to static field from initializer
Color() { count++; }
^
(মেসেজটি OpenJDK 21-এর।) কারণ বুঝতে অংশ ২-এর স্টেপারের ধাপ ৩ মনে করুন। enum constant-গুলো আসলে static field, আর static field-গুলো সোর্সে যে ক্রমে লেখা, ঠিক সেই ক্রমে initialize হয়। Java-র নিয়মে constant সবার আগে লিখতে হয় — তাই RED, GREEN, BLUE তৈরি হয় (আর তাদের constructor চলে) অন্য সব static field-এর আগে. RED-এর constructor যখন count++ করছে, count-এর = 0 তখনো চলেনি। Compiler যদি এটা অনুমোদন করত, count তিন পর্যন্ত বাড়ত, তারপর count = 0 চলে সব মুছে দিত — ফল ০, যা কেউ আশা করে না। এই বিভ্রান্তি এড়াতে Java Language Specification (§8.9.2) enum-এর constructor, instance initializer আর instance field initializer থেকে static field-এর উল্লেখ নিষিদ্ধ করেছে — ব্যতিক্রম শুধু compile-time constant (যেমন static final int MAX = 5), যার মান compile-এর সময়েই বসে যায়।
সমাধান: constant-এর পরে static block. অংশ ৫-এর MobileOperator-এ প্রতিবার values() হাঁটার বদলে একটি Map চাইলে:
private static final Map<String, MobileOperator> BY_PREFIX = new HashMap<>();
static {
for (MobileOperator op : values())
for (String p : op.prefixes) BY_PREFIX.put(p, op);
}
static Optional<MobileOperator> fromPrefix(String p) {
return Optional.ofNullable(BY_PREFIX.get(p));
}
static block সোর্সে constant-গুলোর পরে, তাই সে চলার সময় সব constant তৈরি হয়ে গেছে। constructor-এ “নিজেকে Map-এ ঢুকিয়ে দাও” লেখার লোভ সামলান — Map তখনো null.
একই ফাঁদ সাধারণ class-এও। static initialization-এর ক্রমের সমস্যা শুধু enum-এর নয় — যেকোনো class-এ একটি static field যদি নিজের class-এর object বানায় আর তার constructor অন্য static field পড়ে, অর্ধেক-তৈরি অবস্থা দেখা যায়। enum-এ এটা অনিবার্য (constant সবসময় আগে), তাই Java সেখানে compile error দিয়ে আগেই আটকেছে।
মূল উপলব্ধি: “কোনটা আগে তৈরি হয়” — initialization order-এর এই প্রশ্ন বহু সূক্ষ্ম bug-এর উৎস। enum-এ ভাষা নিজেই ভুলটি অসম্ভব করেছে; আপনার কাজ নিয়মটির কারণ বোঝা, যাতে অন্য জায়গায় একই ফাঁদ চিনতে পারেন।
প্র ০৫Functional ভাষায় “algebraic data type” আর “sum type” বলে একটি ধারণা আছে। enum কি একটি sum type? sealed interface আর record কীভাবে এই ধারণাকে আরও এগিয়ে নেয়?
Product আর sum. টাইপ বানানোর দুটি মৌলিক উপায় আছে। প্রথমটি “এবং”: একটি ঠিকানা = বাড়ি নম্বর এবং রাস্তা এবং শহর। সম্ভাব্য মানের সংখ্যা গুণফল, তাই নাম product type — Java-তে class বা record. দ্বিতীয়টি “অথবা”: একটি পেমেন্ট-ফল = সফল অথবা ব্যর্থ। সম্ভাব্য মানের সংখ্যা যোগফল, তাই নাম sum type. দুটি মিলিয়ে বানানো টাইপকে বলে algebraic data type. ধারণাটির শিকড় ১৯৭০ ও ১৯৮০-র দশকের ML আর Hope ভাষায়, পরে Haskell, OCaml-এ কেন্দ্রীয় হয়; আজ Rust-এর enum, Swift-এর associated value-সহ enum, Kotlin-এর sealed class — সবই এর রূপ।
Java-র enum: ডেটাহীন sum type. enum OrderStatus { PENDING, PAID, SHIPPED, DELIVERED } একটি খাঁটি sum: মানটি এই চারটির একটি, আর কিছু নয়। Compiler তালিকাটি জানে, তাই switch-এ exhaustiveness যাচাই করে — এটাই sum type-এর মূল শক্তি। কিন্তু সীমাবদ্ধতা: প্রতিটি বিকল্প একটিই object, ডেটা সবার জন্য স্থির। Rust-এ enum Payment { Success { tx_id: String }, Failed { reason: String } } লেখা যায় — প্রতিটি বিকল্প নিজের ডেটা বহন করে। Java-র enum তা পারে না।
sealed + record: পূর্ণ algebraic data type. Java 16-এ record (JEP 395) দিল সংক্ষিপ্ত product type; Java 17-এ sealed class/interface (JEP 409) দিল বন্ধ তালিকার sum; আর Java 21-এ switch-এর pattern matching (JEP 441) ও record pattern (JEP 440) সেগুলোকে ভেঙে পড়ার উপায় দিল। sealed interface PaymentResult permits Success, Failed — একটি sum; record Success(String txId, long amount) — একটি product. মিলে “sum of products”, ঠিক ML-এর ধারণা, Java-র পরিচিত ব্যাকরণে। আর switch-এ case Success(var id, var amt) -> … লিখলে compiler আবার exhaustiveness দেখে — enum-এর সেই পাহারা, এবার ডেটাসহ। পর্ব ২৬-এ পুরোটা দেখবেন।
তাহলে enum কি অপ্রয়োজনীয় হয়ে গেল? না। মানগুলো যখন শুধুই নাম — অবস্থা, দিন, রক্তের গ্রুপ — enum সবচেয়ে সরল, আর সাথে আসে values(), valueOf(), EnumSet, EnumMap, নাম দিয়ে নিরাপদ serialization. sealed+record-এ এগুলো নেই। এমনকি দুটি একসাথেও চলে: একটি sealed interface-এর একটি implementation নিজেই একটি enum হতে পারে।
মূল উপলব্ধি: enum হলো sum type-এর সবচেয়ে সরল রূপ — “এগুলোর একটি, আর কিছু নয়”. প্রতিটি বিকল্পের নিজস্ব ডেটা লাগলে sealed interface আর record একই ধারণাকে পূর্ণ algebraic data type-এ নিয়ে যায়। দুটোই একটি নীতির প্রকাশ: সম্ভাব্য অবস্থাগুলো টাইপে লিখে দিন, যাতে compiler প্রতিটি অবস্থা সামলানো হয়েছে কি না যাচাই করতে পারে।
অনুশীলন
বেশিরভাগ কাজে নতুন কোড লিখতে হবে না — ওপরের কোড বদলে দেখা আর ভাবাই আসল কাজ।
-
ক্রম বদলে দেখুন: অংশ ৪-এর Day enum-এ FRIDAY-কে তুলে সবার আগে বসান (SATURDAY-এর আগে)। না চালিয়ে বলুন: কোন কোন আউটপুট লাইন বদলাবে, আর কোনগুলো একই থাকবে? তারপর চালিয়ে মিলিয়ে নিন।
বদলাবে: প্রথম সাত লাইনের ক্রম আর ordinal — এখন
0 FRIDAY শুক্রবার (ছুটি),1 SATURDAY শনিবার (ছুটি),2 SUNDAY রবিবার… আরDay.SUNDAY.compareTo(Day.FRIDAY)এখন2(2 − 0), আগে ছিল −5 — চিহ্নই উল্টে গেল। একই থাকবে:true(valueOf নাম দিয়ে খোঁজে) আরNo enum constant Day.Friday, আর ছুটির চিহ্ন — কারণisWeekend()নাম দিয়ে তুলনা করে, ক্রম দিয়ে নয়। শিক্ষা: যে কোড নাম দিয়ে কাজ করে তা ক্রম বদলে টেকে; ordinal বা compareTo-নির্ভর কোড টেকে না। -
নতুন অবস্থা যোগ করুন: অংশ ৮-এর state machine-এ
CANCELLED-এর পরেRETURNEDযোগ করুন, কিন্তুnext()ছোঁবেন না। কী হয়? তারপর নিয়ম বসান: DELIVERED থেকে RETURNED-এ যাওয়া যায়, RETURNED থেকে কোথাও নয়। অনুরোধের তালিকার শেষে “RETURNED” যোগ করে আউটপুট মেলান।প্রথমে compile error:
the switch expression does not cover all possible input values—next()-এর switch-এ RETURNED নেই, আর default নেই বলে compiler ধরেছে। ঠিক এটাই চাই। সমাধান:case DELIVERED -> EnumSet.of(RETURNED);আরcase CANCELLED, RETURNED -> EnumSet.noneOf(OrderStatus.class);. এখন আগের পঞ্চম লাইন হবেDELIVERED -> PENDING : REJECTED, allowed [RETURNED], আর নতুন ষষ্ঠ লাইনDELIVERED -> RETURNED : OK. খেয়াল করুন, নতুন অবস্থার সব নিয়ম বসাতে হলো একটিই method-এ। -
অগোছালো ইনপুট: একটি মোবাইল অ্যাপ JSON-এ পাঠাচ্ছে
"status": "shipped"(ছোট হাতে), কখনো" Paid "(দুপাশে ফাঁকা)। সরাসরিOrderStatus.valueOf(...)করলে কী হবে? একটি নিরাপদfrom(String)method কেমন হওয়া উচিত — কথায় বর্ণনা করুন।দুটিতেই
IllegalArgumentException:No enum constant OrderStatus.shippedআরNo enum constant OrderStatus. Paid— valueOf হুবহু নাম চায়। নিরাপদfrom: (১) null হলে পরিষ্কার error বাOptional.empty(); (২)strip()দিয়ে দুপাশের ফাঁকা বাদ; (৩)toUpperCase(Locale.ROOT)— Locale.ROOT জরুরি, কারণ কিছু ভাষার নিয়মে (যেমন তুর্কি) “i”-এর বড় হাত “I” নয়, ফলে ভিন্ন locale-এর কম্পিউটারে “paid” থেকে অন্য লেখা তৈরি হতে পারে; (৪) তারপর valueOf, আর exception ধরে বোঝার মতো বার্তা — “অজানা অবস্থা: ' Paid '; বৈধ: PENDING, PAID, …” (Arrays.toString(values())দিয়ে)। কোথায় এমন নমনীয়তা চলবে আর কোথায় কঠোর হতে হবে (যেমন পেমেন্ট API), সেটা দলের সিদ্ধান্ত। -
enum হবে কি না: প্রতিটির জন্য বলুন enum ঠিক কি না, এক লাইনে কারণসহ: (ক) রক্তের গ্রুপ; (খ) HTTP method (GET, POST, PUT, DELETE…); (গ) অনলাইন শপের পণ্যের ক্যাটাগরি, যা অ্যাডমিন প্যানেল থেকে যোগ হয়; (ঘ) পেমেন্টের ফল, যেখানে সফল হলে transaction ID আর ব্যর্থ হলে কারণ রাখতে হবে; (ঙ) সপ্তাহের দিন।
(ক) enum — আটটি স্থির মান, আর canDonateTo-র মতো আচরণ আছে। (খ) enum — তালিকা মানক দ্বারা নির্দিষ্ট আর খুব কমই বদলায় (Java-র নিজের HttpClient-এ অবশ্য method String হিসেবে নেওয়া হয়, যাতে অপ্রচলিত method-ও পাঠানো যায় — একটি সচেতন ট্রেড-অফ)। (গ) enum নয় — তালিকা চালানোর সময় বদলায়, তাই ডেটাবেসের টেবিল; enum বদলাতে কোড deploy লাগে। (ঘ) enum নয় — প্রতিটি ফলের সাথে আলাদা ডেটা;
sealed interfaceআরrecord(পর্ব ২৫-২৬), বড়জোর ধরনটুকুর জন্য একটি enum. (ঙ) enum — আর নিজে না বানিয়ে Java-রjava.time.DayOfWeekব্যবহার করুন, শুধু বাংলা নাম বা ছুটির নিয়মের জন্য নিজের কোড লিখুন।
নিজেকে যাচাই করুন
ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।
ক্রমenum OrderStatus { PENDING, PAID; static int MAX_ITEMS = 50; ... } — কোড প্রথমবার OrderStatus.PAID ব্যবহার করলে ঘটনাগুলো সঠিক ক্রমে সাজান (▲▼ চেপে সরান)
- 📥 JVM OrderStatus class load করে, initialization শুরু হয়
- 🏗️ PENDING-এর জন্য constructor চলে: name "PENDING", ordinal 0
- 🏗️ PAID-এর জন্য constructor চলে: name "PAID", ordinal 1
- 📋 সব constant নিয়ে লুকানো $VALUES array তৈরি হয়
- 🔢 constant-এর পরে লেখা static field MAX_ITEMS-এ 50 বসে
- ✅ OrderStatus.PAID-এর reference কোডের হাতে আসে
enum Level { LOW, MEDIUM, HIGH } থাকলে Level.HIGH.ordinal() কী ফেরত দেয়?
- 3
- "HIGH"
- 2
- compile error, কারণ ordinal() নিজে লিখতে হয়
enum Level { LOW, MEDIUM, HIGH } থাকলে Level.valueOf("low") চালালে কী হয়?
- IllegalArgumentException ছোড়ে
- LOW ফেরত দেয়
- null ফেরত দেয়
- compile error হয়
Java enum-এর constructor সম্পর্কে কোন কথাটি সঠিক?
- public রাখা যায়, যাতে অন্য class নতুন constant বানাতে পারে
- new Level() লিখে নতুন মান বানানো যায়
- enum-এ constructor লেখা যায় না
- এটি নিজে থেকেই private, শুধু constant তৈরির সময় চলে
একটি অর্ডারের enum অবস্থা ডেটাবেসে সংরক্ষণের সবচেয়ে নিরাপদ উপায় কোনটি?
- ordinal() — ছোট সংখ্যা, কম জায়গা
- name() অথবা enum-এ রাখা একটি স্থির code
- hashCode()
- toString(), যেটা override করে সুন্দর লেখা দেওয়া হয়েছে
enum Status { PENDING, PAID, SHIPPED } থাকলে একটি EnumMap-এ প্রথমে SHIPPED, তারপর PENDING key put করে map ছাপালে key-গুলো কোন ক্রমে আসে?
- যে ক্রমে put করা হয়েছে: SHIPPED, PENDING
- অনির্দিষ্ট, hash অনুযায়ী
- ঘোষণার ক্রমে: PENDING, SHIPPED
- বর্ণানুক্রমে, নাম অনুযায়ী
Java-তে একটি enum কোনটি করতে পারে?
- এক বা একাধিক interface implement করতে
- অন্য একটি class extends করতে
- অন্য class-কে নিজেকে extends করতে দিতে
- চলার সময় নতুন constant যোগ করতে
শব্দকোষ
লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখাবে। এই পর্বের সব নতুন শব্দ এখানে একসাথে:
মনে রাখুন
- মান কয়েকটি নির্দিষ্ট পছন্দের একটি হলে enum — int বা String constant নয়। ভুল মান তখন compile-ই হয় না (
incompatible types). - enum একটি final class; প্রতিটি constant একটি
public static finalobject, একবারই তৈরি। তাই তুলনায়==; constructor নিজে থেকেই private. values(),valueOf()(ভুল নামেIllegalArgumentException),name(),ordinal(),compareTo(). সংরক্ষণে name বা স্থির code, ordinal কখনো নয়।- Field, constructor, method, constant-specific body বা lambda field দিয়ে আচরণ enum-এর ভেতরে রাখুন; switch লাগলে arrow-switch, সব case, default ছাড়া।
- enum key-তে EnumSet/EnumMap (দ্রুত, ঘোষণার ক্রম); state machine-এ বৈধ পরের অবস্থা enum-এর জ্ঞান। প্রতিটি মানের নিজস্ব ডেটা লাগলে sealed interface + record.
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- পর্ব ২৫ · record — ডেটার জন্য ছোট classপরবর্তী পর্বequals, hashCode, toString নিজে থেকে — আর sealed interface-এর সাথে মিলে sum of products.
- Java প্রোগ্রামিং · কোর্স হোম৫৯টি পর্বপুরো কোর্সের রোডম্যাপ — কোন পর্ব কখন।
- C++ · enum class, struct আর structured bindingsপাশের ট্র্যাকscoped enum, underlying type — পূর্ণসংখ্যা-ভিত্তিক enum-এর আধুনিক রূপ।
- Python · শর্ত আর matchপাশের ট্র্যাকmatch-এ Enum আর ধ্রুবক মেলানো — Python-এর দৃষ্টিভঙ্গি।