আমাদের দৈনন্দিন জাভা কোডিংয়ে equals() এবং == নিয়ে বিভ্রান্তি প্রায়ই দেখা যায়। নতুন ডেভেলপাররা মনে করেন এরা একই জিনিস, কিন্তু বাস্তবে এদের কাজ সম্পূর্ণ ভিন্ন। এই টিউটোরিয়ালে আমরা শিখবো কিভাবে == অপারেটর রেফারেন্স তুলনা করে, আর equals() মেথড অবজেক্টের ভ্যালু তুলনা করে — এবং কখন কোনটা ব্যবহার করা উচিত। পাশাপাশি, আমরা স্ট্রিং, কাস্টম ক্লাস, এবং নাল সেফটি নিয়েও আলোচনা করবো।

ভূমিকা (Background)

জাভাতে == হলো একটি রিলেশনাল অপারেটর যা দুটি প্রিমিটিভ টাইপের ভ্যালু বা দুটি অবজেক্টের রেফারেন্সের মেমোরি অ্যাড্রেস তুলনা করে। অন্যদিকে, equals() হলো Object ক্লাসের একটি মেথড যা স্ট্রিং, ইন্টিজার, বা কাস্টম ক্লাসের মধ্যে ভ্যালু-ভিত্তিক সমতা (content equality) চেক করে।

int a = 10;
int b = 10;
System.out.println(a == b); // true - কারণ প্রিমিটিভ ভ্যালু সমান

String s1 = new String("Hello");
String s2 = new String("Hello");
System.out.println(s1 == s2); // false - কারণ দুটি ভিন্ন অবজেক্ট রেফারেন্স
System.out.println(s1.equals(s2)); // true - কারণ কনটেন্ট সমান

উপরের কোডে দেখা যাচ্ছে, == প্রিমিটিভের জন্য ভ্যালু চেক করে, কিন্তু অবজেক্টের জন্য মেমোরি লোকেশন চেক করে। equals() মেথডটি স্ট্রিং ক্লাসের ওভাররাইডেড ভার্সনে ক্যারেক্টার বাই ক্যারেক্টার ম্যাচ করে।

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

এই টিউটোরিয়ালটি বুঝতে আপনার যা যা জানা দরকার:

  • জাভা বেসিক সিনট্যাক্স: আপনি যদি public static void main লিখতে পারেন এবং প্রিমিটিভ ও রেফারেন্স টাইপের মধ্যে পার্থক্য জানেন, তাহলে যথেষ্ট।
  • IDE বা টার্মিনাল: যেকোনো জাভা IDE (যেমন IntelliJ, Eclipse) বা কমান্ড লাইন সহ পরিবেশ। কোড কম্পাইল ও রান করতে পারবেন।
  • Object ক্লাস সম্পর্কে ধারণা: জানেন যে Object হলো সব জাভা ক্লাসের সুপারক্লাস, এবং এতে equals()hashCode() মেথড আছে।
  • ইনহেরিট্যান্স বেসিক: পলিমরফিজম এবং মেথড ওভাররাইডিং বুঝলে ভালো, কারণ equals() মেথডটি ওভাররাইড করা হয়।

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

১. == অপারেটর দিয়ে প্রিমিটিভ ও রেফারেন্স তুলনা

প্রথমে একটি সহজ ক্লাস তৈরি করি:

public class ComparisonDemo {
    public static void main(String[] args) {
        int x = 5;
        int y = 5;
        System.out.println("x == y: " + (x == y)); // true

        String str1 = "Java";
        String str2 = "Java";
        System.out.println("str1 == str2: " + (str1 == str2)); // true (স্ট্রিং পুল)

        String str3 = new String("Java");
        System.out.println("str1 == str3: " + (str1 == str3)); // false (হিপে নতুন অবজেক্ট)
    }
}

এখানে লক্ষ্য করুন, স্ট্রিং লিটারেল str1str2 জাভা স্ট্রিং পুলে একই ইন্টার্নড অবজেক্টকে পয়েন্ট করে, তাই == true দেয়। কিন্তু new দিয়ে তৈরি str3 হিপে ভিন্ন মেমোরি ব্লকে থাকে।

২. equals() মেথড ওভাররাইড না করা

ডিফল্ট Object.equals() আসলে == এর মতোই কাজ করে:

class Person {
    String name;
    Person(String name) { this.name = name; }
}

public class EqualsTest {
    public static void main(String[] args) {
        Person p1 = new Person("Rahim");
        Person p2 = new Person("Rahim");
        System.out.println(p1.equals(p2)); // false - ডিফল্ট ইমপ্লিমেন্টেশন
    }
}

এখানে Person ক্লাসে equals() ওভাররাইড না করায়, জাভা Object ক্লাসের ডিফল্ট মেথড ব্যবহার করে, যা শুধু রেফারেন্স তুলনা করে। তাই p1p2 ভিন্ন অবজেক্ট হওয়ায় ফলাফল false।

৩. কাস্টম ক্লাসে equals() ওভাররাইড

আমাদের নিজস্ব লজিক দিয়ে equals() ওভাররাইড করতে হবে:

