ما هي القضايا التي يجب أخذها في الاعتبار عند تجاوز يساوي hashCode في جافا ؟

StackOverflow https://stackoverflow.com/questions/27581

سؤال

ما هي القضايا / مطبات يجب مراعاتها عند تجاوز equals و hashCode?

هل كانت مفيدة؟

المحلول

نظرية (اللغة المحامين رياضيا ميلا):

equals() (جافادوك) يجب أن تحدد فيما يتعلق التعادل (يجب أن تكون انعكاسية, متماثل, ، متعدية).وبالإضافة إلى ذلك ، يجب أن يكون ثابت (إذا كانت الكائنات المعدلة ، فإنه يجب الحفاظ على العودة بنفس القيمة).وعلاوة على ذلك ، o.equals(null) يجب دائما return false.

hashCode() (جافادوك) كما يجب أن تكون ثابت (إذا كان الكائن لا تعديل في شروط equals(), فإنه يجب الحفاظ على العودة بنفس القيمة).

على العلاقة بين الطريقتين هو:

كلما a.equals(b), ثم a.hashCode() يجب أن يكون نفس b.hashCode().

في الممارسة:

إذا تجاوز أحد ، ثم عليك أن تلغي الأخرى.

استخدام نفس مجموعة من الحقول التي يمكنك استخدامها لحساب equals() لحساب hashCode().

استخدام ممتاز فئات المساعد EqualsBuilder و HashCodeBuilder من أباتشي العموم لانج المكتبة.على سبيل المثال:

public class Person {
    private String name;
    private int age;
    // ...

    @Override
    public int hashCode() {
        return new HashCodeBuilder(17, 31). // two randomly chosen prime numbers
            // if deriving: appendSuper(super.hashCode()).
            append(name).
            append(age).
            toHashCode();
    }

    @Override
    public boolean equals(Object obj) {
       if (!(obj instanceof Person))
            return false;
        if (obj == this)
            return true;

        Person rhs = (Person) obj;
        return new EqualsBuilder().
            // if deriving: appendSuper(super.equals(obj)).
            append(name, rhs.name).
            append(age, rhs.age).
            isEquals();
    }
}

تذكر أيضا:

عند استخدام تجزئة على أساس جمع أو خريطة مثل HashSet, LinkedHashSet, HashMap, Hashtable, أو WeakHashMap, تأكد من أن hashCode() من أهم الأشياء التي كنت وضعت في جمع لم يتغير بينما الكائن في المجموعة.الرصاص طريقة للتأكد من هذا هو جعل المفاتيح الخاصة بك غير قابل للتغيير ، التي لديها أيضا فوائد أخرى.

نصائح أخرى

هناك بعض القضايا يستحق أن يلاحظ إذا كنت تتعامل مع الفئات التي استمرت باستخدام كائن-العلاقة مخطط (ORM) مثل السبات ، إذا كنت لا أعتقد أن هذا غير معقول معقدة بالفعل!

كسول تحميلها الكائنات فرعية

إذا كانت الكائنات استمرت باستخدام ORM في كثير من الحالات سوف يكون التعامل مع وكلاء الديناميكي لتجنب التحميل كائن في وقت مبكر جدا من مخزن البيانات.هؤلاء الوكلاء يتم تنفيذها فرعية من الفئة الخاصة بك.وهذا يعني أنthis.getClass() == o.getClass() سيعود false.على سبيل المثال:

Person saved = new Person("John Doe");
Long key = dao.save(saved);
dao.flush();
Person retrieved = dao.retrieve(key);
saved.getClass().equals(retrieved.getClass()); // Will return false if Person is loaded lazy

إذا كنت تتعامل مع مكتب إدارة السجلات باستخدام o instanceof Person هو الشيء الوحيد الذي سوف تتصرف بشكل صحيح.

كسول تحميل كائنات فارغة-الميادين

ORMs عادة استخدام حاصل على قوة التحميل من كسول تحميلها الكائنات.وهذا يعني أن person.name سوف يكون null إذا person هو كسول تحميلها حتى لو person.getName() قوات التحميل و إرجاع "فلان الفلاني".في تجربتي هذه المحاصيل في كثير من الأحيان في hashCode() و equals().

