জাভা ডেভেলপাররা প্রায়শই এমন ক্লাস তৈরি করতে বাধ্য হন যেগুলো শুধুমাত্র ডেটা ধারণ করে, কোনো জটিল লজিক না। এই ক্লাসগুলোতে getter, setter, equals(), hashCode() এবং toString() মেথড লিখতে লিখতে কোডবেস বিশাল হয়ে যায় এবং রক্ষণাবেক্ষণ কঠিন হয়ে পড়ে। জাভা ১৪-এ প্রবর্তিত Records এই সমস্যার একটি মার্জিত সমাধান দেয়, যা আপনাকে মাত্র কয়েক লাইনে একটি অপরিবর্তনীয় (immutable) ডেটা ক্যারিয়ার ক্লাস তৈরি করতে দেয়। এই টিউটোরিয়ালে আমরা দেখব কীভাবে Records ব্যবহার করে আপনার জাভা কোডকে আরও সংক্ষিপ্ত, নিরাপদ এবং পাঠযোগ্য করা যায়।

ভূমিকা (Background)

জাভা ভাষায় ডেটা ট্রান্সফার অবজেক্ট (DTO) বা ভ্যালু অবজেক্ট তৈরি করা একটি সাধারণ কাজ। ঐতিহ্যগতভাবে, আমরা একটি ক্লাস লিখি, তারপর প্রাইভেট ফিল্ড ডিক্লেয়ার করি, কনস্ট্রাক্টর, getter মেথড, এবং equals(), hashCode(), toString() মেথড ওভাররাইড করি। এই পুনরাবৃত্তিমূলক কাজটি সময়সাপেক্ষ এবং ত্রুটির সম্ভাবনা বাড়ায়।

Records এই সমস্যা সমাধানের জন্য জাভা ১৪ (JEP 359) এ একটি প্রিভিউ ফিচার হিসেবে আসে এবং জাভা ১৬-এ স্থায়ী হয়। এটি একটি বিশেষ ধরনের ক্লাস যা অপরিবর্তনীয় (immutable) ডেটা মডেল তৈরি করতে সাহায্য করে। Records স্বয়ংক্রিয়ভাবে নিম্নলিখিত জিনিসগুলো জেনারেট করে:

  • একটি পাবলিক কনস্ট্রাক্টর যার প্যারামিটারগুলো কম্পোনেন্ট নামে পরিচিত।
  • প্রতিটি কম্পোনেন্টের জন্য একটি public getter মেথড (ফিল্ডের নাম অনুসারে)।
  • equals(), hashCode(), এবং toString() মেথডের স্বয়ংক্রিয় ইমপ্লিমেন্টেশন।

একটি Record ক্লাস ডিক্লেয়ার করতে শুধুমাত্র record কীওয়ার্ড এবং ফিল্ডের তালিকা প্রয়োজন। উদাহরণস্বরূপ:

public record Point(int x, int y) { }

এই একটি লাইনই একটি সম্পূর্ণ ডেটা ক্লাস তৈরি করে, যা ঐতিহ্যবাহী পদ্ধতিতে কমপক্ষে ৫০-৬০ লাইন কোডের প্রয়োজন হতো।

পূর্বশর্ত (Prerequisites)

এই টিউটোরিয়ালটি অনুসরণ করতে আপনার নিম্নলিখিত জিনিসগুলোর প্রয়োজন হবে:

  • জাভা ডেভেলপমেন্ট কিট (JDK): আপনার সিস্টেমে জাভা ১৬ বা তার উপরের ভার্সন ইনস্টল থাকতে হবে। আপনি java -version কমান্ড দিয়ে ভার্সন চেক করতে পারেন।
  • একটি IDE বা টেক্সট এডিটর: IntelliJ IDEA, Eclipse, VS Code (জাভা এক্সটেনশন সহ) অথবা আপনার পছন্দের যেকোনো এডিটর ব্যবহার করতে পারেন।
  • মৌলিক জাভা জ্ঞান: ক্লাস, অবজেক্ট, কনস্ট্রাক্টর এবং equals()/hashCode() মেথড সম্পর্কে প্রাথমিক ধারণা থাকা প্রয়োজন।

আপনি যদি জাভা ১৭ বা তার পরে ব্যবহার করেন, তাহলে Records ফিচারটি ডিফল্টভাবে সক্রিয় থাকে। জাভা ১৪ বা ১৫ ব্যবহার করলে প্রজেক্টে --enable-preview ফ্ল্যাগ ব্যবহার করতে হতে পারে।

