Maison  >  Article  >  Java  >  Pourquoi la méthode equals() devrait-elle être remplacée ?

Pourquoi la méthode equals() devrait-elle être remplacée ?

零下一度
零下一度original
2017-06-29 09:58:352826parcourir

Pourquoi la méthode equals() devrait-elle être réécrite ?

Déterminer si deux objets sont logiquement égaux, par exemple juger si les instances de deux classes sont égales en fonction des variables membres de la classe, tandis que la méthode égale dans l'objet hérité ne peut en juger que deux Si la variable de référence est le même objet . De cette façon, nous devons souvent remplacer la méthode equals().

Lorsque nous ajoutons des éléments à une collection sans objets en double, la collection stocke souvent des objets. Nous devons d'abord déterminer s'il y a des objets connus dans la collection, nous devons donc remplacer la méthode equals.

Comment remplacer la méthode equals() ?

Conditions requises pour remplacer la méthode equals :

1 Réflexivité : x.equals(x) doit renvoyer true pour toute référence x non nulle.

2. Symétrie : pour toute référence x et y, si x.equals(y) renvoie vrai, alors y.equals(x) devrait également renvoyer vrai.

3. Transitivité : pour toute référence x, y et z, si x.equals(y) renvoie true et y.equals(z) renvoie true, alors x.equals(z) devrait également renvoyer true .

4. Cohérence : si les objets référencés par x et y n'ont pas changé, alors appeler x.equals(y) à plusieurs reprises devrait renvoyer le même résultat.

5. Non-nullabilité : pour toute référence x non nulle, x.equals(null) doit renvoyer false.

1. Principe de réflexivité

Dans JavaBean, la méthode equals est souvent remplacée pour déterminer si deux objets sont égaux en fonction de conditions commerciales réelles, comme la nôtre. classe de personne pour déterminer si deux objets d'instance de classe de personne sont égaux en fonction de leurs noms. Le code est le suivant :

 1 public class Person { 2     private String name; 3  4     public Person(String name) { 5         this.name = name; 6     } 7  8     public String getName() { 9         return name;10     }11 12     public void setName(String name) {13         this.name = name;14     }15 16     @Override17     public boolean equals(Object obj) {18         if (obj instanceof Person) {19             Person person = (Person) obj;20             return name.equalsIgnoreCase(person.getName().trim());21         }22         return false;23     }24 25     public static void main(String[] args) {26         Person p1 = new Person("张三");27         Person p2 = new Person("张三    ");28         List<Person> list = new ArrayList<Person>();29         list.add(p1);30         list.add(p2);31         System.out.println("是否包含张三:" + list.contains(p1));32         System.out.println("是否包含张三:" + list.contains(p2));33     }34 }

La liste contient cet objet personne généré. Le résultat devrait être vrai, mais le résultat réel : le problème des espaces de chaîne est considéré ici, et. les espaces de début et de fin sont supprimés.

Qu'il contienne Zhang San : vrai

Qu'il contienne Zhang San : faux

Pourquoi le deuxième est-il faux ?

La raison est que lors de la vérification si la liste contient des éléments, elle est jugée en appelant la méthode equals de l'objet, c'est-à-dire si contain(p2) est transmis, p2.equals(p1) , p2.equals(p2) sera exécuté dans l'ordre ), tant que l'on renvoie vrai, le résultat est vrai. Mais ici, p2.equals(p2) renvoie false ? Puisque nous coupons les espaces avant et après les caractères, la comparaison de p2.equals(p2) est en fait : "Zhang San".equals ("Zhang San") L'un a des espaces et l'autre n'en a pas, ce qui est une erreur.

Cela viole le principe réflexif des égalités : pour toute référence x non nulle, x.equals(x) doit renvoyer vrai.

Ceci peut être résolu en supprimant la méthode de découpage.

2. Principe de symétrie

L'exemple ci-dessus n'est pas très bon. Que se passera-t-il si on passe une valeur nulle ? Ajoutez une déclaration : Person p2=new Person(null);

Résultat :

是否包含张三:trueException in thread "main" java.lang.NullPointerException//空指针异常

La raison est que lorsque p2.equals(p1) est exécuté, car le nom est une valeur nulle, donc une exception de pointeur nul sera signalée lorsque la méthode name.equalsIgnoreCase() est appelée.

Cela ne suit pas le principe de symétrie lors du remplacement de la méthode égale : pour toute situation où x, y est appliqué, si vous voulez que x.equals(y) renvoie vrai, alors y.equals(x) devrait également renvoyer vrai.

