equals
알고 있습니다. hashCode
약간 더 적은 수의 사람들이 이것이 왜 그런지, 그리고 이 규칙을 어길 경우 어떤 슬픈 결과가 발생할 수 있는지 알고 있습니다. 나는 이러한 방법의 개념을 고려하고 그 목적을 반복하며 왜 그렇게 연결되어 있는지 이해할 것을 제안합니다. 나는 문제의 모든 세부 사항을 최종적으로 공개하고 더 이상 제3자 소스로 돌아가지 않기 위해 클래스 로딩에 관한 이전 기사와 마찬가지로 이 기사를 직접 썼습니다. 따라서 어딘가에 공백이 있으면 제거해야하기 때문에 건설적인 비판을 기쁘게 생각합니다. 아쉽게도 기사가 꽤 길어졌습니다.
재정의 규칙과 같음
Java에서는 동일한 출처의 두 객체가 논리적으로 동일equals()
하다는 사실을 확인하거나 거부하는 메서드가 필요합니다 . 즉, 두 개체를 비교할 때 프로그래머는 해당 개체의 유효 필드가 동일한지 여부를 이해해야 합니다 . 이 방법은 논리적 동등성을 의미 하므로 모든 필드가 동일해야 할 필요는 없습니다 . 그러나 때로는 이 방법을 특별히 사용할 필요가 없는 경우도 있습니다. 그들이 말했듯이 특정 메커니즘을 사용할 때 문제를 피하는 가장 쉬운 방법은 해당 메커니즘을 사용하지 않는 것입니다. 또한 계약을 위반하면 다른 개체 및 구조가 개체와 상호 작용하는 방식을 이해하는 통제력을 잃게 됩니다. 이후에 오류의 원인을 찾는 것은 매우 어려울 것입니다. equals()
equals
이 메서드를 재정의하지 말아야 하는 경우
- 클래스의 각 인스턴스가 고유한 경우. 이는 데이터 작업을 위해 설계되기보다는 특정 동작을 제공하는 클래스에 더 많이 적용됩니다. 예를 들어 클래스와 같습니다
- 실제로 클래스는 해당 인스턴스의 동등성을 결정할 필요가 없습니다. 예를 들어, 클래스의 경우
- 확장하려는 클래스에 이미 자체 메소드 구현이
equals
있고 이 구현의 동작이 적합할 경우. 예를 들어, 클래스의 경우 equals
그리고 마지막으로 클래스 범위가private
또는 인 경우 재정의할 필요가 없으며package-private
이 메서드가 절대 호출되지 않을 것이라고 확신합니다.
Thread
. 이들에게는 equals
클래스에서 제공하는 메서드를 구현하는 Object
것만으로도 충분합니다. 또 다른 예는 열거형 클래스( Enum
)입니다.
java.util.Random
클래스의 인스턴스를 서로 비교하여 동일한 난수 시퀀스를 반환할 수 있는지 여부를 결정할 필요가 전혀 없습니다. 단순히 이 클래스의 특성상 그러한 동작을 암시하지도 않기 때문입니다.
Set
구현 은 List
각각 및 에 있습니다 . Map
equals
AbstractSet
AbstractList
AbstractMap
계약과 같음
메서드를 재정의할 때equals
개발자는 Java 언어 사양에 정의된 기본 규칙을 준수해야 합니다.
- 반사성 주어진 값에 대해
- 대칭 주어진 값에 대해
- 전이성 지정된 값에 대해
- 일관성 두 개체를 비교하는 데 사용된 필드가 호출 간에 변경되지 않은 경우 반복된
- 비교 null 주어진 값에 대해
x
표현식은 를 x.equals(x)
반환해야 합니다 true
.
주어진 - 그런 의미
x != null
x
and 는 를 y
반환 하는 경우에만 x.equals(y)
반환해야 합니다 . true
y.equals(x)
true
x
and y
는 반환 및 반환 인 z
경우 해당 값을 반환해야 합니다 . x.equals(y)
true
y.equals(z)
true
x.equals(z)
true
x
호출 y
은 x.equals(y)
이 메서드에 대한 이전 호출의 값을 반환합니다.
x
호출은 를 x.equals(null)
반환해야 합니다 false
.
계약 위반과 같습니다
Java Collections Framework의 클래스와 같은 많은 클래스는 메소드 구현에 의존하므로equals()
이를 무시해서는 안 됩니다. 이 방법의 계약을 위반하면 애플리케이션이 비합리적으로 작동할 수 있으며, 이 경우 그 이유를 찾기가 매우 어렵습니다. 재귀성의 원리에 따르면 모든 객체는 그 자체와 동등해야 합니다. 이 원칙을 위반하면 컬렉션에 개체를 추가한 후 메서드를 사용하여 검색할 때 contains()
방금 컬렉션에 추가한 개체를 찾을 수 없습니다. 대칭 조건은 두 개체가 비교되는 순서에 관계없이 동일해야 함을 나타냅니다. 예를 들어 문자열 유형의 필드가 하나만 포함된 클래스가 있는 경우 equals
이 필드를 메서드의 문자열과 비교하는 것은 올바르지 않습니다. 왜냐하면 역비교의 경우 메서드는 항상 값을 반환합니다 false
.
// Нарушение симметричности
public class SomeStringify {
private String s;
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o instanceof SomeStringify) {
return s.equals(((SomeStringify) o).s);
}
// нарушение симметричности, классы разного происхождения
if (o instanceof String) {
return s.equals(o);
}
return false;
}
}
//Правильное определение метода equals
@Override
public boolean equals(Object o) {
if (this == o) return true;
return o instanceof SomeStringify &&
((SomeStringify) o).s.equals(s);
}
전이성의 조건에 따르면 세 객체 중 두 개가 동일하면 이 경우 세 객체 모두가 동일해야 합니다. 이 원칙은 의미 있는 구성 요소를 추가하여 특정 기본 클래스를 확장해야 할 때 쉽게 위반될 수 있습니다 . Point
예를 들어 좌표가 있는 클래스의 경우 점을 x
확장 y
하여 점의 색상을 추가해야 합니다. ColorPoint
이렇게 하려면 적절한 필드를 사용하여 클래스를 선언해야 합니다 color
. 따라서 확장 클래스에서 부모 메서드를 호출 equals
하고 부모에서 좌표만 비교한다고 가정하면 x
색상 y
은 다르지만 좌표가 동일한 두 점은 동일한 것으로 간주되며 이는 잘못된 것입니다. 이 경우 파생 클래스에 색상을 구별하도록 가르쳐야 합니다. 이렇게 하려면 두 가지 방법을 사용할 수 있습니다. 그러나 하나는 대칭의 법칙을 위반 하고, 두 번째는 전이성을 위반하는 것입니다 .
// Первый способ, нарушая симметричность
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
if (!(o instanceof ColorPoint)) return false;
return super.equals(o) && ((ColorPoint) o).color == color;
}
이 경우 호출은 point.equals(colorPoint)
값을 반환 true
하고 비교는 값 colorPoint.equals(point)
을 반환합니다 false
. "그것" 클래스의 객체를 기대합니다. 따라서 대칭의 법칙이 위반됩니다. 두 번째 방법은 점의 색상에 대한 데이터가 없는 경우 "블라인드" 검사를 수행하는 것입니다. 즉, 클래스가 입니다 Point
. 또는 색상에 대한 정보가 있으면 색상을 확인하십시오. 즉, 클래스의 객체를 비교하십시오 ColorPoint
.
// Метод переопределен в классе ColorPoint
@Override
public boolean equals(Object o) {
if (!(o instanceof Point)) return false;
// Слепая проверка
if (!(o instanceof ColorPoint))
return super.equals(o);
// Полная проверка, включая цвет точки
return super.equals(o) && ((ColorPoint) o).color == color;
}
여기서는 다음과 같이 전이성의 원리가 위반됩니다. 다음 객체에 대한 정의가 있다고 가정해 보겠습니다.
ColorPoint p1 = new ColorPoint(1, 2, Color.RED);
Point p2 = new Point(1, 2);
ColorPoint p3 = new ColorPoint(1, 2, Color.BLUE);
따라서 동등성 p1.equals(p2)
과 가 만족 p2.equals(p3)
되더라도 p1.equals(p3)
값을 반환합니다 false
. 동시에 내 생각에는 두 번째 방법이 덜 매력적으로 보입니다. 어떤 경우에는 알고리즘이 블라인드되어 비교를 완전히 수행하지 않을 수 있으며 이에 대해 알지 못할 수도 있습니다. 약간의 시 일반적으로 제가 이해하는 바에 따르면 이 문제에 대한 구체적인 해결책은 없습니다. Kay Horstmann이라는 권위 있는 저자는 연산자 사용을 개체의 클래스를 반환하는 instanceof
메서드 호출로 대체할 수 getClass()
있으며 개체 자체 비교를 시작하기 전에 개체가 동일한 유형인지 확인해야 한다는 의견이 있습니다. , 공통 기원 사실에주의를 기울이지 마십시오. 따라서 대칭성 과 전이성 규칙이 충족됩니다. 그러나 동시에 바리케이드 반대편에는 넓은 집단에서 그다지 존경받지 못하는 또 다른 작가인 Joshua Bloch가 서 있습니다. 그는 이 접근 방식이 Barbara Liskov의 대체 원칙을 위반한다고 믿습니다. 이 원칙은 "호출 코드는 기본 클래스를 알지 못한 채 하위 클래스와 동일한 방식으로 처리해야 한다 "고 명시합니다 . 그리고 Horstmann이 제안한 솔루션에서는 이 원칙이 구현에 따라 다르기 때문에 분명히 위반됩니다. 요컨대 문제가 어둡다는 것이 분명합니다. 또한 Horstmann은 자신의 접근 방식을 적용하는 규칙을 명확히 하고 클래스를 설계할 때 전략을 결정해야 한다고 평이한 영어로 쓰고 있으며, 동등성 테스트가 슈퍼클래스에 의해서만 수행되는 경우 다음을 수행하여 이를 수행할 수 있다는 점에 유의해야 합니다. 작업 instanceof
. 그렇지 않은 경우 파생 클래스에 따라 검사의 의미가 변경되고 메서드 구현을 계층 구조 아래로 이동해야 하는 경우 메서드를 사용해야 합니다 getClass()
. Joshua Bloch는 상속을 포기하고 클래스에 ColorPoint
클래스를 포함시키고 해당 지점에 대한 구체적인 정보를 얻을 수 있는 Point
액세스 방법을 제공함으로써 객체 구성을 사용할 것을 제안합니다. asPoint()
이렇게 하면 모든 규칙을 위반하는 것을 피할 수 있지만 내 생각에는 코드를 이해하기가 더 어려워질 것입니다. 세 번째 옵션은 IDE를 사용하여 같음 메서드의 자동 생성을 사용하는 것입니다. 그런데 Idea는 Horstmann 생성을 재현하므로 슈퍼클래스나 그 하위 클래스에서 메서드를 구현하기 위한 전략을 선택할 수 있습니다. 마지막으로, 다음 일관성 규칙은 객체가 변경되지 x
않더라도 y
객체를 다시 호출하면 x.equals(y)
이전과 동일한 값을 반환해야 한다고 명시합니다. 마지막 규칙은 어떤 객체도 와 같아서는 안 된다는 것입니다 null
. 여기서 모든 것이 명확합니다 null
. 이것은 불확실성입니다. 대상이 불확실성과 동일합니까? 명확하지 않습니다 false
.
같음을 결정하는 일반 알고리즘
this
객체 참조 와 메소드 매개변수 가 같은지 확인하세요o
.if (this == o) return true;
- 링크가 정의되어 있는지
o
, 즉 정의되어 있는지 확인하세요null
.
나중에 객체 유형을 비교할 때 연산자가 사용되는 경우instanceof
이 매개변수가 반환되므로 이 항목을 건너뛸 수false
있습니다null instanceof Object
. - 위의 설명과 자신의 직관에 따라 연산자 나 메소드를
this
사용하여 객체 유형을 비교하세요 .o
instanceof
getClass()
- 하위 클래스에서 메서드가
equals
재정의된 경우 반드시 호출해야 합니다.super.equals(o)
- 매개변수 유형을
o
필수 클래스로 변환합니다. - 모든 중요한 개체 필드를 비교합니다.
- 기본 유형(
float
및 제외double
)의 경우 연산자 사용==
- 참조 필드의 경우 해당 메소드를 호출해야 합니다.
equals
- 배열의 경우 순환 반복 또는 메소드를 사용할 수 있습니다.
Arrays.equals()
- 유형의 경우
float
해당 래퍼 클래스의 비교 방법 을double
사용해야 하며Float.compare()
Double.compare()
- 기본 유형(
- 마지막으로 세 가지 질문에 답해 보세요. 구현된 메서드가 대칭 인가요 ? 전이적 ? 동의 ? 다른 두 가지 원칙( 재귀성과 확실성 ) 은 일반적으로 자동으로 수행됩니다.
HashCode 재정의 규칙
해시는 특정 시점의 상태를 설명하는 개체에서 생성된 숫자입니다. 이 숫자는 주로 Java와 같은 해시 테이블에서 사용됩니다HashMap
. 이 경우 객체를 기반으로 숫자를 얻는 해시 함수는 해시 테이블 전체에 걸쳐 요소가 상대적으로 균등하게 분포되도록 구현되어야 합니다. 또한 함수가 서로 다른 키에 대해 동일한 값을 반환할 때 충돌 가능성을 최소화합니다.
계약 해시 코드
해시 함수를 구현하기 위해 언어 사양은 다음 규칙을 정의합니다.- 동일한 객체에 대해 메서드를
hashCode
두 번 이상 호출하면 값 계산과 관련된 객체의 필드가 변경되지 않은 경우 동일한 해시 값을 반환해야 합니다. - 두 개체에 대해 메서드를 호출하면
hashCode
개체가 동일한 경우 항상 같은 숫자를 반환해야 합니다(equals
이러한 개체에 대해 메서드를 호출하면 반환됨true
). hashCode
동일하지 않은 두 객체에 대해 메서드를 호출하면 서로 다른 해시 값을 반환해야 합니다. 이 요구 사항은 필수 사항은 아니지만 이를 구현하면 해시 테이블 성능에 긍정적인 영향을 미칠 것이라는 점을 고려해야 합니다.
equals 및 hashCode 메소드는 함께 재정의되어야 합니다.
위에서 설명한 계약에 따르면 코드에서 메서드를 재정의할 때는equals
항상 메서드를 재정의해야 합니다 hashCode
. 실제로 클래스의 두 인스턴스는 서로 다른 메모리 영역에 있기 때문에 다르기 때문에 일부 논리적 기준에 따라 비교해야 합니다. 따라서 논리적으로 동등한 두 개체는 동일한 해시 값을 반환해야 합니다. 이러한 메서드 중 하나만 재정의되면 어떻게 되나요?
-
equals
예hashCode
아니오equals
클래스에서 메서드를 올바르게 정의하고hashCode
해당 메서드를 클래스에 그대로 두기로 결정했다고 가정해 보겠습니다Object
. 그러면 메서드의 관점에서 볼 때equals
두 개체는 논리적으로 동일하지만 메서드의 관점에서는hashCode
공통점이 없습니다. 따라서 해시 테이블에 개체를 배치하면 키로 개체를 다시 가져오지 못할 위험이 있습니다.
예를 들어 다음과 같습니다.Map<Point, String> m = new HashMap<>(); m.put(new Point(1, 1), “Point A”); // pointName == null String pointName = m.get(new Point(1, 1));
분명히 배치된 객체와 검색 중인 객체는 논리적으로는 동일하지만 두 개의 다른 객체입니다. 하지만 왜냐하면 우리가 계약을 위반했기 때문에 서로 다른 해시 값을 가지므로 해시 테이블 내부 어딘가에서 개체를 잃어버렸다고 말할 수 있습니다.
-
hashCode
예equals
아니오.메서드를 재정의
hashCode
하고equals
클래스에서 메서드 구현을 상속 하면 어떻게 될까요Object
? 아시다시피,equals
기본 방법은 단순히 포인터를 객체에 비교하여 동일한 객체를 참조하는지 여부를 결정합니다. 모든 표준에 따라 메소드를 작성했다고 가정해 보겠습니다hashCode
. 즉, IDE를 사용하여 생성했으며 논리적으로 동일한 객체에 대해 동일한 해시 값을 반환합니다. 분명히 그렇게 함으로써 우리는 두 객체를 비교하기 위한 몇 가지 메커니즘을 이미 정의했습니다.따라서 이론적으로는 이전 단락의 예가 수행되어야 합니다. 하지만 여전히 해시 테이블에서 개체를 찾을 수 없습니다. 우리는 이것에 가깝지만 최소한 물체가 놓일 해시 테이블 바구니를 찾을 것이기 때문입니다.
해시 테이블에서 개체를 성공적으로 검색하려면 키의 해시 값을 비교하는 것 외에도 키와 검색된 개체가 논리적으로 동일한지 확인하는 방법도 사용됩니다. 즉,
equals
메서드를 재정의하지 않고서는 수행할 수 있는 방법이 없습니다.
hashCode를 결정하기 위한 일반 알고리즘
여기서는 너무 걱정하지 말고 좋아하는 IDE에서 메서드를 생성하면 될 것 같습니다. 황금 비율, 즉 정규 분포를 찾기 위해 이러한 모든 비트가 오른쪽과 왼쪽으로 이동하기 때문에 이것은 완전히 완고한 친구들을 위한 것입니다. 개인적으로 같은 아이디어보다 더 잘하고 더 빠르게 할 수 있을지 의문입니다.결론 대신
따라서 우리는 메소드가 Java 언어에서 잘 정의된 역할을equals
수행 하고 두 객체의 논리적 동등 특성을 얻도록 설계되었음을 알 수 있습니다. hashCode
방법의 경우 equals
이는 객체를 비교하는 것과 직접적인 관련이 있으며 hashCode
, 간접적인 경우에는 해시 테이블이나 유사한 데이터 구조에서 객체의 대략적인 위치를 결정해야 하는 경우입니다. 개체 검색 속도를 높입니다. 계약 외에도 객체 비교와 관련된 또 다른 요구 사항이 있습니다 equals
. hashCode
이는 compareTo
. Comparable
_ equals
_ 이 요구 사항에 따라 개발자는 항상 반환해야 x.equals(y) == true
합니다 x.compareTo(y) == 0
. 즉, 두 객체의 논리적 비교는 애플리케이션의 어느 부분에서도 모순되어서는 안 되며 항상 일관되어야 한다는 것을 알 수 있습니다.
GO TO FULL VERSION