ধাপে ধাপে বাস্তবায়ন (Step-by-step Implementation)

একটি Record ক্লাস তৈরি করা খুবই সহজ। আসুন আমরা একটি সাধারণ উদাহরণ দিয়ে শুরু করি — একটি Student রেকর্ড যা নাম, রোল নম্বর এবং গ্রেড ধারণ করে।

ধাপ ১: একটি রেকর্ড ডিক্লেয়ার করুন

record কীওয়ার্ড ব্যবহার করে একটি রেকর্ড ডিক্লেয়ার করুন। কম্পোনেন্টগুলোকে কনস্ট্রাক্টর প্যারামিটারের মতো লিখুন:

public record Student(String name, int rollNumber, String grade) { }

ব্যাখ্যা: এই কোডটি একটি পাবলিক রেকর্ড Student তৈরি করে যার তিনটি কম্পোনেন্ট আছে: name (String), rollNumber (int), এবং grade (String)। কম্পাইলার স্বয়ংক্রিয়ভাবে একটি কনস্ট্রাক্টর, তিনটি getter মেথড (name(), rollNumber(), grade()), এবং equals(), hashCode(), toString() মেথড জেনারেট করে। আপনি কোনো অতিরিক্ত কোড লিখবেন না।

ধাপ ২: রেকর্ডের অবজেক্ট তৈরি করুন

রেকর্ডের অবজেক্ট সাধারণ ক্লাসের মতোই new কীওয়ার্ড দিয়ে তৈরি করুন:

Student student1 = new Student("Rahim", 101, "A");
System.out.println(student1.name()); // "Rahim" প্রিন্ট করবে
System.out.println(student1.rollNumber()); // 101 প্রিন্ট করবে

ব্যাখ্যা: এখানে student1 একটি Student রেকর্ডের ইনস্ট্যান্স। name(), rollNumber(), এবং grade() মেথডগুলো সরাসরি অ্যাক্সেস করা যায়। মনে রাখবেন, এগুলো getter মেথড নয় — এদের নাম কম্পোনেন্টের নামের সাথে মিলে যায়।

ধাপ ৩: রেকর্ডে কাস্টম মেথড যোগ করুন

রেকর্ডের ভিতরে আপনি কাস্টম মেথডও যোগ করতে পারেন। তবে শুধুমাত্র কম্প্যাক্ট কনস্ট্রাক্টর (যা প্যারামিটার ভ্যালিডেশন করে) বা অতিরিক্ত মেথড যোগ করা যায়:

public record Student(String name, int rollNumber, String grade) {
    // কম্প্যাক্ট কনস্ট্রাক্টর - ভ্যালিডেশন
    public Student {
        if (rollNumber < 0) {
            throw new IllegalArgumentException("Roll number cannot be negative");
        }
        // name এবং grade স্বয়ংক্রিয়ভাবে অ্যাসাইন হয়
    }

    // একটি কাস্টম মেথড
    public String displayInfo() {
        return "Name: " + name + ", Roll: " + rollNumber + ", Grade: " + grade;
    }
}

ব্যাখ্যা: কম্প্যাক্ট কনস্ট্রাক্টর (public Student { ... }) ইনপুট ভ্যালিডেশনের জন্য ব্যবহার করা হয়। এখানে আমরা রোল নম্বর নেগেটিভ হলে একটি এক্সেপশন থ্রো করছি। কাস্টম মেথড displayInfo() রেকর্ডের ডেটা ফরম্যাট করে দেখায়।

সম্পূর্ণ উদাহরণ (Complete Example)

নিচে একটি সম্পূর্ণ, রানযোগ্য উদাহরণ দেওয়া হল যা Records এর ব্যবহার স্পষ্টভাবে প্রদর্শন করে:

public record Book(String title, String author, int year) {
    // কম্প্যাক্ট কনস্ট্রাক্টর
    public Book {
        if (year < 0) {
            throw new IllegalArgumentException("Year cannot be negative: " + year);
        }
    }

    // একটি ইউটিলিটি মেথড
    public boolean isClassic() {
        return year < 1950;
    }
}