Vous devez ajouter un jugement indiquant si la valeur est nulle dans la méthode égale :

 1 @Override 2     public boolean equals(Object obj) { 3         if (obj instanceof Person) { 4             Person person= (Person) obj; 5             if (person.getName() == null || name == null) { 6                 return false; 7             }else{ 8                 return name.equalsIgnoreCase(person.getName()); 9             }10         }11         return false;12     }

3. Principe de transitivité

Nous avons maintenant une classe Employee qui hérite de la classe person :

 1 public class Employee extends Person{ 2     private int id; 3    4    5     public int getId() { 6         return id; 7     } 8     public void setId(int id) { 9         this.id = id;10     }11     public Employee(String name,int id) {12         super(name);13         this.id = id;14         // TODO Auto-generated constructor stub15     }16     @Override17     public boolean equals(Object obj) {18         if(obj instanceof Employee){19             Employee e = (Employee)obj;20             return super.equals(obj) && e.getId() == id;21         }22         return super.equals(obj);23     }24   25     public static void main(String[] args){26         Employee e1=new Employee("张三",12);27         Employee e2=new Employee("张三",123);28         Person p1 = new Person("张三");29   30         System.out.println(p1.equals(e1));31         System.out.println(p1.equals(e2));32         System.out.println(e1.equals(e2));33     }34 }

C'est le même employé seulement si le nom et l'identifiant sont les mêmes, pour éviter d'avoir le même nom et prénom. Il est défini en principe que deux salariés et un travailleur social, bien qu'ils portent le même nom et prénom, ne sont certainement pas la même personne. Les résultats en cours devraient être tous les trois faux. Mais :

true

true

false

p1 est définitivement égal à e1 et e2. Les instances de la même classe ne sont-elles pas également égales ?

Parce que p1.equals(e1) appelle la méthode égale de la classe parent pour le jugement. Il utilise le mot-clé instanceof pour vérifier si e1 est une instance de personne. Puisque l'employé et la personne sont des relations d'héritage, le résultat est. vrai. Mais cela n’est pas vrai si on le met de côté. e1 et e2 ne sont pas égaux à p1. C’est aussi un cas typique de violation du principe de symétrie.

e1 n'est pas égal à e2 ?

e1.equals(e2) appelle la méthode égale de Employee. Elle détermine non seulement que les noms sont les mêmes, mais que les numéros de travail sont également les mêmes. Les numéros de travail des deux sont différents et c'est correct. quand ils ne sont pas égaux. Mais p1 est égal à e1, et est également égal à e2, mais e1 n'est pas égal à e2. Il y a ici une contradiction. La raison pour laquelle l'équation n'est pas transitive est qu'elle viole le principe de transitivité des égaux : par exemple les objets x. , y, z ; si x.equals(y) renvoie vrai, y.equals(z) renvoie vrai, alors x.equals(z) devrait également renvoyer vrai.

La situation ci-dessus se produit parce que la classe parent utilise le mot-clé instanceof (qu'il s'agisse d'une instance de cette classe spécifique ou de sa sous-classe) pour déterminer s'il s'agit d'un objet instance d'une classe. C'est très simple. les sous-classes « profitent des failles ».

La solution est très simple. Utilisez getClass pour déterminer le type. La méthode equals de la classe person est modifiée comme suit :

 1 @Override 2     public boolean equals(Object obj) { 3         if (obj != null && obj.getClass() == this.getClass()) { 4             Person person= (Person) obj; 5             if (person.getName() == null || name == null) { 6                 return false; 7             }else{ 8                 return name.equalsIgnoreCase(person.getName()); 9             }10         }11         return false;12 }

4、必须覆写hashCode方法这样结果就是三个false。

覆写equals方法就必须覆写hashCode方法,这是Javaer都知道的。

原因就是HashMap的底层处理机制是以数组的方式保存map条目的,这其中的关键是这个数组下标的处理机制:

依据传入元素的hashCode方法的返回值决定其数组的下标,如果该数组位置上已经有了map条目,且与传入的键值相等则不处理,若不相等则覆盖;如果数组位置没有条目,则插入,并加入到map条目的链表中。同理检查键是否存在也是根据哈希吗确定文职,然后遍历查找键值的。

那么对象的hashCode方法返回的是什么呢?

他是一个对象的哈希码,是有Object类的本地方法生成的,确保每个对象有一个哈希码。

1、重写equals方法实例   部分代码参考

重写equals方法的目的是判断两个对象的内容(内容可以有很多,比如同时比较姓名和年龄,同时相同的才是用一个对象)是否相同。

如果不重写equals,那么比较的将是对象的引用是否指向同一块内存地址,重写之后目的是为了比较两个对象的value值是否相等。特别指出利用equals比较八大包装对象,(如int,float等)和String类(因为该类已重写了equals和hashcode方法)对象时,默认比较的是值,在比较其它自定义对象时都是比较的引用地址。

package com.lk.C;class User {private String name;private int age;public int getAge() {return age;
    }public void setAge(int age) {this.age = age;
    }public void setName(String name) {  this.name = name;  
    }public String getName() {  return name;  
    }public boolean equals(Object obj) {  if(this == obj) {  return true;  
        }  if(null == obj) {  return false;  
        }  if(this.getClass() != obj.getClass()) {  return false;  
        }  

        User user = (User) obj;  if(this.name.equals(user.name)&&this.age == user.age) {  return true;  
        }  return false;  
    }  
    
}  

public class Test6 {  public static void main(String[] args) {  
        User userA = new User();  
        userA.setName("王明");
        userA.setAge(10);

        User userB = new User();  
        userB.setName("王明");
        userB.setAge(10);

        User userC = new User();  
        userC.setName("王亮");
        userC.setAge(10);

        System.out.println("userA equals userB:" + userA.equals(userB));  
        System.out.println("userA equals userC:" + userA.equals(userC));
    }  
}
userA equals userB:trueuserA equals userC:false

在Java中,问什么说重写了equals方法都要进而重写Hashcode方法呢?

原因如下:当equals此方法被重写时,通常有必要重写 hashCode 方法,以维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码。如下:

(1)当obj1.equals(obj2)为true时,obj1.hashCode() == obj2.hashCode()必须为true

(2)当obj1.hashCode() == obj2.hashCode()为false时,obj1.equals(obj2)必须为false

hashcode是用于散列数据的快速存取,如利用HashSet/HashMap/Hashtable类来存储数据时,都是根据存储对象的hashcode值来进行判断是否相同的。

这样如果我们对一个对象重写了euqals,意思是只要对象的成员变量值都相等那么euqals就等于true,但不重写hashcode,那么我们再new一个新的对象,当原对象.equals(新对象)等于true时,两者的hashcode却是不一样的,由此将产生了理解的不一致。

2、看看下面的三段程序

package com.lk.C;public class Test7 {public static void main(String[] args) {int a = 10;int b = 10;
        System.out.print("基本类型a==b:");
        System.out.println(a == b);
        System.out.println("-----");
        
        String s1 = "abc";
        String s2 = "abc";
        System.out.print("String类型是s1==s2:");
        System.out.println(s1 == s2);
        System.out.println("-----");
        
        String s3 = new String("abc");
        String s4 = new String("abc");//可以看出==比较的是栈的地址是否相同System.out.print("String类型用new String()是s1==s2:");
        System.out.println(s3 == s4);
        System.out.println(s1 == s3);
        System.out.println("-----");
        
        Integer i1 = 1;
        Integer i2 = 1;
        System.out.print("包装类型是i1==i2:");
        System.out.println(i1 == i2);
        System.out.println("-----");
        
        Integer i3 = 128;
        Integer i4 = 128;//此时输出false是因为Integer在-128-127之间会缓存,超出这个范围就不会缓存了System.out.print("包装类型是i3==i4:");
        System.out.println(i3 == i4);
        System.out.println("-----");
        
        Integer i5 = new Integer("1");
        Integer i6 = new Integer("1");
        System.out.print("包装类型用new Integer()是i5==i6:");
        System.out.println(i5 == i6);//用new Integer()多少都不会缓存System.out.println("-----");
        
        A a1 = new A(1);
        A a2 = new A(1);
        A a3 = a2;
        System.out.print("普通引用类型a1 == a2:");
        System.out.println(a1 == a2);
        System.out.println(a2 == a3);//对象赋给新对象连地址都是相同的System.out.println("-----");
    }
}class A{int i;public A(int i){this.i = i;
    }
}
基本类型a==b:true-----String类型是s1==s2:true-----String类型用new String()是s1==s2:falsefalse-----包装类型是i1==i2:true-----包装类型是i3==i4:false-----包装类型用new Integer()是i5==i6:false-----普通引用类型a1 == a2:falsetrue-----
package com.lk.C;public class Test8 {public static void main(String[] args) {// TODO Auto-generated method stubSystem.out.println("基本类型没有equals方法");
        System.out.println("-----");
        
        String s1 = "abc";
        String s2 = "abc";
        System.out.print("String类型的equals方法:");
        System.out.println(s1.equals(s2));
        System.out.println("-----");
        
        String s3 = new String("abc");
        String s4 = new String("abc");//可以看出比较equals方法比较的是堆里的值是否相同System.out.print("String类型的new String()的equals方法:");
        System.out.println(s3.equals(s4));
        System.out.println("-----");
        
        System.out.print("String用==赋值和用new String()赋值的比较:");
        System.out.println(s1.equals(s3));
        System.out.println("-----");
        
        Integer i1 = 1;
        Integer i2 = 1;
        System.out.print("包装类的equals方法:");
        System.out.println(i1.equals(i2));
        System.out.println("-----");
        
        Integer i3 = new Integer(1);
        Integer i4 = new Integer(1);
        System.out.print("包装类的new Integer()用equals方法:");
        System.out.println(i3.equals(i4));
        System.out.println("-----");
        
        System.out.print("Integer用==赋值和用new Integer()赋值的比较:");
        System.out.println(i1.equals(i3));
        System.out.println("-----");
    }

}
基本类型没有equals方法-----String类型的equals方法:true-----String类型的new String()的equals方法:true-----String用==赋值和用new String()赋值的比较:true-----包装类的equals方法:true-----包装类的new Integer()用equals方法:true-----Integer用==赋值和用new Integer()赋值的比较:true-----
package com.lk.C;public class Test9 {public static void main(String[] args) {// TODO Auto-generated method stubStudent s1 = new Student("阿坤",21);
        Student s2 = new Student("阿坤",21);
        Student s3 = new Student();
        Student s4 = new Student();
        Student s5 = s1;
        System.out.print("普通类对象的==非默认构造:");
        System.out.println(s1 == s2);
        System.out.println(s1 == s5);
        System.out.println("-----");
        
        System.out.print("普通类对象的equals非默认构造:");
        System.out.println(s1.equals(s2));
        System.out.println(s1.equals(s5));
        System.out.println("-----");
        
        System.out.print("普通类对象的==默认构造:");
        System.out.println(s3 == s4);
        System.out.println("-----");
        
        System.out.print("普通类对象的equals默认构造:");
        System.out.println(s3.equals(s4));
        System.out.println("-----");
        
        System.out.print("对普通对象的属性进行比较equals:");
        System.out.println(s1.name.equals(s2.name));
        System.out.print("对普通对象的属性进行比较==:");
        System.out.println(s1.name == s2.name);
    }

}class Student{public String name;public int age;public Student(){
        
    }public Student(String name,int age){this.name = name;this.age = age;
    }public void test(){
        System.out.println(this.name);
        System.out.println(this.age);
    }
}
普通类对象的==非默认构造:falsetrue-----普通类对象的equals非默认构造:falsetrue-----普通类对象的==默认构造:false-----普通类对象的equals默认构造:false-----对普通对象的属性进行比较equals:true对普通对象的属性进行比较==:true

从以上的三个程序可以看出:

1)对于==:在简单类型中(int等),这能使用该方法进行比较,这种类型没有equals方法,int的值是存在栈中的,==比较的是栈的内容是否相同。在String类型中,比较特殊,用String=“”;这种进行赋值时,两个相同的值用==比较也是相同的。但是用new String(),赋值就不相同。说明String=“”时,java会检查在堆中是否由相同的值,如果有,把新对象的地址也同老对象的地址赋为相同,因此==比较会相同。但是new String()开辟的就是两个栈,因此用==比较不会相同。对于包装类,如Integer=“”;时,在-128-127会有缓存,请看上面程序。其他的情况与String类似。

2)对于equals:当时String类型或者是包装类,如Integer时,比较的就是堆中的值,Integer也无缓存之说。对于普通类,equals比较的内存的首地址,这时候和==是一样的,即比较两边指向的是不是同一个对象。详细请见程序三。

很好,很详细的文章,感谢网友的分享,记录下来只为学习。

以上程序都是亲自测试过。希望能对大家有帮助。

以下是一些在百度中找到的说法:

java中,
(1)对于字符串变量来说,equal比较的两边对象的内容,所以内容相同返回的是true。
至于你没问到的“==”,比较的是内存中的首地址,所以如果不是同一个对象,“==”不会返回true 而是false。
举个简单的例子,
String s1="abc", s2="abc";
String s3 =new String("abc");
String s4=new String("abc");
s1==s2 //true,s1.equals(s2) //true,s3.equals(s3) //true,equal比较的是内容s3==s4//false,==比较的是首地址,所以是false(2)对于非字符串变量,equals比较的内存的首地址,这时候和==是一样的,即比较两边指向的是不是同一个对象,
即
Sample sa1 = new Sample();
Sample sa2 = new Sample();
sa1.equals(sa2) //false,因为不是同一对象 注意,如果加上
sa1=sa2;
那么
sa1.equals(sa2) //true

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn