>  기사  >  Java  >  왜 equals() 메서드를 재정의해야 합니까?

왜 equals() 메서드를 재정의해야 합니까?

零下一度
零下一度원래의
2017-06-29 09:58:352826검색

equals() 메서드를 다시 작성해야 하는 이유는 무엇인가요?

클래스의 멤버 변수를 기준으로 두 클래스의 인스턴스가 동일한지 여부를 판단하는 것처럼 두 객체가 논리적으로 동일한지 여부를 판단하고, Object의 equals 메서드는 두 참조 변수가 동일한 객체인지 여부만 판단할 수 있습니다. 이런 방식으로 우리는 종종 equals() 메서드를 재정의해야 합니다.

중복된 개체 없이 컬렉션에 요소를 추가할 때 개체가 컬렉션에 저장되는 경우가 많습니다. 먼저 컬렉션에 알려진 개체가 있는지 확인해야 하므로 equals 메서드를 재정의해야 합니다.

equals() 메소드를 어떻게 재정의하나요?

equals 메서드 재정의 요구 사항:

1. 재귀성: null이 아닌 참조 x의 경우 x.equals(x)는 true를 반환해야 합니다.

2. 대칭: x와 y에 대한 참조의 경우 x.equals(y)가 true를 반환하면 y.equals(x)도 true를 반환해야 합니다.

3. 전이성: x, y 및 z 참조에 대해 x.equals(y)가 true를 반환하고 y.equals(z)가 true를 반환하면 x.equals(z)도 true를 반환해야 합니다.

4. 일관성: x와 y가 참조하는 객체가 변경되지 않은 경우 x.equals(y)를 반복적으로 호출하면 동일한 결과가 반환되어야 합니다.

5. Null 허용 여부: Null이 아닌 참조 x의 경우 x.equals(null)은 false를 반환해야 합니다.

1. 재귀성 원리

JavaBeans에서는 실제 비즈니스 조건에 따라 두 개체가 동일한지 확인하기 위해 equals 메서드를 재정의하는 경우가 많습니다. 예를 들어 사람 클래스를 작성하고 이름을 기준으로 두 사람을 결정합니다. 클래스 인스턴스 객체가 동일한지 여부입니다. 코드는 다음과 같습니다.

 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 }

list에는 생성된 사람 개체가 포함되어 있으며 결과는 true여야 하지만 실제 결과는 문자열 공백 문제가 여기에서 고려되고 앞뒤 공백이 제거된다는 것입니다.

장삼(Zhang San) 포함 여부: true

장삼(Zhang San) 포함 여부: false

두 번째는 왜 거짓인가요?

그 이유는 목록에 요소가 포함되어 있는지 확인할 때 객체의 equals 메소드를 호출하여 판단하기 때문입니다. 즉, Contains(p2)가 전달되면 p2.equals(p1) 및 p2.equals입니다. (p2)는 하나가 true를 반환하는 한 순차적으로 실행되며 결과는 true입니다. 하지만 여기서 p2.equals(p2)는 false를 반환합니까? 문자 앞뒤에 공백을 자르기 때문에 p2.equals(p2)의 비교는 실제로 "Zhang San".equals("Zhang San")입니다. 하나는 공백이 있고 다른 하나는 공백이 없습니다. 이는 오류입니다.

이것은 같음의 재귀 원칙을 위반합니다. null이 아닌 참조 x에 대해 x.equals(x)는 true를 반환해야 합니다.

트림 방법을 제거하면 이 문제를 해결할 수 있습니다.

2. 대칭의 원리

위의 예는 그다지 좋지 않습니다. null 값을 전달하면 어떻게 될까요? 다음 문을 추가하세요. Person p2=new Person(null);

Result:

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

p2.equals(p1)을 실행할 때 p2의 이름이 null 값이므로 name.equalsIgnoreCase() 메서드는 다음과 같습니다. 호출됨 널 포인터 예외가 보고됩니다.

이것은 equals 메서드를 재정의할 때 대칭 원칙을 따르지 않습니다. x, y가 적용되는 모든 상황에서 x.equals(y)가 true를 반환하도록 하려면 y.equals(x)도 true를 반환해야 합니다. .

equals 메서드에 값이 null인지 여부에 대한 판단을 추가해야 합니다:

 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. 전이성 원칙

이제 person 클래스에서 상속되는 Employee 클래스가 있습니다:

 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 }

Only 이름과 아이디는 모두 동일해야 동일한 직원이므로 이름과 성이 동일하지 않도록 하세요. 두 명의 직원과 사회복지사 한 명은 비록 이름과 성은 같지만 결코 동일인이 아니라는 것이 원칙적으로 정의되어 있습니다. 실행 결과는 세 가지 모두 거짓이어야 합니다. 하지만:

true

true

false

p1은 확실히 e1 및 e2와 동일합니다. 동일한 클래스의 인스턴스도 동일하지 않습니까?

p1.equals(e1)은 판단을 위해 상위 클래스의 equals 메소드를 호출하기 때문에 e1이 person의 인스턴스인지 확인하기 위해 instanceof 키워드를 사용하기 때문에 Employee와 Person은 상속 관계이므로 결과는 true입니다. 하지만 e1과 e2가 p1과 같지 않다면 이는 성립하지 않습니다. 이 역시 대칭의 원리를 위반하는 전형적인 경우입니다.

e1은 e2와 같지 않나요?

e1.equals(e2)는 Employee의 equals 메소드를 호출하여 이름이 동일할 뿐만 아니라 둘의 작업 번호도 동일하며 그렇지 않은 경우에는 정확합니다. 동일한. 그러나 p1은 e1과 동일하고 e2와도 동일하지만 e1은 e2와 동일하지 않습니다. 여기에는 방정식이 추이적이지 않은 이유는 평등의 전이성 원리를 위반하기 때문입니다. , y, z; x.equals(y)가 true를 반환하고 y.equals(z)가 true를 반환하면 x.equals(z)도 true를 반환해야 합니다.

위 상황은 상위 클래스가 특정 클래스의 인스턴스인지 하위 클래스인지 여부에 관계없이 인스턴스 오브 키워드를 사용하여 클래스의 인스턴스 개체인지 여부를 확인하기 때문에 발생합니다. .”

해결 방법은 매우 간단합니다. getClass를 사용하여 person 클래스의 equals 메소드를 다음과 같이 수정합니다.

 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

위 내용은 왜 equals() 메서드를 재정의해야 합니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.