إذا كنت تتعامل مع ORM ، تأكد دائما من استخدام حاصل ، و لا مجال المراجع في hashCode() و equals().

توفير كائن تغيير الدولة

الأشياء الثابتة في كثير من الأحيان استخدام id حقل مفتاح من وجوه.هذا المجال سوف يتم تحديثها تلقائيا عندما كائن هو أول حفظها.لا تستخدم حقل معرف في hashCode().ولكن يمكنك استخدامه في equals().

نمط أنا غالبا ما تستخدم هي

if (this.getId() == null) {
    return this == other;
}
else {
    return this.getId().equals(other.getId());
}

ولكن:لا يمكنك تضمين getId() في hashCode().إذا كنت تفعل عندما كائن استمرت ، hashCode التغييرات.إذا كان الكائن في HashSet, سوف "أبدا" العثور عليه مرة أخرى.

في بلدي Person على سبيل المثال, ربما سوف تستخدم getName() بالنسبة hashCode و getId() بالإضافة إلى getName() (فقط من أجل جنون العظمة) عن equals().لا بأس إذا كان هناك بعض المخاطر من "التصادم" على hashCode(), ولكن لم بخير equals().

hashCode() يجب استخدام غير تغيير فرعية من خصائص من equals()

توضيح حول obj.getClass() != getClass().

هذا البيان هو نتيجة equals() يجري الإرث غير ودية.JLS (لغة جافا مواصفات) تنص على أنه إذا A.equals(B) == true ثم B.equals(A) يجب أيضا العودة true.إذا قمت بحذف هذا البيان وراثة الفئات التي تجاوز equals() (تغيير السلوك) كسر هذه المواصفات.

النظر في المثال التالي ما يحدث عندما البيان حذف:

    class A {
      int field1;

      A(int field1) {
        this.field1 = field1;
      }

      public boolean equals(Object other) {
        return (other != null && other instanceof A && ((A) other).field1 == field1);
      }
    }

    class B extends A {
        int field2;

        B(int field1, int field2) {
            super(field1);
            this.field2 = field2;
        }

        public boolean equals(Object other) {
            return (other != null && other instanceof B && ((B)other).field2 == field2 && super.equals(other));
        }
    }    

القيام new A(1).equals(new A(1)) أيضا ، new B(1,1).equals(new B(1,1)) نتيجة نعطيه صحيح ، كما يجب.

يبدو هذا كل شيء جيد جدا, ولكن انظر ماذا يحدث لو كنا في محاولة لاستخدام كل فئات:

A a = new A(1);
B b = new B(1,1);
a.equals(b) == true;
b.equals(a) == false;

ومن الواضح أن هذا هو الخطأ.

إذا كنت ترغب في التأكد متماثل الشرط.أ=ب إذا ب=أ و Liskov إحلال مبدأ الدعوة super.equals(other) ليس فقط في حالة B سبيل المثال ، ولكن تحقق بعد A على سبيل المثال:

if (other instanceof B )
   return (other != null && ((B)other).field2 == field2 && super.equals(other)); 
if (other instanceof A) return super.equals(other); 
   else return false;

والتي سوف الإخراج:

a.equals(b) == true;
b.equals(a) == true;

حيث إذا a ليست إشارة من B, ثم قد يكون إشارة من الدرجة A (لأنك تمديد), في هذه الحالة يمكنك الاتصال super.equals() أيضا.

للحصول على الميراث الصديقة التنفيذ ، تحقق من تل كوهين هو الحل ، كيف تنفذ بشكل صحيح يساوي() الأسلوب ؟

موجز:

في كتابه فعالة لغة البرمجة جافا دليل (Addison-Wesley, 2001), جوشوا بلوخ يدعي أن "ببساطة ليس هناك طريقة توجيه instantiable فئة إضافة جانبا مع الاحتفاظ يساوي العقد". تل يختلف.

الحل هو تنفيذ يساوي() من خلال الاتصال آخر nonsymmetric blindlyEquals() في كلا الاتجاهين.blindlyEquals() هو تجاوز من قبل فرعية ، يساوي() يورث ، وعدم تجاوزها.

على سبيل المثال:

