"이제 유용한 몇 가지 방법인 equals(Object o) & hashCode() 에 대해 말씀드리겠습니다 ."
"자바에서 참조 변수를 비교할 때 개체 자체가 비교되는 것이 아니라 개체에 대한 참조라는 사실을 이미 기억하셨을 것입니다."
암호 | 설명 |
---|---|
|
i is not equal to j 변수가 다른 객체를 가리킵니다. 객체가 동일한 데이터를 포함하더라도. |
|
나는 j와 같다. 변수에는 동일한 개체에 대한 참조가 포함됩니다. |
"네, 기억나네요."
같음 . _
"여기서 equals 메소드는 표준 솔루션입니다. equals 메소드의 목적은 오브젝트 내부에 저장된 내용을 비교하여 오브젝트가 내부적으로 동일한지 여부를 판별하는 것입니다."
"그럼 어떻게 하죠?""모든 것이 toString() 메서드와 매우 유사합니다."
Object 클래스에는 단순히 참조를 비교하는 equals 메소드의 자체 구현이 있습니다.
public boolean equals(Object obj)
{
return (this == obj);
}
"좋아요... 다시 그 얘기로 돌아가죠?"
"턱을 높이세요! 실제로는 매우 까다롭습니다."
"이 방법은 개발자가 자신의 클래스에서 덮어쓸 수 있도록 만들어졌습니다. 결국 클래스의 개발자만이 비교할 때 어떤 데이터가 관련이 있고 무엇이 그렇지 않은지 알고 있습니다."
"예를 들어 주시겠습니까?"
"물론입니다. 수학 분수를 나타내는 클래스가 있다고 가정해 보겠습니다. 다음과 같이 표시됩니다."
class Fraction
{
private int numerator;
private int denominator;
Fraction(int numerator, int denominator)
{
this.numerator = numerator;
this.denominator = denominator;
}public boolean equals(Object obj)
{
if (obj==null)
return false;
if (obj.getClass() != this.getClass() )
return false;
Fraction other = (Fraction) obj;
return this.numerator* other.denominator == this.denominator * other.numerator;
}
}
메서드 호출 예: |
---|
Fraction one = new Fraction(2,3); Fraction two = new Fraction(4,6); System.out.println(one.equals(two)); |
메서드 호출은 true를 반환합니다. 분수 2/3은 분수 4/6과 같습니다. |
"이제 이 예를 분석해 보겠습니다."
"우리는 equals 메서드를 재정의했으므로 Fraction 개체는 자체 구현을 갖게 됩니다.
"방법에는 몇 가지 검사가 있습니다."
" 1) 비교를 위해 전달된 개체가 null 이면 개체가 같지 않은 것입니다. 개체에서 equals 메서드를 호출할 수 있다면 확실히 null 이 아닙니다 ."
" 2) 클래스 비교. 개체가 다른 클래스의 인스턴스인 경우 비교를 시도하지 않습니다. 대신 즉시 return false를 사용하여 서로 다른 개체임을 나타냅니다."
" 3) 2/3은 4/6과 같다는 것을 2학년 때부터 모두 기억하고 있습니다. 그런데 그것을 어떻게 확인합니까?"
2/3 == 4/6 |
---|
양변에 두 제수(6과 3)를 곱하면 다음을 얻습니다. |
6 * 2 == 4 * 3 |
12 == 12 |
일반 규칙: |
a / b == c / d 이면 a * d == c * b |
"따라서 equals 메서드의 세 번째 부분에서 전달된 개체를 Fraction으로 캐스팅하고 분수를 비교합니다."
"알겠습니다. 단순히 분자와 분자, 분모와 분모를 비교하면 2/3은 4/6과 같지 않습니다."
"클래스의 개발자만이 클래스를 올바르게 비교하는 방법을 안다고 하신 말씀의 의미를 이제 이해합니다."
"예, 하지만 그건 이야기의 절반에 불과합니다. 또 다른 방법이 있습니다: hashCode(). "
"equals 메서드에 대한 모든 것이 이제 의미가 있지만 왜 hashCode () 가 필요한가요 ? "
" 빠른 비교를 위해서는 hashCode 메서드가 필요합니다."
" equals 메서드에는 큰 단점이 있습니다. 너무 느리게 작동합니다. 수백만 개의 요소 집합이 있고 특정 개체가 포함되어 있는지 확인해야 한다고 가정해 보겠습니다. 어떻게 합니까?"
"루프를 사용하여 모든 요소를 순환하고 개체를 세트의 각 개체와 비교할 수 있습니다. 일치하는 항목을 찾을 때까지."
"그리고 그것이 거기에 없다면? 우리는 단지 그 물체가 거기에 없다는 것을 알아내기 위해 백만 번의 비교를 할 것입니까? 그것은 많이 보이지 않습니까?"
"예, 저도 비교가 너무 많다는 것을 알고 있습니다. 다른 방법이 있습니까?"
"예, 이를 위해 hashCode ()를 사용할 수 있습니다 .
hashCode () 메서드는 각 개체에 대해 특정 번호를 반환합니다 . 클래스의 개발자는 equals 메서드에 대해 수행하는 것과 마찬가지로 반환되는 숫자를 결정합니다.
"예를 들어 보겠습니다."
"백만 개의 10자리 숫자가 있다고 상상해 보세요. 그런 다음 숫자를 100으로 나눈 후 각 숫자의 해시 코드를 나머지로 만들 수 있습니다."
예를 들면 다음과 같습니다.
숫자 | 우리의 해시코드 |
---|---|
1234567890 | 90 |
9876554321 | 21 |
9876554221 | 21 |
9886554121 | 21 |
"그래, 말이 되는군. 그러면 이 해시코드로 무엇을 할까?"
"숫자를 비교하는 대신 해시 코드를 비교합니다 . 그렇게 하면 더 빠릅니다."
"그리고 hashCode가 동일한 경우에만 equals를 호출합니다 ."
"예, 그게 더 빠릅니다. 하지만 우리는 여전히 백만 번을 비교해야 합니다. 우리는 단지 더 작은 숫자를 비교하고 있으며 일치하는 해시 코드가 있는 모든 숫자에 대해 여전히 등호를 호출해야 합니다."
"아니요, 훨씬 적은 수의 비교로 벗어날 수 있습니다."
"우리 세트가 해시코드 로 그룹화되거나 정렬된 숫자를 저장한다고 상상해 보십시오 (이런 방식으로 정렬하는 것은 본질적으로 그룹화입니다. 동일한 해시코드를 가진 숫자가 서로 옆에 있기 때문입니다.) 그런 다음 관련 없는 그룹을 매우 빠르고 쉽게 삭제할 수 있습니다. 충분합니다. 해당 hashCode가 개체의 hashCode와 일치하는지 확인하기 위해 그룹당 한 번씩 확인합니다."
"당신이 눈으로 알아볼 수 있고 기숙사 17호에 사는 친구를 찾고 있는 학생이라고 상상해보세요. 그런 다음 대학의 모든 기숙사에 가서 '여기가 17호 기숙사인가요?'라고 물어보세요. 그렇지 않으면 기숙사에 있는 모든 사람들을 무시하고 다음 방으로 이동하고 대답이 '예'이면 각 방을 지나쳐 친구를 찾습니다."
"이 예에서 기숙사 번호(17)는 해시코드입니다."
"hashCode 함수를 구현하는 개발자는 다음을 알아야 합니다."
A) 두 개의 서로 다른 개체가 동일한 해시코드를 가질 수 있음 (다른 사람들이 같은 기숙사에 살 수 있음)
B) 동일한 개체 ( equals 메서드에 따라 ) 는 동일한 hashCode를 가져야 합니다. .
C) 동일한 hashCode를 가진 다른 개체가 많지 않도록 해시 코드를 선택해야 합니다. 만약 있다면, 해시코드의 잠재적인 이점이 사라집니다.
"그리고 이제 가장 중요한 것이 있습니다. equals 메소드를 오버라이드하는 경우 반드시 hashCode () 메소드를 오버라이드하고 위에서 설명한 세 가지 규칙을 준수해야 합니다.
"이유는 다음과 같습니다. Java에서 컬렉션의 객체는 equals를 사용하여 비교/검색되기 전에 항상 hashCode()를 사용하여 비교/검색됩니다. 그리고 동일한 객체에 다른 hashCode가 있는 경우 객체는 서로 다른 것으로 간주되며 equals 메서드가 사용됩니다. 부르지도 않을 것입니다.
"Fraction 예제에서 hashCode를 분자와 동일하게 만들면 분수 2/3과 4/6은 다른 hashCode를 갖게 됩니다. 분수는 동일하고 equals 메서드는 동일하다고 말하지만 hashCode는 다음과 같이 말합니다. 그것들은 다릅니다. 그리고 equals를 사용하여 비교하기 전에 hashCode를 사용하여 비교하면 개체가 다르다고 결론을 내리고 equals 메서드로 만들지도 않습니다."
예를 들면 다음과 같습니다.
HashSet<Fraction>set = new HashSet<Fraction>(); set.add(new Fraction(2,3)); System.out.println( set.contains(new Fraction(4,6)) ); |
hashCode() 메서드가 분수의 분자를 반환하면 결과는 false 가 됩니다 . 그리고 «new Fraction(4,6) » 개체는 컬렉션에서 찾을 수 없습니다. |
"그래서 분수에 대한 hashCode를 구현하는 올바른 방법은 무엇입니까?"
"여기서 등가 분수는 동일한 해시 코드를 가져야 한다는 점을 기억해야 합니다."
" 버전 1 : hashCode는 정수 나누기 결과와 같습니다."
"7/5와 6/5의 경우 1이 됩니다."
"4/5와 3/5의 경우 0이 됩니다."
"하지만 이 옵션은 의도적으로 1보다 작은 분수를 비교하는 데 적합하지 않습니다. hashCode(정수 나누기 결과)는 항상 0입니다."
" 버전 2 : hashCode는 분모를 분자로 정수로 나눈 결과와 같습니다."
"이 옵션은 분수가 1보다 작은 경우에 적합합니다. 분수가 1보다 작으면 그 역수는 1보다 큽니다. 그리고 모든 분수를 반전해도 비교는 전혀 영향을 받지 않습니다."
"우리의 최종 버전은 두 가지 솔루션을 결합합니다."
public int hashCode()
{
return numerator/denominator + denominator/numerator;
}
2/3과 4/6을 사용하여 테스트해 봅시다. 그들은 동일한 hashCodes를 가져야 합니다:
분수 2/3 | 분수 4/6 | |
---|---|---|
분자 / 분모 | 2 / 3 == 0 | 4 / 6 == 0 |
분모 / 분자 | 3 / 2 == 1 | 6 / 4 == 1 |
분자 / 분모 + 분모 / 분자 |
0 + 1 == 1 | 0 + 1 == 1 |
"지금은 여기까지입니다."
"고마워, 엘리. 정말 흥미로웠어."
GO TO FULL VERSION