public class RecordDemo {
    public static void main(String[] args) {
        Book book1 = new Book("The Great Gatsby", "F. Scott Fitzgerald", 1925);
        Book book2 = new Book("1984", "George Orwell", 1949);
        
        // স্বয়ংক্রিয় toString()
        System.out.println(book1); // আউটপুট: Book[title=The Great Gatsby, author=F. Scott Fitzgerald, year=1925]
        
        // equals() এবং hashCode() ব্যবহার
        Book book3 = new Book("The Great Gatsby", "F. Scott Fitzgerald", 1925);
        System.out.println(book1.equals(book3)); // true
        
        // কাস্টম মেথড কল
        System.out.println(book1.isClassic()); // true (1925 < 1950)
        System.out.println(book2.isClassic()); // false (1949 < 1950? না, 1949 < 1950? আসলে 1949 < 1950 true)
        
        // ভ্যালিডেশন টেস্ট
        try {
            Book invalidBook = new Book("Invalid", "Author", -1);
        } catch (IllegalArgumentException e) {
            System.out.println("Error: " + e.getMessage());
        }
    }
}

আউটপুট ব্যাখ্যা: প্রথম println রেকর্ডের স্বয়ংক্রিয় toString() মেথড কল করে, যা একটি ফরম্যাটেড স্ট্রিং আউটপুট দেয়। equals() মেথড দুটি ভিন্ন অবজেক্টের তুলনা করে এবং true রিটার্ন করে কারণ তাদের সব কম্পোনেন্ট সমান। isClassic() মেথড দেখায় যে আমরা রেকর্ডের ভিতরে কাস্টম লজিক যোগ করতে পারি।

সাধারণ সমস্যা ও সমাধান (Common Issues)

Records ব্যবহার করার সময় কিছু সাধারণ সমস্যা হতে পারে:

  • অপরিবর্তনীয়তা (Immutability): রেকর্ডের ফিল্ডগুলো ডিফল্টভাবে final এবং private। আপনি এগুলোর মান পরিবর্তন করতে পারবেন না। কোনো ফিল্ড পরিবর্তন করতে চাইলে আপনাকে একটি নতুন রেকর্ড অবজেক্ট তৈরি করতে হবে।

    // ভুল: book1.title = "New Title"; // কম্পাইল এরর
    // সঠিক: Book updatedBook = new Book("New Title", book1.author(), book1.year());
    
  • সিরিয়ালাইজেশন: Records স্বয়ংক্রিয়ভাবে Serializable ইমপ্লিমেন্ট করে না। যদি আপনার রেকর্ডকে সিরিয়ালাইজ করতে হয়, তাহলে ম্যানুয়ালি implements Serializable যোগ করুন।

    public record MyData(int id) implements Serializable { }
    
  • ইনহেরিটেন্স: Records অন্য ক্লাসকে এক্সটেন্ড করতে পারে না (যেমন abstract ক্লাস)। তবে তারা ইন্টারফেস ইমপ্লিমেন্ট করতে পারে।

    public record Color(int red, int green, int blue) implements Comparable<Color> {
        // compareTo() ইমপ্লিমেন্ট করতে হবে
    }
    
  • জাভা ১৪/১৫ প্রিভিউ মোড: আপনি যদি জাভা ১৪ বা ১৫ ব্যবহার করেন, তাহলে প্রজেক্টে --enable-preview ফ্ল্যাগ সক্রিয় করতে হবে। IDE-তে এটি সেটিংসে কনফিগার করা যায়।

সারসংক্ষেপ (Summary)

জাভা Records একটি শক্তিশালী ফিচার যা ডেটা ক্যারিয়ার ক্লাস তৈরি করার প্রক্রিয়াকে সহজ ও নিরাপদ করে তোলে। এর প্রধান সুবিধাগুলো হলো:

  • কম কোড: একটি রেকর্ড ডিক্লেয়ার করতে মাত্র একটি লাইন লাগে।
  • অটোমেটেড মেথড: equals(), hashCode(), toString() স্বয়ংক্রিয়ভাবে জেনারেট হয়।
  • অপরিবর্তনীয়তা: সব ফিল্ড final হওয়ায় ডেটা নিরাপদ থাকে।
  • ভ্যালিডেশন: কম্প্যাক্ট কনস্ট্রাক্টর ব্যবহার করে ইনপুট ভ্যালিডেশন সহজে করা যায়।

আপনার প্রজেক্টে যেখানে শুধুমাত্র ডেটা ধারণ করা প্রয়োজন (যেমন API রেসপন্স, ডেটাবেস রো, কনফিগারেশন), সেখানে Records ব্যবহার করুন। এটি আপনার কোডকে আরও পরিষ্কার, রক্ষণাবেক্ষণযোগ্য এবং ডেভেলপার-বান্ধব করে তুলবে।

Share