Java의 Equals 메소드 및 해시 코드 방법은 객체에 있으므로 각 객체에는이 두 가지 방법이 있습니다. 때로는 특정 요구를 구현해야 하며이 두 가지 방법을 다시 작성해야 할 수도 있습니다. 오늘, 우리는이 두 가지 방법의 기능을 소개 할 것입니다.
equals () 및 hashcode () 메소드는 특히 동일한 클래스에 객체를 저장하기 위해 컨테이너에 동일한 클래스 객체를 저장할 때 동일한 클래스에서 비교하는 데 사용됩니다.
여기서는 먼저 문제를 이해해야합니다.
equals ()가 같은 두 개의 객체, hashcode ()가 같고 동일하지 않아야합니다. 다시 말해, () 메소드가 동일하지 않은 두 개체의 경우 hashcode ()가 같을 수 있습니다. (내 이해는 생성 될 때 해시 코드 충돌로 인해 발생합니다)
여기서 해시 코드는 사전에서 각 문자의 색인과 같으며, Equals ()는 사전에서 동일한 문자 아래 다른 단어를 비교하는 것과 같습니다. 사전에서와 마찬가지로, 사전에서 "자기"라는 단어 아래에서 "자기"와 "자발적"이라는 두 단어를 검색하면, equals ()가 단어 쿼리의 평등을 결정하는 데 사용되면 동일한 단어입니다. 예를 들어, equals ()에 의해 비교 된 두 단어는 "self"이며, hashcode () 메소드에 의해 얻은 값은 현재 동일해야합니다. equals () 메소드가 "자기"와 "자발적"이라는 단어를 비교하면, 결과는 기다리지 않기를 원하지만이 두 단어는 "self"라는 단어에 속하며 색인을 검색 할 때, 즉 hashcode ()가 동일합니다. equals ()가 "자기"와 "그들"이라는 단어를 비교하면 결과도 다르며 hashcode ()에 의해 얻은 결과도 현재 다릅니다.
반대로 : hashcode ()는 다르고 equals ()를 도입 할 수 있습니다. hashcode ()는 동일하며, equals ()는 같거나 같지 않을 수 있습니다. 객체 클래스에서 hashcode () 메소드는 로컬 메소드이며 객체의 주소 값을 반환합니다. 객체 클래스의 equals () 메소드는 두 객체의 주소 값을 비교합니다. equals ()가 같으면 두 객체의 주소 값도 동일하다는 것을 의미합니다. 물론 hashcode ()는 동일합니다.
동시에 해시 알고리즘은 요소를 찾는 데 고효율을 제공합니다.
객체가 컬렉션에 포함되어 있는지 확인하려면 근사 프로그램 코드를 어떻게 작성합니까?
당신은 일반적으로 각 요소를 하나씩 꺼내서 찾고있는 객체와 비교합니다. Equals 메소드의 결과를 찾으면 요소와 원하는 객체 간의 비교가 검색을 중지하고 긍정적 인 정보를 반환하십시오. 그렇지 않으면 부정적인 정보를 반환합니다. 컬렉션에 10,000 개의 요소와 같은 요소가 많이 있고 원하는 객체가 포함되어 있지 않은 경우 프로그램이 컬렉션에서 10,000 개의 요소를 꺼내어 하나씩 비교하여 결론을 내려야한다는 것을 의미합니다.
누군가는 세트에서 요소를 찾는 효율성을 향상시키기 위해 해시 알고리즘을 발명했습니다. 이런 식으로 세트는 여러 저장 공간으로 나뉩니다. 각 객체는 해시 코드를 계산할 수 있으며 해시 코드를 그룹화 할 수 있습니다 (다른 해시 함수를 사용하여 계산). 각 그룹은 특정 스토리지 영역에 해당합니다. 물체의 해시에 따르면, 객체가 저장되어야하는 영역을 결정할 수 있습니다. 해시 세트는 해시 알고리즘을 사용하여 객체 세트에 액세스합니다. 내부적으로 해시 코드를 그룹화하고 나누기 위해 특정 숫자 n의 나머지를 취하는 방법을 사용합니다 (이 해시 함수는 가장 쉽습니다). 객체 클래스는 각 Java 객체의 해시 코드를 반환하기 위해 hashcode () 메소드를 정의합니다. Hashset 컬렉션에서 객체를 찾을 때 Java 시스템은 먼저 객체의 Hashcode () 메소드를 호출하여 객체의 해시 코드 테이블을 얻습니다. 그런 다음 해싱을 기반으로 해당 스토리지 영역을 찾아서 마지막으로 저장 영역에서 각 요소를 얻고이를 Equals 메소드의 객체와 비교하십시오. 이런 식으로 컬렉션의 모든 요소를 가로지 않고 결론을 얻을 수 있습니다. Hashset 컬렉션은 객체 검색 성능이 우수하지만 해시 세트 컬렉션에 객체를 저장하는 효율성은 상대적으로 낮습니다. 해시 세트 컬렉션에 객체를 추가 할 때 먼저 객체의 해시 코드를 계산 하고이 해시 코드를 기반으로 컬렉션의 객체의 저장 위치를 결정해야합니다. 클래스의 인스턴스 객체를 해시 세트에 정상적으로 저장할 수 있도록이 클래스의 두 인스턴스 객체의 결과가 equals () 메소드에 비해 결과가 같을 때도 동일해야합니다. 즉, OBJ1.equals (OBJ2)의 결과가 사실이라면 다음 표현식의 결과도 마찬가지입니다.
obj1.hashcode () == obj2.hashcode ()
다시 말해 : 객체의 평등 메소드를 다시 작성하면 해시 코드 메소드를 다시 작성해야합니다. 그러나 해시 코드 메소드를 다시 작성하지 않으면 객체 객체의 해시 코드 메소드는 항상 객체의 해시 주소를 반환 하며이 주소는 결코 같지 않습니다. 따라서이 시점에서 동등한 메소드가 다시 작성 되더라도 해시 코드 메소드가 대기하고 싶지 않으면 비교를위한 동등한 메소드를 호출하지 않으므로 의미가 없기 때문에 특정 효과가 없습니다.
클래스의 Hashcode () 메소드가 위의 요구 사항을 따르지 않으면이 클래스의 두 인스턴스 객체를 비교 한 결과가 equals () 메소드와 동일 할 때 세트에 동시에 저장되지 않아야합니다. 그러나 해시 세트에 저장된 경우, 해시 코드 () 메소드의 리턴 값이 다르기 때문에 (객체의 해시 코드 메소드의 리턴 값은 항상 다릅니다), 두 번째 객체는 해시 코드 계산에 따라 첫 번째 객체와 다른 영역에 놓일 수 있으므로, 첫 번째 객체와 동등한 메소드를 비교할 수 없을 수도 있습니다. 객체 클래스의 Hashcode () 메소드는 해시 세트에 저장되는 객체의 요구 사항을 충족 할 수 없습니다. 반환 값은 객체의 메모리 주소에서 계산되기 때문입니다. 프로그램 실행 중 언제든지 동일한 객체에 의해 반환 된 해시 값은 항상 변경되지 않습니다. 따라서, 두 개의 다른 인스턴스 개체 인 한, 평등 메소드의 비교 결과가 동일하더라도 기본 해시 코드 메소드의 반환 값은 다릅니다.
특정 예를 살펴 보겠습니다.
rectobject 객체 : 패키지 com.weijia.demo; 공개 클래스 rectobject {public int x; 공개 in y; public rectobject (int x, int y) {this.x = x; this.y = y; } @override public int hashcode () {Final int prime = 31; int result = 1; 결과 = 프라임 * 결과 + x; 결과 = 프라임 * 결과 + y; 반환 결과; } @override public boolean equals (Object obj) {if (this == obj) return true; if (obj == null) false를 반환합니다. if (getClass ()! = obj.getClass ()) false를 반환합니다. 최종 퇴근 기타 = (rectobject) obj; if (x! = Other.x) {return false; } if (y! = Other.y) {return false; } true를 반환합니다. }} 우리는 부모 클래스 객체에서 해시 코드를 재정의하고 메소드와 동일하게 해시 코드와 동등한 메소드에서, 두 개의 rectobject 객체의 x와 y 값이 같으면 해시 코드 값이 같고 동일하며 동등한 것이 true를 반환합니다.
테스트 코드는 다음과 같습니다.
패키지 com.weijia.demo; java.util.hashset 가져 오기; public class demo {public static void main (String [] args) {Hashset <rectobject> set = new Hashset <rectobject> (); rectobject r1 = 새로운 rectobject (3,3); rectobject r2 = 새로운 rectobject (5,5); rectobject r3 = 새로운 rectobject (3,3); set.add (r1); set.add (r2); set.add (R3); set.add (r1); System.out.println ( "size :"+set.size ()); }} 우리는 4 개의 객체를 해시 세트에 저장하고 세트 컬렉션의 크기를 인쇄했습니다. 결과는 무엇입니까?
실행 결과 : 크기 : 2
왜 2인가? 우리는 rectobject 클래스의 해시 코드 메소드를 다시 작성하기 때문에 매우 간단합니다. rectobject 객체의 x 및 y 속성 값이 같으면 해시 코드 값도 동일합니다. 따라서 먼저 해시 코드 값을 비교하십시오. R1 및 R2 객체의 X 및 Y 속성 값은 동일하지 않으므로 해시 코드가 다르므로 R2 객체를 넣을 수 있지만 R3 객체의 X 및 Y 속성 값은 R1 객체의 속성 값과 동일하므로 해시 코드는 동일합니다. 이 시점에서 우리는 R1과 R3의 동등한 방법을 비교합니다. 두 가지의 x 값이 같기 때문에 R1과 R3 객체는 동일하므로 R3을 넣을 수 없으므로 결국 R1을 추가하지 않으므로 세트에 R1과 R2가 두 개만 있습니다.
다음으로, rectobject 객체의 해시 코드 메소드, 즉 객체 객체의 해시 코드 메소드를 무시하지 않고 코드를 실행합니다.
실행 결과 : 크기 : 3
이 결과는 또한 매우 간단합니다. 먼저, R1 객체 및 R2 객체의 해시 코드를 판단하십시오. 객체의 해시 코드 메소드는 오브젝트의 로컬 메모리 주소의 변환 결과를 리턴하므로 다른 인스턴스 객체의 해시 코드가 다릅니다. 마찬가지로 R3 및 R1의 해시 코드도 불평등하지만 R1 == R1이므로 최종 세트에는 R1, R2 및 R3이 3 개만 있으므로 크기는 3입니다.
다음으로, 우리는 rectobject 객체의 평등 메소드의 내용을 주석하고 해시 코드 메소드를 주석하지 않고 직접 false를 반환하고 코드를 실행합니다.
실행 결과 : 크기 : 3
이 결과는 예상치 못한 일입니다. 분석하겠습니다.
먼저, R1 및 R2의 객체는 동일하지 않은 해시 코드를 비교하므로 R2가 설정된 다음 R3의 해시 코드 방법을보고 R1과 R3을 비교 한 다음 동등한 방법을 비교합니다. Equals 메소드는 항상 False를 반환하기 때문에 R1과 R3도 불평등하며 R3 및 R2를 언급 할 필요가 없습니다. 두 사람의 해시 코드는 동일하지 않으므로 R3이 설정된 다음 R4를보고 R1과 R4를 비교하여 해시 코드가 동일하다는 것을 비교합니다. Equals 메소드를 비교할 때, Equals는 False를 반환하고 R1과 R4가 동일하지 않기 때문에 동일한 R2와 R4도 불평등하고 R3과 R4도 불평등하므로 R4는 세트 세트에 배치 할 수 있으므로 결과는 크기 : 4 인 이유는 무엇입니까? 3 인 이유는 무엇입니까?
현재 해시 세트의 소스 코드를 확인해야합니다. 다음은 해시 세트에서 ADD 메소드의 소스 코드입니다.
/*** 지정된 요소가 아직 존재하지 않으면 지정된 요소를 추가합니다. * 더 공식적으로,이 세트 에이 세트에 지정된 요소 <tt> e </tt>를 추가하여 * <tt> (e == null? e2 == null : eequals (e2)) </tt>와 같은 요소 <tt> e2 </tt>가 포함되지 않으면이 세트에 추가합니다. *이 세트에 이미 요소가 포함되어 있으면 호출은 세트를 변경하지 않고 <tt> false </tt>를 반환합니다. * * * @param e이 세트에 추가 될 요소 * @return <tt> true </ tt>이 세트가 이미 지정된 * 요소 */ public boolean add (e e) {return map.put (e, present) == null; } 여기서는 해시 세트가 실제로 해시 맵을 기반으로 구현되어 있음을 알 수 있습니다. 해시 맵의 풋 방법을 클릭하고 소스 코드는 다음과 같습니다.
/*** 지정된 값을이 맵에서 지정된 키와 연결합니다. * 맵에 이전에 키에 대한 매핑이 포함되어 있으면 이전 * 값이 대체됩니다. * * @param 키 키 지정된 값이 연결되어야하는 * @param 값 값 지정된 키 * @return <tt> key </tt> 또는 * <tt> null </tt>와 관련된 이전 값을 <tt> key </tt>에 연결하지 않으면 이전 값을 연결합니다. * (a <tt> null </tt> return은 또한 <tt> key </tt>와 함께 <tt> null </tt>를 이전에 연관된지도 */public v put (k key, v value) {if (key == null) return putfornullkey (value)를 나타냅니다. int hash = 해시 (키); int i = indexfor (Hash, table.length); for (Entry <k, v> e = 테이블 [i]; e! = null; e = e.next) {object k; if (e.hash == hash && ((k = e.key) == key || key.equals (k))) {v OldValue = e.Value; e.Value = 값; e.recordaccess (this); OldValue를 반환하십시오. }} modcount ++; Addentry (해시, 키, 값, i); 널 리턴; } 주로 IF의 판단 조건을 살펴 보겠습니다.
먼저 해시 코드가 동일한지 여부를 결정합니다. 동일하지 않은 경우 직접 건너 뛰십시오. 그것이 동일하다면,이 두 객체가 동일인지 또는이 두 객체의 동등한 방법을 비교하십시오. 그것이 수행되거나 작동되기 때문에, 하나가 사실이라면, 우리는 여기에서 설명 할 수 있습니다. 실제로, 위의 세트의 크기는 3입니다. 마지막 R1이 입력되지 않았기 때문에 R1 == R1은 TRUE를 반환했다고 생각되었습니다. 따라서 세트의 크기는 3입니다. 해시 코드 메소드를 설정하면 항상 False를 반환하면이 세트는 4가됩니다.
마지막으로 해시 코드로 인한 메모리 누출을 살펴 보겠습니다. 코드를 살펴 보겠습니다.
패키지 com.weijia.demo; java.util.hashset 가져 오기; public class demo {public static void main (String [] args) {Hashset <rectobject> set = new Hashset <rectobject> (); rectobject r1 = 새로운 rectobject (3,3); rectobject r2 = 새로운 rectobject (5,5); rectobject r3 = 새로운 rectobject (3,3); set.add (r1); set.add (r2); set.add (R3); r3.y = 7; System.out.println ( "삭제 전 크기 :"+set.size ()); set.remove (R3); System.out.println ( "삭제 후 크기 :"+set.size ()); }} 실행 결과 :
삭제 전 크기 : 3
삭제 된 크기 : 3
러쉬, 나는 문제를 발견했고 큰 문제였습니다. R3 객체를 삭제하기 위해 REMING을 호출하여 삭제되었다고 생각했지만 실제로는 삭제되지 않았습니다. 이것을 메모리 누설이라고하며, 이는 사용되지 않은 물체이지만 여전히 메모리에 있습니다. 그래서 우리가 이것을 여러 번 작동시킨 후에는 메모리가 폭발합니다. 제거의 소스 코드를 살펴보십시오.
/***이 세트에서 지정된 요소가 존재하는 경우 지정된 요소를 제거합니다. *보다 공식적으로, * <tt> (o == null? e == null : o.equals (e)) </tt>, *이 세트에 그러한 요소가 포함되도록 요소 <tt> e </tt>를 제거합니다. 이 세트에 <tt> true </tt>를 반환합니다. (이 세트는 호출이 반환되면 * 요소가 포함되지 않습니다.) * * @param o이 세트에서 제거 할 객체, 현재 * @return <tt> true </ tt> 지정된 요소 */ public boolean remove (object o) {return map.remove (o) == present; } 그런 다음 제거 메소드의 소스 코드를보십시오.
/***이 맵에서 지정된 키의 매핑을 제거합니다. * * @param 키 <tt> key </tt> 또는 * <tt> null </tt>와 관련된 이전 값을지도에서 맵핑 할 수있는지도 * @param 키 키 <tt> key </tt>. * (a <tt> null </tt> return은 또한 <tt> key </tt>와 함께 이전에 <Tt> null </tt>를 연결한지도 */public v remain (객체 키) {enther <k, v> e = removeNtryforKey (key)를 나타냅니다. return (e == null? null : e.value); } remodEntryforkey 메소드 소스 코드를 살펴 보겠습니다.
/** * 해시 맵에서 지정된 키 *와 관련된 항목을 제거하고 반환합니다. 해시 맵 에이 키에 대한 매핑 *이 포함되어 있지 않은 경우 NULL을 반환합니다. */ 최종 항목 <k, v> removeEntryForKey (개체 키) {int hash = (key == null)? 0 : 해시 (키); int i = indexfor (Hash, table.length); 입력 <k, v> prev = 테이블 [i]; 입력 <k, v> e = 이전; while (e! = null) {enther <k, v> next = e.next; 객체 k; if (e.hash == hash && ((k = e.key) == key || (key! = null && key.equals (k))) {modcount ++; 크기--; if (prev == e) 테이블 [i] = 다음; else prev.next = 다음; e.recordremoval (this); 반환 e; } prev = e; e = 다음; } 반환 e; } 제거 메소드를 호출 할 때 먼저 객체의 해시 코드 값을 사용하여 객체를 찾은 다음 삭제합니다. 이 문제는 R3 객체의 y 속성 값을 수정했기 때문입니다. 그리고 rectobject 객체의 해시 코드 방법은 작동에 참여하는 y 값을 가지므로 R3 객체의 해시 코드가 변경되었으므로 R3은 제거 방법에서 찾을 수 없으므로 삭제에 실패했습니다. 즉, R3의 해시 코드는 변경되었지만 저장 한 위치는 업데이트되지 않았으며 여전히 원래 위치에 있으므로 새 해시 코드를 사용하여 찾을 때는 확실히 찾을 수 없습니다.
실제로 위의 방법은 구현하기가 매우 간단합니다. 그림과 같이 :
매우 간단한 선형 해시 테이블, 사용 된 해시 함수는 Mod이며 소스 코드는 다음과 같습니다.
/*** 해시 코드 h의 인덱스를 반환합니다. */ static int Indexfor (int h, int length) {return h & (longth-1); } 이것은 실제로 MOD 작동이지만 이러한 종류의 작동은 % 작동보다 효율적입니다.
1,2,3,4,5는 MOD의 결과를 의미하며 각 요소는 연결된 목록 구조에 해당합니다. 따라서 <k, v> 항목을 삭제하려면 먼저 링크 된 목록의 헤더 노드를 얻은 다음 링크 된 목록을 반복 할 수 있도록 해시 코드를 얻게됩니다. 해시 코드와 평등이 같으면이 요소를 삭제하십시오.
위의 메모리 누출은 메시지를 알려줍니다. 객체의 속성 값의 해시 코드 작업에 참여하는 경우 삭제할 때 속성 값을 수정할 수 없습니다. 그렇지 않으면 심각한 문제가 발생합니다.
실제로, 우리는 8 개의 기본 데이터 유형에 해당하는 해시 코드 메소드와 객체 유형의 방법을 볼 수 있습니다.
그중에서도 기본 유형의 해시 코드는 숫자 크기를 직접 반환하는 것이 매우 간단합니다. 문자열 객체는 복잡한 계산 방법을 통해 이루어 지지만이 계산 방법은이 문자열의 값이 동일하면 해시 코드가 동일하도록 보장 할 수 있습니다. 8 개의 기본 유형의 평등 방법은 숫자 값을 직접 비교하는 것이며 문자열 유형 Equals 메소드는 문자열 값을 비교합니다.
위는이 기사의 모든 내용입니다. 모든 사람의 학습에 도움이되기를 바랍니다. 모든 사람이 wulin.com을 더 지원하기를 바랍니다.