Java의 기본 클래스인 Object는 몇 가지 메소드를 제공하는데, 그 중 equals() 메소드는 두 객체가 동일한지 확인하는 데 사용되며, hashCode() 메소드는 객체의 해시 코드를 계산하는 데 사용됩니다. equals()나 hashCode()는 최종 메서드가 아니므로 덮어쓸 수 없습니다.
이 글에서는 두 메서드를 사용하고 다시 작성할 때 주의가 필요한 몇 가지 문제를 소개합니다.
1. equals() 메서드
Object 클래스의 equals() 메서드는 다음과 같이 구현됩니다. 다음: #🎜 🎜#
public boolean equals(Object obj) { return (this == obj); }
이 구현에서 Object 클래스의 구현이 가장 높은 수준의 차별화를 갖춘 알고리즘을 채택한다는 것을 알 수 있습니다. 즉, 두 객체가 서로 일치하지 않는 한 동일한 객체인 경우 equals()는 false를 반환해야 합니다.
클래스를 정의할 때 equals() 메서드를 재정의할 수 있지만 JDK에서는 equals() 메서드를 구현할 때 따라야 하는 몇 가지 규칙을 설명합니다. #🎜 🎜 #(1) 재귀성: x.equals(x)는 true를 반환해야 합니다.
(2) 대칭성: x.equals(y)와 y.equals(x)의 반환 값은 동일해야 합니다.
(3) 이행성: x.equals(y)가 true이고 y.equals(z)도 true인 경우 x.equals(z)도 true여야 합니다.
(4) 일관성: equals()의 x 및 y 객체가 사용하는 정보가 변경되지 않으면 x.equals(y) 값은 항상 변경되지 않습니다.
(5) Null이 아님: x는 null이 아니고 y는 null인 경우 x.equals(y)는 false여야 합니다.
2. hashCode() 메서드
1. ()Object 클래스의 hashCode() 메소드는 다음과 같이 선언됩니다.
public native int hashCode();
hashCode()가 다음과 같이 선언되어 있음을 알 수 있습니다. 기본 메서드이고 반환 값 유형은 정수입니다. 실제로 이 기본 메서드는 메모리에 있는 개체의 주소를 해시 코드로 반환하므로 서로 다른 개체의 반환 값이 서로 다르다는 것을 확인할 수 있습니다.
equals() 메서드와 유사하게 hashCode() 메서드를 재정의할 수 있습니다. JDK에서는 hashCode() 메소드의 기능과 구현 시 주의사항을 설명하고 있습니다.
(1) hashCode()는 java.util.HashMap과 같은 해시 테이블에서 작동합니다.
(2) equals()의 객체가 사용하는 정보가 변경되지 않은 경우 hashCode() 값은 항상 변경되지 않습니다.
(3) equals() 메서드를 사용하여 두 객체가 동일하다고 판단되면 hashCode() 메서드도 동일해야 합니다.
(4) equals() 메서드를 사용하여 두 객체가 동일하지 않다고 판단되는 경우 hashCode()는 필요하지 않으며 동일하지 않아야 합니다. 그러나 개발자는 동일하지 않은 객체가 서로 다른 hashCode를 생성하면 성능이 향상될 수 있다는 점을 인식해야 합니다. 해시 테이블의 성능.
2. hashCode()의 역할일반적으로 해싱에는 hashCode()가 사용됩니다. HashSet, HashMap 등과 같은 테이블의 함수
해시 테이블(HashSet, HashMap 등)에 객체를 추가할 때 먼저 hashCode() 메서드를 호출하여 해시 코드를 통해 객체의 해시 코드를 계산합니다. 해시 테이블에서 개체를 직접 찾을 수 있습니다. 해시 테이블의 위치(일반적으로 해시 테이블 크기에 대한 해시 코드 모듈로). 이 위치에 객체가 없으면 이 위치에 객체를 직접 삽입할 수 있습니다. 이 위치에 객체가 있으면(연결된 목록을 통해 구현된 객체가 두 개 이상 있을 수 있음), equals() 메서드를 호출하여 여부를 비교합니다. 이러한 객체는 객체와 동일합니다. 동일하면 객체를 저장할 필요가 없습니다. 동일하지 않으면 객체를 연결 목록에 추가합니다.
이것은 또한
equals()가 동일한 이유를 설명하며 hashCode()는 동일해야 합니다.두 객체가 동일하면 해시 테이블(예: HashSet, HashMap 등)에 한 번만 나타나야 합니다. hashCode()가 동일하지 않으면 해시 테이블에 해시됩니다. 서로 다른 위치의 해시 테이블에 두 번 이상 나타납니다. 사실 JVM에서 로드된 객체는 메모리에 객체 헤더, 인스턴스 데이터, 채우기라는 세 부분을 포함합니다. 그 중 객체 헤더에는 객체의 타입에 대한 포인터와 MarkWord가 포함되어 있으며
MarkWord에는 객체의 GC 생성 연령 정보와 잠금 상태 정보뿐만 아니라 객체의 해시코드도 포함됩니다. 객체 인스턴스 데이터는 객체에 의해 실제로 저장되는 유효한 정보입니다. HotSpot에서는 객체의 시작 주소가 8바이트의 정수 배수여야 하기 때문에 패딩 부분은 자리 표시자 역할만 합니다.
private final char value[]; private int hash; // Default to 0 public boolean equals(Object anObject) { if (this == anObject) { return true; } if (anObject instanceof String) { String anotherString = (String)anObject; int n = value.length; if (n == anotherString.value.length) { char v1[] = value; char v2[] = anotherString.value; int i = 0; while (n-- != 0) { if (v1[i] != v2[i]) return false; i++; } return true; } } return false; } public int hashCode() { int h = hash; if (h == 0 && value.length > 0) { char val[] = value; for (int i = 0; i < value.length; i++) { h = 31 * h + val[i]; } hash = h; } return h; }
코드를 통해 다음 사항을 확인할 수 있습니다.
1. 문자열 데이터는 최종적입니다. 즉, 문자열 개체가 생성되면, 예를 들어 String s = "hello"; s = "world";라는 문이 실행되면 문자열 개체의 값이 "world"로 변경되지 않습니다. , 그러나 새 String 개체가 생성되고 s 참조는 새 개체를 가리킵니다.
2. String 클래스는 성능 향상을 위해 hashCode()의 결과를 해시 값으로 캐시합니다.
3. String 객체가 동일하다는 조건은 둘 다 String 객체이고 길이가 동일하며 정확히 동일한 문자열 값을 가져야 한다는 것입니다. .
4. 문자열의 hashCode() 계산 공식은 s[0]*31^(n-1) + s[1]*31^(n-2) + ... + s [n-1]
hashCode()의 계산 과정에서 숫자 31이 사용되는 이유는 주요 이유는 다음과 같습니다.
1、使用质数计算哈希码,由于质数的特性,它与其他数字相乘之后,计算结果唯一的概率更大,哈希冲突的概率更小。
2、使用的质数越大,哈希冲突的概率越小,但是计算的速度也越慢;31是哈希冲突和性能的折中,实际上是实验观测的结果。
3、JVM会自动对31进行优化:31 * i == (i << 5) - i
本节先介绍重写hashCode()方法应该遵守的原则,再介绍通用的hashCode()重写方法。
1、重写hashcode()的原则
通过前面的描述我们知道,重写hashCode需要遵守以下原则:
(1)如果重写了equals()方法,检查条件“两个对象使用equals()方法判断为相等,则hashCode()方法也应该相等”是否成立,如果不成立,则重写hashCode ()方法。
(2)hashCode()方法不能太过简单,否则哈希冲突过多。
(3)hashCode()方法不能太过复杂,否则计算复杂度过高,影响性能。
2、hashCode()重写方法
《Effective Java》中提出了一种简单通用的hashCode算法
A、初始化一个整形变量,为此变量赋予一个非零的常数值,比如int result = 17;
B、选取equals方法中用于比较的所有域(之所以只选择equals()中使用的域,是为了保证上述原则的第1条),然后针对每个域的属性进行计算:
(1) 如果是boolean值,则计算f ? 1:0
(2) 如果是byte\char\short\int,则计算(int)f
(3) 如果是long值,则计算(int)(f ^ (f >>> 32))
(4) 如果是float值,则计算Float.floatToIntBits(f)
(5) 如果是double值,则计算Double.doubleToLongBits(f),然后返回的结果是long,再用规则(3)去处理long,得到int
(6) 如果是对象应用,如果equals方法中采取递归调用的比较方式,那么hashCode中同样采取递归调用hashCode的方式。否则需要为这个域计算一个范式,比如当这个域的值为null的时候,那么hashCode 值为0
(7) 如果是数组,那么需要为每个元素当做单独的域来处理。java.util.Arrays.hashCode方法包含了8种基本类型数组和引用数组的hashCode计算,算法同上。
C、最后,把每个域的散列码合并到对象的哈希码中。
下面通过一个例子进行说明。在该例中,Person类重写了equals()方法和hashCode()方法。因为equals()方法中只使用了name域和age域,所以hashCode()方法中,也只计算name域和age域。
对于String类型的name域,直接使用了String的hashCode()方法;对于int类型的age域,直接用其值作为该域的hash。
public class Person { private String name; private int age; private boolean gender; public Person() { super(); } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } public boolean isGender() { return gender; } public void setGender(boolean gender) { this.gender = gender; } @Override public boolean equals(Object another) { if (this == another) { return true; } if (another instanceof Person) { Person anotherPerson = (Person) another; if (this.getName().equals(anotherPerson.getName()) && this.getAge() == anotherPerson.getAge()) { return true; } else { return false; } } return false; } @Override public int hashCode() { int hash = 17; hash = hash * 31 + getName().hashCode(); hash = hash * 31 + getAge(); return hash; } }
推荐教程:java教程
위 내용은 equals() 메소드 및 hashCode() 메소드(자세한 소개)의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!