English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

تحليل مفصل لمشكلة مقارنة Java

مشكلة المقارنة في Java هي مشكلة أساسية وسهلة التشويه. اليوم، سنقوم بمراجعة بعض النقاط التي يمكن أن تكون مصدرًا للأخطاء، ونقوم بتنظيمها بشكل مفصل، على أمل أن تكون مفيدة للتعلم والمقابلات.

الجزء الأول: الفرق بين == و equals()

أولاً، يجب علينا معرفة الفرق بين == و equals(). == يقوم بمقارنة العناوين، بالنسبة للأنواع الأساسية، يقوم == بمقارنة القيمة، أما بالنسبة للأنواع المعبأة، يقوم بمقارنة العناوين. يجب الانتباه إلى نوع String، حيث يمكن استخدام == بشكل تلقائي، مما يؤدي إلى ارتكاب أخطاء. طريقة equals() هي طريقة في Object، ونحن نعلم أن كل فئة في Java تنسب بشكل افتراضي إلى Object، لذا سيكون لدينا طريقة equals() في كل فئة. كما هو موضح في الشكل التالي لطريقة equals() في Object:

من خلال النظر في المصدر، يمكن رؤية أن طريقة equals() في Object هي أيضًا تستخدم ==، لذا فإن ما يقارن به هو أيضًا قيمة العنوان. لذا، إذا أردنا استخدام طريقة equals() للقيام بمقارنة أخرى، يجب علينا إعادة كتابة هذه الطريقة.

الجزء الثاني: أنواع البيانات الأساسية ووحداتها المعبأة

نحن نعلم جميعًا أن byte، short، int، long، boolean، char، double، float هذه الثمانية هي أنواع بيانات أساسية، ويتم تخزين المتغيرات المعلنة منها في ذاكرة الوحدة. بينما تتواجد متغيراتها المعبأة (Byte، Short، Integer، Long، Boolean، Character، Double) في ذاكرة النظام. بالنسبة للأنواع الأساسية، يبدو التحقق من المساواة بسيطًا نسبيًا، حيث يتم التحقق من المساواة باستخدام ==، والتحقق من الحجم باستخدام <، >، <=، >=. أما بالنسبة للأنواع المعبأة، فهي تختلف بعض الشيء.

أولاً، نرى نتائج تنفيذ الكود التالي للتحقق من إمكانية المساواة:

package dailytest;
import org.junit.Test;
/**
 * تلخيص المقارنة في Java
 * @author yrr
 */
public class JavaCompareTest {
  /**
   * التحقق من إمكانية المساواة لنوع Integer
   */
  @Test
  public void test01() {
    int n3 = 48;
    System.out.println("--------استخدام الجسم الجديد، عندما تكون القيمة بين [-127,128]---------");
    Integer n7 = new Integer(48);
    Integer n8 = new Integer(48);
    System.out.println(n7 == n8);  //خطأ
    System.out.println(n7 == n3);  //صحيح
    System.out.println("--------طريقة التخصيص المباشر، عندما تكون القيمة بين [-128,127]---------");
    Integer n1 = 48;
    Integer n2 = 48;
    System.out.println(n3 == n1); //صحيح
    System.out.println(n1 == n2); //صحيح
    System.out.println(n1.equals(n2)); //صحيح
    System.out.println(n1.equals(n3)); //صحيح
    System.out.println(n1.intValue() == n2.intValue()); //صحيح
    System.out.println("--------طريقة التخصيص المباشر، عندما لا تكون القيمة بين [-127,128]---------");
    Integer n4 = 128;
    Integer n5 = 128;
    int n6 = 128;
    System.out.println(n4 == n5);  //خطأ
    System.out.println(n4 == n6);  //صحيح
    System.out.println(n4.equals(n5)); //صحيح
    System.out.println(n4.equals(n6)); //صحيح
    System.out.println(n4.intValue() == n5.intValue()); //صحيح
    //استخدام طريقة Integer.intValue() يجب التحقق من عدم وجود null لتجنب NullPointException
  }
  /**
   * تحديد Long النوع إذا كان متساويًا
   */
  @Test
  public void test02() {
    //هنا يجب الانتباه، عند استخدام 'long' لا تحتاج إلى إضافة 'L' أو 'l'، ولكن يجب إضافة 'Long'، وإلا سيتم إرسال خطأ
    //بإضافة 'البناء' هنا، لنظهر الفرق
    long n3 = 48L;
    System.out.println("--------استخدام الجسم الجديد، عندما تكون القيمة بين [-127,128]---------");
    Long n7 = new Long(48);
    Long n8 = new Long(48);
    System.out.println(n7 == n8);  //خطأ
    System.out.println(n7 == n3);  //صحيح
    System.out.println("--------طريقة التخصيص المباشر، عندما تكون القيمة بين [-127,128]---------");
    Long n1 = 48L;
    Long n2 = 48L;
    System.out.println(n3 == n1); //صحيح
    System.out.println(n1 == n2); //صحيح
    System.out.println(n1.equals(n2)); //صحيح
    System.out.println(n1.equals(n3)); //صحيح
    System.out.println(n1.intValue() == n2.intValue()); //صحيح
    System.out.println("--------طريقة التخصيص المباشر، عندما لا تكون القيمة بين [-127,128]---------");
    Long n4 = 128L;
    Long n5 = 128L;
    long n6 = 128;
    System.out.println(n4 == n5);  //خطأ
    System.out.println(n4 == n6);  //صحيح
    System.out.println(n4.equals(n5)); //صحيح
    System.out.println(n4.equals(n6)); //صحيح
    System.out.println(n4.intValue() == n5.intValue()); //صحيح
    // هنگام استفاده از روش Long.intValue()، باید اطمینان حاصل شود که null نیست تا از بروز NullPointException جلوگیری شود  
  }
}

