Java Enum - নতুন করে যা শিখলাম
জুন ২২, ২০১৬, বুধবার।
শুরুতেই ডিসক্লেইমার : পুরো লেখাটাই Java Magazine -এর এপ্রিল/মে ২০১৬ সংখ্যায় University of Kent -র প্রফেসর মাইকেল কলিং (Michael Kölling)-এর লেখা একটা আর্টিকেল থেকে নেয়া।
[কারো যদি জাভা ম্যাগাজিন সাবস্ক্রাইব করতে সমস্যা হয়, এই সংখ্যার pdf টা আমার গুগল ড্রাইভে আপলোড করে রাখছি। আর্টিকেলটা ম্যাগাজিনের ৪০ নম্বর পাতায় আছে। এই সংখ্যার মূল টাইটেল "Inside Java and the JVM", খুব ভালো ভালো কিছু আর্টিকেল আছে]
প্রফেসর মাইকেল কলিং-এর আর্টিকেলটার শিরোনাম:
"Making the Most of Enums - Anytime you have a set of known constant values, an enum is a type-safe representation that prevents common problems"
এখানে বলে নেই , আমার ব্লগ না পড়ে মূল লেখাটা পড়ে নেয়াটাই সবচেয়ে ভালো হবে, খুবই সুন্দর, সাবলীল ভাষায় গোছানো লেখা। লেখাটা পড়ার আগে Enum সম্পর্কে যা জানতাম, আর পড়ার পরে যা শিখলাম - দুটোর মধ্যে অনেক তফাৎ। আমি মোটামুটি নিশ্চিত যে লেখাটা পড়লে আপনি নিজেও নতুন করে Enum সম্পর্কে অনেক কিছু জানবেন।
যাই হোক, ধরা যাক, আমরা একটা গেম বানাতে যাচ্ছি যার কয়েকটা ফিক্সড বা নির্দিষ্ট সংখ্যক কমান্ড আছে: "go", "look" , "take", "help", "quit". স্বভাবিক ভাবেই একেকটা কমান্ড একেকটা কাজ করবে। এটা implement করার জন্য প্রথমে আমরা নিচের কোড লিখতে পারি:
যেহেতু কমান্ড গুলো ফিক্সড, তাই array টাকে final করে দিলাম। এবার, একটা switch স্টেটমেন্ট লিখে একেকটা কমান্ডের জন্য একেকটা একশন লিখে দিলেই হয়ে গেলো। সিম্পল ভাবে লেখার জন্য আপাতত শুধু প্রিন্ট স্টেটমেন্ট দিয়েই একশন গুলো লিখি:
উপরের ছবি-২ এ একটা ইচ্ছাকরেই একটা ভুল রাখা আছে। আরেকবার ভালো করে দেখে দেখুন তো, ভুলটা ধরতে পারেন কীনা!
যদি ভেবে থাকেন যে "কোডে default সেকশন তো নাই" - তাহলে আপনি আমার মতোই ভাবছেন। আমিও তাই ভেবেছিলাম। কিন্তু আসলে তা না. খেয়াল করে দেখুন, ছবি ১ এ দেয়া কমান্ড ছিল "help", কিন্তু switch স্টেটমেন্টে কিন্তু দেয়া আছে "Help " - শুরুতে capital H! তার মানে, কমান্ড "help"- এর কোনো ইম্প্লিমেন্টেশন আমাদের কোডে নাই।
এই ভুল কিন্তু compile time -এ ধরা পড়বে না, কারণ এটা কোডের লজিকে ভুল, "কোড" হিসাবে তো কোনো ভুল না।
মূলত উপরের কোডে ২ ধরণের সমস্যা আছে:
১. টাইপ সেফটি (type safety), ২. ইন্টারন্যাশনালাইজেশন (internationalization )
কিভাবে? ভেবে দেখুন, আমাদের টাইপ String হওয়ায় switch -এ যেকোনো String -ই ইনপুট হিসাবে দিয়ে দেয়া যাবে। আমাদের কমান্ড যে ফিক্সড সংখ্যক স্ট্রিং -র একটা সেট - আমাদের কোড কিন্তু এই টাইপ সেফটি দিতে পারছে না। আর ইন্টারন্যাশনালাইজেশন -এর সমস্যাটা একটু পরে ব্যাখ্যা করছি।
এই সমস্যা গুলো Enum ব্যবহার করলে থাকবে না। প্রথমে দেখে নেই Enum ব্যবহার করে কিভাবে কমান্ড গুলো ডিক্লায়ার করা যায় (খেয়াল রাখবেন Enum হচ্ছে ক্লাস, enum কী-ওয়ার্ড ):
এবার আগের মতোই switch ব্যবহার করে কমান্ড গুলোর একশন ইমপ্লিমেন্ট করা যাবে এভাবে:
দেখতে হুবহু আগের মতো হলেও, এই কোড কিন্তু টাইপ সেইফ। আপনি চাইলেও অন্য কোনো কমান্ড পাস করতে পারবেন না। GO না লিখে Go লিখে দেখুন, compile time - এরর খাবে।
মূল লেখক মাইকেল কলিং-র মতো আমিও বলি, আপনি যদি এই পর্যন্ত পড়েই ভাবেন, Enum সম্পর্কে জেনে গেছেন, তবে ভুল করছেন। Enum এর মূল ব্যাপার বুঝতে আপনাকে আরো পড়তে হবে তবেই আপনি এর বেস্ট ফিচার গুলো জানবেন।
আসলে Enum কী? [ এই ব্যাপারটাই আমার কাছে নতুন, একদমই জানা ছিল না]
Enum ডিক্লারেশন আসলে একটা class। আর এর একেকটা value হচ্ছে একেকটা instance, বা অবজেক্ট। enum ডিক্লারেশন -এ ক্লাসের মতো ফিল্ড, কন্সট্রাক্টর কিংবা মেথড ইত্যাদি থাকতে পারে!! মাইকেল কলিং -র ভাষায় :
"Enum declarations are full classes, and the values listed are constant names referring to separate instances of these classes. The enum declaration can contain fields, constructors, and methods, just like other classes."
একটা উদাহরণ দিয়ে দেখা যাক:
আমাদের মূল enum এর মতোই এই enum -এর ফিক্সড বা নির্দিষ্ট সংখ্যক ভ্যালু (GO, LOOK, ...) আছে। তফাৎ হচ্ছে, এইক্ষেত্রে একটা স্ট্রিং টাইপ প্রাইভেট ফিল্ড (commandString) আছে, একটা কন্সটাক্টর আছে যা কিনা এই প্রাইভেট ফিল্ডকে স্ট্রিং এসাইন করে। আর একটা পাবলিক মেথড toStirng() আছে, যা আবার এই ফিল্ড -র ভ্যালু রিটার্ন করে।
অন্য ক্লাস-এর সাথে এর মূল তফাৎ হচ্ছে, অন্য ক্লাসের অবজেক্ট বা ইন্সটান্স শুরুতেই থাকে না বা exist করে না, একজন User বা ব্যবহারকারী ক্লাস-এর কন্সটাক্টর কল করে ইন্সটান্স তৈরি করে (চাইলে বার বার কল করে অনেকগুলো অবজেক্ট-ও তৈরি করতে পারবে, যদি ক্লাস singleton না হয়), কিন্তু enum -এর বেলায় শুরুতেই ইন্সটান্স বা অবজেক্ট গুলো তৈরী হয়ে যায়। আর একেকটা ভ্যালু বা ইন্সটান্স একবারই তৈরি হয়, পরে আর নতুন করে তৈরি করা যায় না। মনে রাখতে হবে, enum -এর কন্সট্রাক্টর পাবলিক না। আপনি চাইলেও পাবলিক করতে পারবেন না। public কী-ওয়ার্ড কন্সট্রাক্টর -এর সামনে বসিয়ে দেখুন, compile time এরর হবে।
কেউ প্রশ্ন করতেই পারে, enum -এর কন্সট্রাক্টর যদি পাবলিক নাই হয়, তাহলে এর ব্যবহারটা কী/কোথায়?
উত্তরটা ছবি ৫ -এ আমাদের পরিবর্তিত enum ডিক্লারেশন -এ আছে। খেয়াল করে দেখুন, enum ডিক্লারেশন এ আমরা শুধু GO না লিখে যখন GO("go") দিচ্ছি, তখন আসলে আমরা কন্সট্রাক্টর কল করে আমাদের মূল কমান্ড "go" কে প্যারামিটার হিসাবে পাস করছি - যা কিনা প্রাইভেট ফিল্ডে থাকছে। আর এভাবেই কন্সট্রাক্টর ব্যবহার করে আমরা (চাইলে আরো ফিল্ড যোগ করে) ইচ্ছামতো ফিল্ড ইনিশিয়ালাইজ করতে পারি।
Enum এর মূল বিষয় গুলোর একটা তালিকা করলে এমন হবে:
জুন ২২, ২০১৬, বুধবার।
শুরুতেই ডিসক্লেইমার : পুরো লেখাটাই Java Magazine -এর এপ্রিল/মে ২০১৬ সংখ্যায় University of Kent -র প্রফেসর মাইকেল কলিং (Michael Kölling)-এর লেখা একটা আর্টিকেল থেকে নেয়া।
[কারো যদি জাভা ম্যাগাজিন সাবস্ক্রাইব করতে সমস্যা হয়, এই সংখ্যার pdf টা আমার গুগল ড্রাইভে আপলোড করে রাখছি। আর্টিকেলটা ম্যাগাজিনের ৪০ নম্বর পাতায় আছে। এই সংখ্যার মূল টাইটেল "Inside Java and the JVM", খুব ভালো ভালো কিছু আর্টিকেল আছে]
প্রফেসর মাইকেল কলিং-এর আর্টিকেলটার শিরোনাম:
"Making the Most of Enums - Anytime you have a set of known constant values, an enum is a type-safe representation that prevents common problems"
এখানে বলে নেই , আমার ব্লগ না পড়ে মূল লেখাটা পড়ে নেয়াটাই সবচেয়ে ভালো হবে, খুবই সুন্দর, সাবলীল ভাষায় গোছানো লেখা। লেখাটা পড়ার আগে Enum সম্পর্কে যা জানতাম, আর পড়ার পরে যা শিখলাম - দুটোর মধ্যে অনেক তফাৎ। আমি মোটামুটি নিশ্চিত যে লেখাটা পড়লে আপনি নিজেও নতুন করে Enum সম্পর্কে অনেক কিছু জানবেন।
যাই হোক, ধরা যাক, আমরা একটা গেম বানাতে যাচ্ছি যার কয়েকটা ফিক্সড বা নির্দিষ্ট সংখ্যক কমান্ড আছে: "go", "look" , "take", "help", "quit". স্বভাবিক ভাবেই একেকটা কমান্ড একেকটা কাজ করবে। এটা implement করার জন্য প্রথমে আমরা নিচের কোড লিখতে পারি:
ছবি ১: স্ট্রিং কমান্ড Array |
ছবি ২: স্ট্রিং -এর switch |
উপরের ছবি-২ এ একটা ইচ্ছাকরেই একটা ভুল রাখা আছে। আরেকবার ভালো করে দেখে দেখুন তো, ভুলটা ধরতে পারেন কীনা!
যদি ভেবে থাকেন যে "কোডে default সেকশন তো নাই" - তাহলে আপনি আমার মতোই ভাবছেন। আমিও তাই ভেবেছিলাম। কিন্তু আসলে তা না. খেয়াল করে দেখুন, ছবি ১ এ দেয়া কমান্ড ছিল "help", কিন্তু switch স্টেটমেন্টে কিন্তু দেয়া আছে "Help " - শুরুতে capital H! তার মানে, কমান্ড "help"- এর কোনো ইম্প্লিমেন্টেশন আমাদের কোডে নাই।
এই ভুল কিন্তু compile time -এ ধরা পড়বে না, কারণ এটা কোডের লজিকে ভুল, "কোড" হিসাবে তো কোনো ভুল না।
মূলত উপরের কোডে ২ ধরণের সমস্যা আছে:
১. টাইপ সেফটি (type safety), ২. ইন্টারন্যাশনালাইজেশন (internationalization )
কিভাবে? ভেবে দেখুন, আমাদের টাইপ String হওয়ায় switch -এ যেকোনো String -ই ইনপুট হিসাবে দিয়ে দেয়া যাবে। আমাদের কমান্ড যে ফিক্সড সংখ্যক স্ট্রিং -র একটা সেট - আমাদের কোড কিন্তু এই টাইপ সেফটি দিতে পারছে না। আর ইন্টারন্যাশনালাইজেশন -এর সমস্যাটা একটু পরে ব্যাখ্যা করছি।
এই সমস্যা গুলো Enum ব্যবহার করলে থাকবে না। প্রথমে দেখে নেই Enum ব্যবহার করে কিভাবে কমান্ড গুলো ডিক্লায়ার করা যায় (খেয়াল রাখবেন Enum হচ্ছে ক্লাস, enum কী-ওয়ার্ড ):
ছবি ৩: enum ব্যবহার করে কমান্ড ডিক্লারেশন |
ছবি ৪: enum ব্যবহার করে switch স্টেটমেন্ট |
দেখতে হুবহু আগের মতো হলেও, এই কোড কিন্তু টাইপ সেইফ। আপনি চাইলেও অন্য কোনো কমান্ড পাস করতে পারবেন না। GO না লিখে Go লিখে দেখুন, compile time - এরর খাবে।
মূল লেখক মাইকেল কলিং-র মতো আমিও বলি, আপনি যদি এই পর্যন্ত পড়েই ভাবেন, Enum সম্পর্কে জেনে গেছেন, তবে ভুল করছেন। Enum এর মূল ব্যাপার বুঝতে আপনাকে আরো পড়তে হবে তবেই আপনি এর বেস্ট ফিচার গুলো জানবেন।
আসলে Enum কী? [ এই ব্যাপারটাই আমার কাছে নতুন, একদমই জানা ছিল না]
Enum ডিক্লারেশন আসলে একটা class। আর এর একেকটা value হচ্ছে একেকটা instance, বা অবজেক্ট। enum ডিক্লারেশন -এ ক্লাসের মতো ফিল্ড, কন্সট্রাক্টর কিংবা মেথড ইত্যাদি থাকতে পারে!! মাইকেল কলিং -র ভাষায় :
"Enum declarations are full classes, and the values listed are constant names referring to separate instances of these classes. The enum declaration can contain fields, constructors, and methods, just like other classes."
একটা উদাহরণ দিয়ে দেখা যাক:
ছবি ৫: ফিল্ড, কন্সট্রাক্টর আর মেথড দিয়ে enum ডিক্লারেশন |
আমাদের মূল enum এর মতোই এই enum -এর ফিক্সড বা নির্দিষ্ট সংখ্যক ভ্যালু (GO, LOOK, ...) আছে। তফাৎ হচ্ছে, এইক্ষেত্রে একটা স্ট্রিং টাইপ প্রাইভেট ফিল্ড (commandString) আছে, একটা কন্সটাক্টর আছে যা কিনা এই প্রাইভেট ফিল্ডকে স্ট্রিং এসাইন করে। আর একটা পাবলিক মেথড toStirng() আছে, যা আবার এই ফিল্ড -র ভ্যালু রিটার্ন করে।
অন্য ক্লাস-এর সাথে এর মূল তফাৎ হচ্ছে, অন্য ক্লাসের অবজেক্ট বা ইন্সটান্স শুরুতেই থাকে না বা exist করে না, একজন User বা ব্যবহারকারী ক্লাস-এর কন্সটাক্টর কল করে ইন্সটান্স তৈরি করে (চাইলে বার বার কল করে অনেকগুলো অবজেক্ট-ও তৈরি করতে পারবে, যদি ক্লাস singleton না হয়), কিন্তু enum -এর বেলায় শুরুতেই ইন্সটান্স বা অবজেক্ট গুলো তৈরী হয়ে যায়। আর একেকটা ভ্যালু বা ইন্সটান্স একবারই তৈরি হয়, পরে আর নতুন করে তৈরি করা যায় না। মনে রাখতে হবে, enum -এর কন্সট্রাক্টর পাবলিক না। আপনি চাইলেও পাবলিক করতে পারবেন না। public কী-ওয়ার্ড কন্সট্রাক্টর -এর সামনে বসিয়ে দেখুন, compile time এরর হবে।
কেউ প্রশ্ন করতেই পারে, enum -এর কন্সট্রাক্টর যদি পাবলিক নাই হয়, তাহলে এর ব্যবহারটা কী/কোথায়?
উত্তরটা ছবি ৫ -এ আমাদের পরিবর্তিত enum ডিক্লারেশন -এ আছে। খেয়াল করে দেখুন, enum ডিক্লারেশন এ আমরা শুধু GO না লিখে যখন GO("go") দিচ্ছি, তখন আসলে আমরা কন্সট্রাক্টর কল করে আমাদের মূল কমান্ড "go" কে প্যারামিটার হিসাবে পাস করছি - যা কিনা প্রাইভেট ফিল্ডে থাকছে। আর এভাবেই কন্সট্রাক্টর ব্যবহার করে আমরা (চাইলে আরো ফিল্ড যোগ করে) ইচ্ছামতো ফিল্ড ইনিশিয়ালাইজ করতে পারি।
Enum এর মূল বিষয় গুলোর একটা তালিকা করলে এমন হবে:
- Enum ডিক্লারেশন একটা Class, আর enum ভ্যালু গুলো ঐ ক্লাসের একেকটা অবজেক্ট
- প্রতিটা enum ভ্যালুর জন্য একটা করে ইন্সটান্স বা অবজেক্ট শুরুতেই তৈরি হয়।
- Enum ক্লাসের অবজেক্ট পরে আর তৈরি করা যায় না।
- ভিন্ন ভিন্ন enum ভ্যালু ভিন্ন ভিন্ন enum অবজেক্ট কে রেফার করবে, আর একই enum ভ্যালু সবসময়ই একটা অব্জেক্টকেই রেফার করবে।
- প্রতিটা enum ডিক্লারেশন তার নিজস্ব namespace তৈরি করে। যেমন একটা enum ক্লাস যদি হয় BoardGame আর আরেকটা যদি আমাদের CommandWord হয়, দুটোই GO, LOOK, TAKE ইত্যাদি ভ্যালু রাখতে পারবে, কোনো রকম ঝামেলা হবে না।
আমাদের মূল ইম্প্লিমেন্টেশন (ছবি ১, ২: স্ট্রিং আর switch নির্ভর) -এ ইন্টারন্যাশনালইজেশন -এর একটা সমস্যার কথা বলেছিলাম, মনে আছে? Enum ব্যবহার করে কীভাবে তা দূর করা যায় বলি।
ধরুন আপনার কোড এখন জার্মান ভাষায় কাজ করাতে হবে। জার্মান ভাষায় "help" কে লেখে "hilfe"। আমাদের মূল ইম্প্লিমেন্টেশন -এ শুধু Array তে (ছবি ১) সবগুলো কমান্ড কে জার্মান ভাষায় ট্রান্সলেট করলে কোড compile করবে কিন্তু কোনো কাজ করবে না। কারণ, switch স্টেটমেন্টে কোনো কমান্ড- ই মিলবে না, ঠিক না? কাজ করাতে গেলে switch স্টেটমেন্টেও স্ট্রিং গুলো (ছবি ২) কে জার্মান ভাষায় পরিবর্তিত করতে হবে। এখানে সমস্যাটা হচ্ছে আমরা স্ট্রিং শুধু ইনপুট না, কোড লজিকেও ব্যবহার করেছিলাম।
কিন্তু Enum ব্যবহার করে লেখা আমাদের কোডে শুধু এক জায়গায়, (ছবি ৫) কন্সট্রাক্টর -এ পরিবর্তন করলেই (যেমন, GO("help") -র বদলে GO("hilfe" ) লিখে দিয়ে) কাজ করবে। কারণ মূল ইম্প্লিমেন্টেশন কোডের লজিক স্ট্রিং -এর উপর নির্ভর করে না, enum ইন্সটান্স ব্যবহার করে কাজ করে।
সবশেষে, Enum এর বৈশিষ্ট্য গুলো দেখে অনেকেরই নিশ্চই Singleton Design Pattern -এর কথা মনে হচ্ছে। Singleton Design Pattern নিয়ে আগে একটা ব্লগ পোস্ট লিখেছিলাম, দেখে নিতে পারেন। মাইকেল কলিং তাঁর আর্টিকেলেটিতে Enum -এর আরো কিছু খুঁটিনাটি সহ Enum আর Singleton Pattern নিয়েও লিখেছেন। ব্যাপারটা নিয়ে আমি আর লিখছি না। মূল লেখাটা পড়ার জন্যই বাদ রেখে দিলাম।
ধন্যবাদ।
--ইশতিয়াক
No comments:
Post a Comment