class Point {
    private int x;
    private int y;
    protected boolean blindlyEquals(Object o) {
        if (!(o instanceof Point))
            return false;
        Point p = (Point)o;
        return (p.x == this.x && p.y == this.y);
    }
    public boolean equals(Object o) {
        return (this.blindlyEquals(o) && o.blindlyEquals(this));
    }
}

class ColorPoint extends Point {
    private Color c;
    protected boolean blindlyEquals(Object o) {
        if (!(o instanceof ColorPoint))
            return false;
        ColorPoint cp = (ColorPoint)o;
        return (super.blindlyEquals(cp) && 
        cp.color == this.color);
    }
}

علما بأن يساوي() يجب أن تعمل عبر الميراث الهرمية إذا Liskov مبدأ الإحلال هو أن يكون راضيا.

لا تزال دهشتها أن أيا أوصى الجوافة المكتبة من أجل هذا.

 //Sample taken from a current working project of mine just to illustrate the idea

    @Override
    public int hashCode(){
        return Objects.hashCode(this.getDate(), this.datePattern);
    }

    @Override
    public boolean equals(Object obj){
        if ( ! obj instanceof DateAndPattern ) {
            return false;
        }
        return Objects.equal(((DateAndPattern)obj).getDate(), this.getDate())
                && Objects.equal(((DateAndPattern)obj).getDate(), this.getDatePattern());
    }

هناك طريقتين في السوبر فئة java.لانغ.الكائن.نحن بحاجة إلى تجاوز بهم إلى كائن مخصص.

public boolean equals(Object obj)
public int hashCode()

المساواة في الأشياء يجب أن تنتج نفس رمز التجزئة طالما هم على قدم المساواة ، ومع ذلك غير متكافئة الكائنات لا تحتاج تنتج متميزة تجزئة رموز.

public class Test
{
    private int num;
    private String data;
    public boolean equals(Object obj)
    {
        if(this == obj)
            return true;
        if((obj == null) || (obj.getClass() != this.getClass()))
            return false;
        // object must be Test at this point
        Test test = (Test)obj;
        return num == test.num &&
        (data == test.data || (data != null && data.equals(test.data)));
    }

    public int hashCode()
    {
        int hash = 7;
        hash = 31 * hash + num;
        hash = 31 * hash + (null == data ? 0 : data.hashCode());
        return hash;
    }

    // other methods
}

إذا كنت تريد الحصول على المزيد ، يرجى مراجعة هذا الرابط http://www.javaranch.com/journal/2002/10/equalhash.html

هذا هو مثال آخر ، http://java67.blogspot.com/2013/04/example-of-overriding-equals-hashcode-compareTo-java-method.html

وقد متعة!@.@

هناك عدة طرق للقيام التحقق من الدرجة المساواة قبل التحقق من الأعضاء المساواة ، وأعتقد أن كلاهما مفيد في الظروف المناسبة.

  1. استخدام instanceof المشغل.
  2. استخدام this.getClass().equals(that.getClass()).

أنا استخدم رقم 1 في final يساوي التنفيذ ، أو عند تنفيذ واجهة التي تنص على خوارزمية يساوي (مثل java.util مجموعة واجهات—الطريقة الصحيحة للتحقق مع (obj instanceof Set) أو أيا كان واجهة انت تنفيذ).عموما خيارا سيئا عندما يساوي يمكن تجاوزها لأن ذلك يكسر التناظر الملكية.

الخيار #2 يسمح الطبقة لتكون بأمان الموسعة دون تجاوز يساوي أو كسر التماثل.

إذا صفك أيضا Comparable, ، equals و compareTo الأساليب يجب أن تكون متسقة للغاية.هنا هو قالب أسلوب يساوي في Comparable الدرجة:

final class MyClass implements Comparable<MyClass>
{

  …

  @Override
  public boolean equals(Object obj)
  {
    /* If compareTo and equals aren't final, we should check with getClass instead. */
    if (!(obj instanceof MyClass)) 
      return false;
    return compareTo((MyClass) obj) == 0;
  }

}

بالنسبة يساوي, انظر إلى أسرار يساوي قبل انجيليكا لانغر.أنا أحب ذلك كثيرا جدا.هي أيضا كبيرة أسئلة وأجوبة حول الأدوية في جافا.نظر لها مقالات أخرى هنا (انتقل لأسفل إلى "جافا الأساسية") ، حيث تذهب أيضا مع الجزء-2 و "مختلطة نوع المقارنة".المتعة في قراءتها!