برای نتایج اجرایی بالا، توضیح زیر ارائه می‌شود:

ابتدا، برای استفاده از روش new برای ایجاد یک شیء Integer یا Long، زیرا شیءهای ایجاد شده در هر دو حالت در حافظه پوسته ایجاد می‌شوند، بنابراین حتی اگر اعداد مشابه باشند، برای ==، مقایسه آدرس انجام می‌شود، بنابراین بازگشت false خواهد بود. برای کلاس‌های بسته نوع داده‌های پایه، روش equals() به صورت پیش‌فرض نوشته شده است و مقایسه اندازه انجام می‌دهد، بنابراین می‌توان با استفاده از equals() بر اساس اندازه ارزیابی کرد. برای مقایسه بین متغیر Integer و int، می‌توان مشاهده کرد که مقایسه بر اساس اندازه انجام می‌شود، زیرا در مقایسه، نوع داده Integer به صورت خودکار به نوع داده int تبدیل می‌شود. توضیحات سه نکته بالا برای همه نوع داده‌های بسته اعمال می‌شود برای روش‌های مستقیم تخصیص، برای دو متغیر Integer با ارزش 48، با استفاده از == می‌توان true بازگشت داد، اما وقتی ارزش به 128 می‌رسد، بازگشت false خواهد بود. دلیل این امر این است که در واقع، برای روش مستقیم تخصیص مانند Integer n1 = 48;، در واقع از روش Integer.value() استفاده می‌شود. می‌توانیم به سادگی کد منبع Integer.value() را بررسی کنیم، مانند تصویر زیر:

می‌توانیم ببینیم که در اینجا یک شرط if وجود دارد، زمانی که ورودی i در محدوده [-128,127] قرار دارد، مستقیماً از آرایه IntegerCache بازمی‌گردد. بنابراین، برای اعداد در این محدوده، بازگشتی که انجام می‌شود آدرس آرایه است، بنابراین با استفاده از == می‌توان true بازگشت داد. اما برای اعداد خارج از این محدوده، اشیاء جدیدی ایجاد می‌شوند، بنابراین بازگشت false خواهد بود. این نتیجه برای نوع داده‌های Byte، Short، Integer، Long صادق است (برای اطلاعات بیشتر می‌توانید کد منبع value() آن‌ها را بررسی کنید)، زیرا محدوده Byte دقیقاً [-128,127] است، بنابراین برای نوع داده Byte، استفاده از == و equals() هیچ تفاوتی ندارد. 

برای مقایسه اندازه، استفاده از >، <، <=، >= مشکلی ندارد، زیرا آن‌ها تبدیل خودکار به نوع داده پایه را انجام می‌دهند. اما ما معمولاً توصیه می‌کنیم که از دو روش زیر برای مقایسه اندازه استفاده کنید:

برای مقایسه با نوع داده‌های پایه، از روش xxxValue() برای تبدیل به نوع داده پایه استفاده کنید و سپس از روش compareTo() برای مقایسه استفاده کنید. در کلاس‌های بسته، روش compareTo() به صورت پیش‌فرض نوشته شده است. با بررسی کد منبع compareTo()، می‌توان مشاهده کرد که در واقع آن از طریق تبدیل خودکار به نوع داده پایه استفاده می‌کند و سپس مقایسه می‌کند.

دوماً، مقایسه اشیاء جاوا

بعد از توضیحات بالا، مقایسه اشیاء آسان‌تر می‌شود. اصول همه یکسان هستند.

1. مقارنة أنواع البيانات String

على الرغم من ذلك، لا يمكن استخدام >، <=، >=، < في نوع البيانات String مباشرة، وإلا سيتم إرسال استثناء في التجميع.

package dailytest;
import org.junit.Test;
/**
 * تلخيص المقارنة في Java
 * @author yrr
 */
public class JavaCompareTest {
  @Test
  public void test03() {
    String s1 = new String("123");
    String s2 = new String("123");
    System.out.println(s1 == s2);  //false
    System.out.println(s1.equals(s2));
    String s3 = "234";
    String s4 = "234";
    System.out.println(s3 == s4);  //true
    System.out.println(s3.equals(s4));  //true
    //System.out.println(s1 <= s3); //The operator < is undefined for the argument type(s) java.lang.String, java.lang.String
    System.out.println(s1.compareTo(s3) < 0);  //true
  }
}

 2. مقارنة الكائنات الفئوية