class Person {
    String name;
    int age;

    Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Person person = (Person) obj;
        return age == person.age && name.equals(person.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

এখানে equals() মেথডটি প্রথমে রেফারেন্স সমতা চেক করে, তারপর নাল ও টাইপ চেক, এবং শেষে ফিল্ড বাই ফিল্ড তুলনা করে। hashCode() ওভাররাইড করাও জরুরি, কারণ equals() ওভাররাইড করলে hashCode() কন্ট্রাক্ট ভাঙা উচিত নয়।

৪. স্ট্রিং ও র‍্যাপার ক্লাসের বিশেষ আচরণ

জাভার বিল্ট-ইন ক্লাস যেমন String, Integer, Double ইতিমধ্যেই equals() ওভাররাইড করেছে:

public class WrapperDemo {
    public static void main(String[] args) {
        Integer i1 = 100;
        Integer i2 = 100;
        System.out.println(i1 == i2); // true (ক্যাশিং -127 to 127)
        System.out.println(i1.equals(i2)); // true

        Integer i3 = 200;
        Integer i4 = 200;
        System.out.println(i3 == i4); // false (ক্যাশের বাইরে)
        System.out.println(i3.equals(i4)); // true
    }
}

ইন্টিজার ক্লাসের অভ্যন্তরীণ ক্যাশিংয়ের কারণে == কিছু মানের জন্য true দেয়, কিন্তু সবসময় নির্ভরযোগ্য নয়। তাই র‍্যাপার ক্লাসের সাথে সবসময় equals() ব্যবহার করা নিরাপদ।

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

নিচে একটি সম্পূর্ণ, রানযোগ্য জাভা প্রোগ্রাম দেওয়া হলো যা ==equals() এর পার্থক্য স্পষ্টভাবে দেখায়:

import java.util.Objects;

class Employee {
    String id;
    String name;

    Employee(String id, String name) {
        this.id = id;
        this.name = name;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        Employee employee = (Employee) obj;
        return id.equals(employee.id) && name.equals(employee.name);
    }

    @Override
    public int hashCode() {
        return Objects.hash(id, name);
    }
}

public class MainApplication {
    public static void main(String[] args) {
        Employee e1 = new Employee("E101", "Rahim");
        Employee e2 = new Employee("E101", "Rahim");
        Employee e3 = e1;

        // == অপারেটর
        System.out.println("e1 == e2: " + (e1 == e2)); // false
        System.out.println("e1 == e3: " + (e1 == e3)); // true (একই রেফারেন্স)

        // equals() মেথড
        System.out.println("e1.equals(e2): " + e1.equals(e2)); // true
        System.out.println("e1.equals(e3): " + e1.equals(e3)); // true

        // স্ট্রিং এর সাথে
        String s1 = "Hello";
        String s2 = new String("Hello");
        System.out.println("s1 == s2: " + (s1 == s2)); // false
        System.out.println("s1.equals(s2): " + s1.equals(s2)); // true
    }
}

আউটপুট:

e1 == e2: false
e1 == e3: true
e1.equals(e2): true
e1.equals(e3): true
s1 == s2: false
s1.equals(s2): true

এই উদাহরণটি দেখায় যে == রেফারেন্স সমতা চেক করে, আর equals() ভ্যালু সমতা চেক করে। e1.equals(e2) true দেয় কারণ আমরা idname ফিল্ডের ভিত্তিতে সমতা নির্ধারণ করেছি।

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

সমস্যা ১: নাল পয়েন্টার এক্সেপশন

String str = null;
if (str.equals("test")) { // NullPointerException!
    // ...
}

সমাধান: নাল চেক করে equals() কল করুন:

if ("test".equals(str)) { // নিরাপদ - str null হলে false
    // ...
}

সমস্যা ২: equals() ওভাররাইড না করলে অপ্রত্যাশিত আচরণ

HashMap বা HashSet ব্যবহার করলে, equals() ওভাররাইড না করায় ডুপ্লিকেট এন্ট্রি তৈরি হতে পারে:

Set<Employee> set = new HashSet<>();
set.add(new Employee("E101", "Rahim"));
set.add(new Employee("E101", "Rahim"));
System.out.println(set.size()); // 2, কিন্তু আমরা চাই 1

সমাধান: সবসময় equals()hashCode() একসাথে ওভাররাইড করুন।

সমস্যা ৩: টাইপ মিসম্যাচ

equals() প্যারামিটার হিসেবে Object নেয়, তাই টাইপ কাস্টিংয়ে সমস্যা হতে পারে:

@Override
public boolean equals(Object obj) {
    if (!(obj instanceof Employee)) return false; // নিরাপদ কাস্ট
    Employee other = (Employee) obj;
    // ...
}

instanceof ব্যবহার করলে ক্লাসের টাইপ চেক করা সহজ হয় এবং ClassCastException এড়ানো যায়।

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

এই টিউটোরিয়ালে আমরা শিখেছি:

  • == অপারেটর প্রিমিটিভ টাইপের জন্য ভ্যালু সমতা এবং অবজেক্টের জন্য মেমোরি রেফারেন্স সমতা চেক করে।
  • equals() মেথড ডিফল্টভাবে == এর মতো কাজ করে, কিন্তু স্ট্রিং, র‍্যাপার, এবং কাস্টম ক্লাসে ওভাররাইড করে ভ্যালু সমতা চেক করতে পারেন।
  • কাস্টম ক্লাসে সবসময় equals()hashCode() একসাথে ওভাররাইড করুন, বিশেষ করে যদি ক্লাসটি HashMap বা HashSet এ ব্যবহার করেন।
  • নাল সেফটি এর জন্য "test".equals(str) প্যাটার্ন ব্যবহার করুন, কারণ স্ট্রিং লিটারেল কখনো নাল হয় না।

মনে রাখবেন, জাভাতে অবজেক্ট তুলনার জন্য সঠিক টুল বেছে নেওয়া আপনার কোডের নির্ভরযোগ্যতা বাড়ায়। == শুধু মেমোরি অ্যাড্রেসের জন্য, আর equals() ভ্যালু লজিকের জন্য — এই পার্থক্যটি আপনার ডেভেলপমেন্ট ক্যারিয়ারে অনেক ভুল এড়াতে সাহায্য করবে।

Share