يساوي() الطريقة المستخدمة لتحديد المساواة بين كائنين.

كما int قيمة 10 هو دائما يساوي 10.ولكن هذا يساوي() الأسلوب هو حول المساواة بين كائنين.عندما نقول كائن سيكون لها خصائص.أن تقرر حول المساواة بين هذه الخصائص تعتبر.فإنه ليس من الضروري أن كل خصائص يجب أن تؤخذ بعين الاعتبار لتحديد المساواة فيما يتعلق الدرجة تعريف السياق يمكن أن تقرر.ثم يساوي() طريقة يمكن تجاوزها.

يجب علينا دائما تجاوز hashCode (طريقة) كلما تجاوز يساوي (طريقة).إذا لم يكن كذلك ، ما الذي سيحدث ؟ إذا كان لنا أن استخدام hashtables في التطبيق لدينا ، فإنه لن تتصرف كما هو متوقع.كما hashCode يستخدم في تحديد المساواة بين القيم المخزنة ، فإنه لن يعود الحق المقابلة قيمة المفتاح.

الافتراضي تنفيذ معين هو hashCode (طريقة) في فئة كائن يستخدم عنوان داخلي كائن وتحولها إلى عدد صحيح إرجاع ذلك.

public class Tiger {
  private String color;
  private String stripePattern;
  private int height;

  @Override
  public boolean equals(Object object) {
    boolean result = false;
    if (object == null || object.getClass() != getClass()) {
      result = false;
    } else {
      Tiger tiger = (Tiger) object;
      if (this.color == tiger.getColor()
          && this.stripePattern == tiger.getStripePattern()) {
        result = true;
      }
    }
    return result;
  }

  // just omitted null checks
  @Override
  public int hashCode() {
    int hash = 3;
    hash = 7 * hash + this.color.hashCode();
    hash = 7 * hash + this.stripePattern.hashCode();
    return hash;
  }

  public static void main(String args[]) {
    Tiger bengalTiger1 = new Tiger("Yellow", "Dense", 3);
    Tiger bengalTiger2 = new Tiger("Yellow", "Dense", 2);
    Tiger siberianTiger = new Tiger("White", "Sparse", 4);
    System.out.println("bengalTiger1 and bengalTiger2: "
        + bengalTiger1.equals(bengalTiger2));
    System.out.println("bengalTiger1 and siberianTiger: "
        + bengalTiger1.equals(siberianTiger));

    System.out.println("bengalTiger1 hashCode: " + bengalTiger1.hashCode());
    System.out.println("bengalTiger2 hashCode: " + bengalTiger2.hashCode());
    System.out.println("siberianTiger hashCode: "
        + siberianTiger.hashCode());
  }

  public String getColor() {
    return color;
  }

  public String getStripePattern() {
    return stripePattern;
  }

  public Tiger(String color, String stripePattern, int height) {
    this.color = color;
    this.stripePattern = stripePattern;
    this.height = height;

  }
}

رمز المثال الإخراج:

bengalTiger1 and bengalTiger2: true 
bengalTiger1 and siberianTiger: false 
bengalTiger1 hashCode: 1398212510 
bengalTiger2 hashCode: 1398212510 
siberianTiger hashCode: –1227465966

منطقيا لدينا:

a.getClass().equals(b.getClass()) && a.equals(b)a.hashCode() == b.hashCode()

ولكن لا والعكس بالعكس!

واحد مسكتك وجدت فيها اثنين من الكائنات تحتوي على إشارات إلى بعضها البعض (مثال واحد يجري أحد الوالدين/الطفل العلاقة مع الراحة الأسلوب على الوالدين للحصول على جميع الأطفال).
هذه الأنواع من الأمور شائعة إلى حد ما عند القيام السبات تعيينات على سبيل المثال.

إذا كنت تشمل كلا طرفي العلاقة في hashCode أو يساوي الاختبارات أنه من الممكن أن ندخل في حلقة متكررة الذي ينتهي في StackOverflowException.
أبسط حل هو أن لا تشمل getChildren جمع في الأساليب.

مرخصة بموجب: CC-BY-SA مع الإسناد
لا تنتمي إلى StackOverflow
scroll top