النتيجة في مقارنة الكائنات الفئوية هي نفسها، ولكن مقارنة مع أنواع البيانات الأساسية وString، أكثر تعقيدًا قليلاً.

لحكم على مساواة两个人对象,يجب إعادة كتابة دالة equals() في فئة الحكم، على سبيل المثال، كود التشغيل التالي:

package dailytest;
import org.junit.Test;
/**
 * تلخيص المقارنة في Java
 * @author yrr
 */
public class JavaCompareTest {
  @Test
  public void test04() {
    Person p1 = new Person(\"yrr\",18);
    Person p2 = new Person("yrr",18);
    System.out.println(p1 == p2);  //false
    System.out.println(p2.equals(p1)); //true
  }
}
class Person{
  private String name;  
  private Integer age;
  public Person() {
  }
  public Person(String name, Integer age) {
    this.name = name;
    this.age = age;
  }
  public String getName() {
    return name;
  }
  public Integer getAge() {
    return age;
  }
  @Override
  public boolean equals(Object obj) {
    Person person = (Person) obj;
    return name.equals(person.getName()) && age.equals(person.getAge());
  }
}

وإذا كان من الممكن مقارنة حجمين لشخصين (وهذا هو السؤال الشائع في المقابلات)، هناك طريقتان:

فئة المقارنة تتحقق من واجهة Comparable وتعيد كتابة طريقة compareTo()، أو تعريف فئة تحقق من واجهة Comparator وتعيد كتابة طريقة compare()، الفرق بينهما: الأول يتم تعريفه في فئة المقارنة، والثاني يتم تعريفه خارج فئة المقارنة. من خلال هذا الفرق، يمكن رؤية مزايا وعيوب كلاهما بوضوح، الأول بسيط، لكنه يتطلب تعديل فئة المقارنة، والثاني لا يتطلب تعديل الكود الأصلي، مما يجعله أكثر مرونة.

الطريقة الأولى، مثال الكود كما يلي:

package dailytest;
import org.junit.Test;
/**
 * تلخيص المقارنة في Java
 * @author yrr
 */
public class JavaCompareTest {
  @Test
  public void test5() {
    Person p1 = new Person(\"yrr\",18);
    Person p2 = new Person(\"wx\",19);
    System.out.println(p1.compareTo(p2) < 0);
  }
}
class Person implements Comparable<Person>{
  private String name;  
  private Integer age;
  public Person() {
  }
  public Person(String name, Integer age) {
    this.name = name;
    this.age = age;
  }
  public Integer getAge() {
    return age;
  }
  @Override
  public int compareTo(Person o) {
    return this.getAge() - o.getAge();
  }  
}

الطريقة الثانية، مثال الكود كما يلي:

package comparator;
import java.util.Arrays;
import java.util.Comparator;
public class MyComparator {
  public static void main(String[] args) {
    User[] users = new User[] { new User(\"u1001\", 25), 
        new User(\"u1002\", 20), new User(\"u1003\", 21) ;
    Arrays.sort(users, new Comparator<User>() {
      @Override
      public int compare(User o1, User o2) {
        return o1.getAge() - o2.getAge();
      }
    });
    for (int i = 0; i < users.length; i++) { 
      User user = users[i]; 
      System.out.println(user.getId() + " " + user.getAge()); 
    } 
  }
}
class User { 
  private String id; 
  private int age; 
  public User(String id, int age) { 
    this.id = id; 
    this.age = age; 
  } 
  public int getAge() { 
    return age; 
  } 
  public void setAge(int age) { 
    this.age = age; 
  } 
  public String getId() { 
    return id; 
  } 
  public void setId(String id) { 
    this.id = id; 
  } 
}

هذا هو محتوى المشكلة في مقارنة Java الذي تحدثنا عنه في هذه المرة، إذا كان لديك أي أسئلة أخرى، يمكنك ترك تعليق في منطقة التعليقات أدناه، شكرًا لدعمك.

البيان: محتوى هذا المقال تم جمعه من الإنترنت، وله حقوق الملكية للمالك الأصلي، تم إضافة المحتوى من قبل مستخدمي الإنترنت بشكل تلقائي، ويتمتع هذا الموقع بلا ملكية، ولا يتم تعديل المحتوى بشكل يدوي، ولا يتحمل هذا الموقع أي مسؤولية قانونية متعلقة بذلك. إذا اكتشفت أي محتوى يشتبه في انتهاك حقوق النسخ، فلا تتردد في إرسال بريد إلكتروني إلى: notice#oldtoolbag.com (عند إرسال البريد الإلكتروني، يرجى استبدال #بـ @) لإبلاغنا، وتقديم الدليل المتعلق، إذا تم التحقق من ذلك، سيتم حذف المحتوى المزعوم فورًا.

توصيات للمستخدم