আচ্ছা, ধরুন আপনি একটি জাভা লাইব্রেরি বানাচ্ছেন যেখানে কিছু নির্দিষ্ট ধরনের অবজেক্ট আছে, যেমন Shape। কিন্তু আপনি চান না যে কেউ গিয়ে ইচ্ছামতো Shape-এর নতুন সাবক্লাস বানিয়ে ফেলুক। কারণ সেটা আপনার ডিজাইনের মূল উদ্দেশ্যকে নষ্ট করে দিতে পারে। জাভা ১৭-এ আসা Sealed Classes (সিল করা ক্লাস) এই সমস্যার সমাধান করে। এটি আপনাকে বলে দেয়, "এই ক্লাস বা ইন্টারফেসকে শুধুমাত্র এই নির্দিষ্ট সাবক্লাসগুলোই এক্সটেন্ড করতে পারবে, আর কেউ নয়।" এই টিউটোরিয়ালে আমরা শিখবো কীভাবে sealed classes ডিক্লেয়ার করতে হয়, তাদের সীমাবদ্ধতা কী, এবং কেন এটা জাভার প্যাটার্ন ম্যাচিংয়ের জন্য এত গুরুত্বপূর্ণ।
ভূমিকা (Background)
জাভাতে ক্লাস বা ইন্টারফেসকে sealed ঘোষণা করার মানে হলো, আপনি কম্পাইলারকে জানিয়ে দিচ্ছেন যে এই টাইপের কেবলমাত্র কিছু নির্দিষ্ট অনুমোদিত সাবটাইপ (subtypes) থাকতে পারে। এটি মূলত ইনহেরিট্যান্সের (inheritance) উপর নিয়ন্ত্রণ আনে। আগে আমরা final ক্লাস ব্যবহার করে যেকোনো এক্সটেনশন বন্ধ করে দিতাম, কিন্তু sealed আরও নমনীয়—আপনি একটি নির্দিষ্ট সেটের সাবক্লাসকে অনুমতি দিতে পারেন।
ধরা যাক, আপনি Vehicle নামে একটি ক্লাস বানালেন। কিন্তু আপনি চান না যে কেউ গিয়ে Spaceship বা Unicycle বানিয়ে ফেলুক। আপনি শুধু Car, Bike, এবং Truck-কে অনুমতি দিতে চান। sealed ক্লাস ব্যবহার করে আপনি এই সীমাবদ্ধতা কম্পাইলার লেভেলে enforce করতে পারেন।
পূর্বশর্ত (Prerequisites)
এই টিউটোরিয়ালটি অনুসরণ করার জন্য আপনার যা যা জানা দরকার:
- জাভা ১৭ বা তার পরের সংস্করণ: Sealed classes জাভা ১৭-এ একটি প্রিভিউ ফিচার হিসেবে এসেছিল এবং জাভা ১৭-এ এটি স্ট্যান্ডার্ড হয়েছে। আপনার JDK কমপক্ষে ১৭ হতে হবে।
- ইনহেরিট্যান্সের (Inheritance) মৌলিক জ্ঞান:
extendsএবংimplementsকীওয়ার্ড সম্পর্কে আপনার পরিষ্কার ধারণা থাকতে হবে। - ইন্টারফেসের (Interface) ধারণা: Sealed ইন্টারফেসও তৈরি করা যায়, তাই ইন্টারফেস বেসিক জানা জরুরি।
- প্যাটার্ন ম্যাচিংয়ের (Pattern Matching) প্রাথমিক ধারণা:
instanceofএবং প্যাটার্ন ভেরিয়েবল সম্পর্কে জানলে sealed classes-এর সুবিধা বুঝতে সহজ হবে।
ধাপে ধাপে বাস্তবায়ন (Step-by-step Implementation)
Sealed class বানানোর মূল ধাপগুলো নিচে দেওয়া হলো:
১. Sealed ক্লাস ডিক্লেয়ার করা
ক্লাসের নামের আগে sealed মডিফায়ার ব্যবহার করুন। তারপর permits ক্লজ দিয়ে বলুন কোন সাবক্লাসগুলোকে অনুমতি দেওয়া হচ্ছে।
public sealed class Shape permits Circle, Rectangle, Triangle {
// ক্লাসের বডি
}
এখানে কী হলো: Shape ক্লাসটি sealed। এটি শুধুমাত্র Circle, Rectangle, এবং Triangle-কে এক্সটেন্ড করার অনুমতি দেয়। অন্য কোনো ক্লাস যদি Shape-কে এক্সটেন্ড করার চেষ্টা করে, তাহলে কম্পাইলার এরর দেবে।
২. অনুমোদিত সাবক্লাস তৈরি করা
প্রত্যেকটি সাবক্লাসকে অবশ্যই final, sealed, অথবা non-sealed হিসেবে ডিক্লেয়ার করতে হবে। কারণ sealed class-এর চেইন অবশ্যই শেষ হতে হবে।
public final class Circle extends Shape {
// Circle কে আর কেউ এক্সটেন্ড করতে পারবে না
}
public sealed class Rectangle extends Shape permits Square {
// Rectangle শুধু Square-কে অনুমতি দেবে
}
public non-sealed class Triangle extends Shape {
// Triangle-কে যেকোনো ক্লাস এক্সটেন্ড করতে পারবে
}
এখানে কী হলো: Circle হলো final—এটি sealed চেইনের শেষ। Rectangle হলো sealed—এটি নিজেও একটি sealed ক্লাস, যা শুধু Square-কে অনুমতি দেয়। আর Triangle হলো non-sealed—এটি আবার খুলে দেওয়া, অর্থাৎ যে কেউ Triangle-কে এক্সটেন্ড করতে পারে।
৩. Sealed ইন্টারফেস (ঐচ্ছিক)
ইন্টারফেসও sealed হতে পারে। এটি তখনই কাজে লাগে যখন আপনি একটি ইন্টারফেসের নির্দিষ্ট কিছু ইমপ্লিমেন্টেশন ঠিক করে দিতে চান।
public sealed interface Vehicle permits Car, Bike {
void start();
}
public final class Car implements Vehicle {
@Override
public void start() {
System.out.println("Car starting...");
}
}
এখানে কী হলো: Vehicle ইন্টারফেসটি শুধুমাত্র Car এবং Bike ক্লাস দ্বারা ইমপ্লিমেন্ট করা যাবে। এটি ডিজাইনকে আরও শক্তিশালী করে।
৪. প্যাটার্ন ম্যাচিংয়ের সাথে ব্যবহার
Sealed classes-এর সবচেয়ে বড় সুবিধা হলো প্যাটার্ন ম্যাচিং (pattern matching for instanceof) এর সাথে। কম্পাইলার জানে যে কেবলমাত্র নির্দিষ্ট কয়েকটি টাইপ থাকতে পারে, তাই switch এক্সপ্রেশনে আপনাকে else বা default কেস লিখতে হবে না।
public void printShape(Shape shape) {
switch (shape) {
case Circle c -> System.out.println("Circle with radius: " + c.radius());
case Rectangle r -> System.out.println("Rectangle with width: " + r.width());
case Triangle t -> System.out.println("Triangle with base: " + t.base());
// no default needed because all subtypes are covered
}
}
এখানে কী হলো: কম্পাইলার নিশ্চিত করে যে আপনি সব কেস কভার করেছেন। যদি কেউ নতুন সাবক্লাস যোগ করে, তাহলে কম্পাইলার আপনাকে switch আপডেট করতে বাধ্য করবে। এটি কোডকে আরও নিরাপদ এবং রিফ্যাক্টরিং-ফ্রেন্ডলি করে।
সম্পূর্ণ উদাহরণ (Complete Example)
নিচে একটি সম্পূর্ণ জাভা প্রোগ্রাম দেওয়া হলো যা sealed classes এবং তাদের ব্যবহার দেখায়:
// Sealed class hierarchy
sealed class Animal permits Dog, Cat, Bird {
public void sound() {
System.out.println("Animal makes sound");
}
}
final class Dog extends Animal {
@Override
public void sound() {
System.out.println("Bark");
}
}
final class Cat extends Animal {
@Override
public void sound() {
System.out.println("Meow");
}
}
non-sealed class Bird extends Animal {
@Override
public void sound() {
System.out.println("Chirp");
}
}
// Main class to test
public class SealedClassDemo {
public static void main(String[] args) {
// Using pattern matching with switch (Java 17+)
Animal animal = new Dog();
switch (animal) {
case Dog d -> System.out.println("It's a dog: " + d.getClass().getSimpleName());
case Cat c -> System.out.println("It's a cat: " + c.getClass().getSimpleName());
case Bird b -> System.out.println("It's a bird: " + b.getClass().getSimpleName());
// No default needed because all subtypes are covered
}
// Trying to create a new subclass would cause compilation error
// class Fish extends Animal {} // ERROR: Fish is not in the permits list
}
}
এখানে কী হলো: আমরা Animal নামে একটি sealed ক্লাস বানিয়েছি, যার অনুমোদিত সাবক্লাস হলো Dog, Cat, এবং Bird। main মেথডে আমরা প্যাটার্ন ম্যাচিং ব্যবহার করে দেখেছি কিভাবে কম্পাইলার সব কেস চেক করে। আপনি যদি Fish নামে আরেকটি ক্লাস বানানোর চেষ্টা করেন, তাহলে কম্পাইলার এরর দেবে কারণ Fish permits লিস্টে নেই।
সাধারণ সমস্যা ও সমাধান (Common Issues)
Sealed classes ব্যবহার করার সময় কিছু সাধারণ সমস্যা দেখা দিতে পারে:
১. "ক্লাসটি একই প্যাকেজে নেই" এরর
Sealed class-এর অনুমোদিত সাবক্লাসগুলোকে অবশ্যই একই প্যাকেজে (বা একই মডিউলে) থাকতে হবে। যদি সাবক্লাস ভিন্ন প্যাকেজে থাকে, তাহলে কম্পাইলার এরর দেবে।
সমাধান: সাবক্লাসগুলোকে একই প্যাকেজে রাখুন। অথবা মডিউল সিস্টেম ব্যবহার করলে module-info.java-তে exports স্টেটমেন্ট দিয়ে ঠিক করুন।
২. "সাবক্লাসটি final/sealed/non-sealed নয়" এরর
প্রত্যেকটি অনুমোদিত সাবক্লাসকে অবশ্যই final, sealed, অথবা non-sealed হিসেবে চিহ্নিত করতে হবে। যদি আপনি কোনো মডিফায়ার না দেন, তাহলে কম্পাইলার এরর দেবে।
সমাধান: প্রতিটি সাবক্লাসের শুরুতে final, sealed, অথবা non-sealed লিখতে ভুলবেন না।
৩. "permits লিস্টে ক্লাসটি নেই" এরর
আপনি যদি permits লিস্টে একটি ক্লাস উল্লেখ না করেন, কিন্তু সেই ক্লাসটি extends করে, তাহলে কম্পাইলার এরর দেবে।
সমাধান: সবসময় permits লিস্টে সব অনুমোদিত সাবক্লাসের নাম উল্লেখ করুন।
সারসংক্ষেপ (Summary)
Sealed classes জাভা ১৭-এর একটি শক্তিশালী ফিচার যা ইনহেরিট্যান্সকে আরও নিয়ন্ত্রিত এবং নিরাপদ করে তোলে। এর মূল সুবিধাগুলো হলো:
- টাইপ সেফটি (Type Safety): কম্পাইলার নিশ্চিত করে যে আপনি সব সম্ভাব্য সাবটাইপ কভার করেছেন।
- প্যাটার্ন ম্যাচিং সহজতর:
switchএক্সপ্রেশনেdefaultকেসের দরকার নেই, কারণ সব টাইপ জানা। - ডিজাইন ক্লিয়ারিটি: আপনার API-এর ডকুমেন্টেশন স্পষ্ট হয় যে কে কাকে এক্সটেন্ড করতে পারে।
- রিফ্যাক্টরিং-বান্ধব: নতুন সাবক্লাস যোগ করলে কম্পাইলার আপনাকে পুরনো কোড আপডেট করতে বাধ্য করে।
এখন আপনি নিজের প্রজেক্টে sealed classes ব্যবহার করে দেখতে পারেন। এটি বিশেষ করে লাইব্রেরি বা ফ্রেমওয়ার্ক ডিজাইন করার সময় খুবই কার্যকরী।