자바 : 왜 컬렉션이 Comparator를 받아들이지 만 (가설적인) Hasher와 Equator는받지 않습니까?


25

이 문제는 인터페이스를 다르게 구현 한 경우에 가장 분명하며 특정 컬렉션의 목적 상 개체의 인터페이스 수준보기에만 관심이 있습니다. 예를 들어, 다음과 같은 인터페이스가 있다고 가정하십시오.

public interface Person {
    int getId();
}

클래스 를 구현 hashcode()하고 구현하는 일반적인 방법 equals()equals메소드 에서 다음과 같은 코드를 갖습니다 .

if (getClass() != other.getClass()) {
    return false;
}

의 구현을 혼합 할 때 문제가 발생 Person합니다 HashMap. (가) 경우 HashMap만의 인터페이스 수준보기에 대한 관심 Person, 그것은 그들의 구현하는 클래스 만 다른 중복으로 끝낼 수 있습니다.

equals()모든 구현에 대해 동일한 자유주의 방법을 사용하여이 사례를 적용 할 수 있지만 equals()다른 컨텍스트 (예 : Person데이터베이스 레코드로 지원되는 두 개의 버전을 버전 번호와 비교)에서 잘못된 작업 을 수행 할 위험이 있습니다.

내 직감에 따르면 평등은 클래스가 아닌 컬렉션마다 정의되어야합니다. 주문에 의존하는 컬렉션을 사용할 때 사용자 정의 Comparator를 사용하여 각 컨텍스트에서 올바른 순서를 선택할 수 있습니다 . 해시 기반 컬렉션에 대한 아날로그는 없습니다. 왜 이런거야?

명확히하기 위해이 질문은 " . 인터페이스의 .compareTo ()가 왜 Java의 클래스에 있는가? "와 구별된다 . 컬렉션 구현을 다루기 때문이다. compareTo()equals()/ hashcode()컬렉션을 사용하는 경우 모두 보편성의 문제로 고통 : 당신이 다른 컬렉션에 대해 서로 다른 비교 기능을 선택할 수 없습니다. 따라서이 질문의 목적 상, 객체의 상속 계층은 전혀 중요하지 않습니다. 중요한 것은 비교 함수가 객체마다 정의되는지 또는 수집마다 정의되는지입니다.


5
Person예상 equalshashCode동작 을 구현하는 래퍼 객체를 항상 도입 할 수 있습니다 . 그러면 님이 있습니다 HashMap<PersonWrapper, V>. 이것은 순수 -OOP 접근 방식이 우아하지 않은 한 가지 예입니다. 객체의 모든 작업이 해당 객체의 방법으로 이해되는 것은 아닙니다. 자바의 전체 Object단지는 - 유형은 다른 책임의 아말감이다 getClass, finalizetoString방법은 오늘날의 모범 사례를 원격으로 정당한 것처럼 보인다.
amon

1
1) C #에서는 IEqualityComparer<T>해시 기반 컬렉션에을 전달할 수 있습니다 . 지정하지 않으면 Object.Equals및에 기반한 기본 구현이 사용 Object.GetHashCode()됩니다. 2) Equals변경 가능한 참조 유형에 대한 IMO 재정의 는 좋은 생각이 아닙니다. 그런 식으로 기본 평등은 매우 엄격하지만 custom을 통해 필요할 때보 다 편안한 평등 규칙을 사용할 수 있습니다 IEqualityComparer<T>.
코드 InChaos

답변:


23

이 디자인은 때때로 "유니버설 평등 (Universal Equality)"으로 알려져 있으며, 두 가지가 같은지 아닌지는 보편적 인 속성이라는 믿음입니다.

또한, 평등은 객체 의 속성 이지만 OO에서는 항상 하나의 단일 객체 에서 메서드를 호출 하며 해당 객체는 해당 메서드 호출을 처리하는 방법 만 결정합니다. 따라서 등식이 비교되는 두 객체 중 하나의 속성 인 Java와 같은 설계에서는 대칭 ( a == bb == a) 과 같은 등가의 일부 기본 속성을 보장 할 수 없습니다. 호출되고 a그것이 호출되는 경우 상기 제 bOO의 기본 원리로 인해, 그것은 전적으로하다 a(전자의 경우)의 결정 또는b자신이 다른 것과 동일한 지 여부를 결정합니다 (두 번째 경우). 대칭을 얻는 유일한 방법은 두 물체가 서로 협력하도록하는 것입니다.

한 가지 해결책은 한 객체의 속성이 아니라 두 객체의 속성 또는 세 번째 객체의 속성을 동등하게 만드는 것입니다. 후자의 옵션은 보편적 평등 문제도 해결합니다. 평등을 세 번째 "컨텍스트"객체의 속성으로 만들면 상황에 따라 다른 EqualityComparer객체 가 있다고 상상할 수 있습니다.

이것은 이다 와 예를 들어, 하스켈 위해 선택된 디자인, Eqtypeclass. 또한 일부 타사 Scala 라이브러리 (예 : ScalaZ)에서 선택한 디자인이지만 기본 호스트 플랫폼과의 호환성을 위해 범용 평등을 사용하는 Scala 코어 또는 표준 라이브러리는 아닙니다.

흥미롭게도 Java Comparable/ Comparator인터페이스로 선택된 디자인 입니다. Java 디자이너는 문제를 분명히 알고 있었지만 어떤 이유로 든 주문을 위해서만 해결했지만 평등 (또는 해싱)으로는 해결하지 못했습니다.

그래서 질문에 관해서는

왜이 Comparator인터페이스하지만 Hasher과는 Equator?

대답은 "모르겠습니다"입니다. 분명히 Java 디자이너는의 존재에 의해 입증 된 바와 같이 문제를 알고 Comparator있었지만 분명히 평등과 해싱에 대한 문제는 아니라고 생각했습니다. 다른 언어와 라이브러리는 다른 선택을합니다.


7
+1이지만 여러 디스패치가 존재하는 OO 언어가 있습니다 (Smalltalk, Common Lisp). 그래서 항상 다음 문장에 너무 강한 : "OO에, 당신은 항상 하나의 객체의 메소드를 호출".
코어 덤프

내가 찾던 인용문을 찾았습니다. JLS 1.0에 따라, The methods equals and hashCode are declared for the benefit of hashtables such as java.util.Hashtable즉 모두 equalshashCode같이 도입 된 Object자바에 의한 방법은 DEVS 만을 위해서 Hashtable-이 UE 또는 사양에 아무것도 silimar의 어느 곳의 어떤 개념이 없다, 그리고 견적 나를 위해 분명 충분하다; 하지에 대한 경우 Hashtable, equals아마 같은 인터페이스에서 있었던 것이다 Comparable. 따라서 이전에는 귀하의 답변이 정확하다고 믿었지만 지금은 확실하지 않습니다.
vaxquis 2016 년

@ JörgWMittag 그것은 오타, IFTFY였습니다. BTW,의 말하기 clone- 그것은 원래이었다 운영자 가 아닌 방법 (오크 언어 사양 참조), 인용 : The unary operator clone is applied to an object. (...) The clone operator is normally used inside new to clone the prototype of some class, before applying the initializers (constructors)- 세 가지 키워드처럼 운영자가 있었다 instanceof new clone(8.1 절, 연산자). 나는 clone/ Cloneable엉망 의 실제 (역사적) 이유라고 생각합니다. Cloneable단순히 단순한 발명품이었고 기존 clone코드는 그와 함께 개조되었습니다.
vaxquis

2
"이것은 예를 들어, Eq 타입 클래스와 함께 Haskell을 위해 선택된 디자인입니다."이것은 사실이지만, Haskell은 다른 타입의 두 객체가 결코 같지 않지만 Java의 접근 방식은 동일하지 않다는 것을 명시 적으로 언급 할 가치가 있습니다 . 따라서 항등 연산은 유형의 일부 이므로 "typeclass"는 세 번째 컨텍스트 값의 일부가 아닙니다.
Jack

19

에 대한 실제 답변

왜이 Comparator인터페이스하지만 Hasher과는 Equator?

Josh Bloch가 인용 한 예입니다 .

원래 Java API는 마감 시한에 맞춰 매우 빠르게 마감되어 시장 마감 시간을 맞이했습니다. 원래 Java 팀은 ​​놀라운 작업을 수행했지만 모든 API가 완벽하지는 않습니다.

문제는 다른 유사한 문제, 예와 마찬가지로, 자바의 역사에서 유일하게 놓여 .clone()Cloneable.

tl; dr

역사적 이유로 주로; 현재의 행동 / 추억은 JDK 1.0에서 도입되었으며, 이전 버전의 코드 호환성을 유지하는 것이 사실상 불가능했기 때문에 나중에 수정되지 않았습니다.


먼저 몇 가지 잘 알려진 Java 사실을 요약 해 보겠습니다.

  1. Java는 처음부터 현재까지 이전 버전과의 호환성을 자랑스럽게 생각했으며, 레거시 API는 최신 버전에서 계속 지원되어야합니다.
  2. 따라서 JDK 1.0에 도입 된 거의 모든 언어 구성 요소는 현재까지 살아 왔으며
  3. Hashtable, .hashCode().equals()JDK 1.0 (구현 된 해시 테이블 )
  4. Comparable/ Comparator는 JDK 1.2 ( Comparable ) 에서 소개되었습니다 .

이제 다음과 같습니다.

  1. 그것은 사실상 불가능 및 개조에 무감각했다 .hashCode().equals()여전히 사람들 후 이전 버전과의 호환성을 유지하는 예 있기 때문에 객체 (superobject)에 이르렀보다 더 추상화가가 실현 동안 서로 다른 인터페이스에 각자 1.2으로 자바 프로그래머하는 모든는 것을 알고 Object그들을 가지고, 그들은했다 컴파일 된 코드 (JVM) 호환성을 제공하기 위해 물리적으로 거기에 머무르고 Object실제로 구현 한 모든 서브 클래스에 명시 적 인터페이스를 추가하면 이 엉망이 Clonable하나가 될 것입니다 ( Bloch는 EJ 2nd에서도 논의 된 Cloneable sucks 에 대해 논의합니다) SO를 포함한 다른 많은 장소)
  2. 그들은 미래 세대가 지속적으로 WTF의 출처를 갖도록 방치했습니다.

이제 " Hashtable이것과 함께 무엇을 가지고 있습니까?"

대답은 : hashCode()/ equals()계약 및 1995과 1996의 핵심 자바 개발자의 그리 좋은 언어 설계 기술.

1996 년 4 월 4 일자 Java 1.0 언어 사양 에서 인용 Object: The Class , p.41 :

방법 equals과 (§21.7) hashCode과 같은 해시 테이블의 이점을 위해 선언되었습니다 java.util.Hashtable. equals 방법은 객체 평등 개념을 정의하며, 이는 기준이 아닌 값을 기준으로합니다.

(이 정확한 문이되었습니다 유의 변경 , 말, 이후 버전에서 견적 : The method hashCode is very useful, together with the method equals, in hashtables such as java.util.HashMap.그것은 불가능 직접적인을 할 수있게 Hashtable- hashCode- equals역사적 JLS을 읽지 않고 연결을!)

Java 팀은 ​​좋은 사전 스타일 컬렉션을 원한다고 결정하고 Hashtable지금까지 좋은 아이디어를 만들었지 만 프로그래머는 가능한 한 적은 코드 / 학습 곡선으로 사용할 수 있기를 원했습니다. 그리고 아직 제네릭이 없었기 때문에 (결국 JDK 1.0), 모든 Object 입력 Hashtable이 명시 적으로 일부 인터페이스를 구현해야 한다는 것을 의미합니다 (인터페이스는 여전히 초기에 시작 Comparable되었지만 아직까지는 없었습니다 !). , 이것을 많은 사람들이 사용하기 위해 억제하거나- 해시 방법 Object암시 적으로 구현 해야합니다 .

분명히, 위에서 설명한 이유로 솔루션 2를 사용했습니다. 네, 이제 우리는 그들이 틀렸다는 것을 압니다. ... 후시가 현명 해지기 쉽습니다. 킬킬 웃음

이제 hashCode() 모든 객체는 별개 가지고있다 갖는 필요 equals()방법 은 그 아주 명백했다, 그래서 - equals()에 넣어했다 Object뿐만 아니라.

때문에 기본 유효한에 이러한 방법의 구현 ab Object의 중복 됨으로써 본질적으로 쓸모가 (하고 a.equals(b) 동일a==ba.hashCode() == b.hashCode() 거의 동일a==b, 또한 하지 않는 hashCode및 / 또는 equals수십만 오버라이드 (override)되어, 또는 GC Object응용 프로그램의 수명주기 동안의를 1 ) , 주로 백업 수단으로 사용하기 편리하도록 제공된 것이 안전합니다. 이것은 우리가하는 잘 알려진 사실에 얼마나 정확하게 항상 모두를 오버라이드 (override) .equals().hashCode()당신이 실제로 개체를 비교하거나 해시 저장에 대한하려는 경우. 다른 것없이 하나만 재정의하는 것이 코드를 망치는 좋은 방법입니다 (사악한 비교 결과 또는 엄청나게 높은 버킷 충돌 값으로)-머리를 얻는 것은 초보자에게 지속적인 혼란과 오류의 원인입니다 (검색 SO) 더 노련한 사람들에게는 끊임없이 성가신 일입니다.

또한 C #이 equals & hashcode를 조금 더 잘 처리하지만 Eric Lippert 자신도 C #이 시작되기 몇 년 전에 Sun과 Java가했던 것과 거의 같은 실수를했다고 말합니다 .

그러나 왜 모든 객체가 해시 테이블에 삽입하기 위해 스스로 해시 할 수 있어야 하는가? 모든 물체가 할 수 있어야하는 이상한 것 같습니다. 오늘 형식 시스템을 처음부터 새로 디자인했다면 해시가 IHashable인터페이스 와 다르게 다르게 수행 될 수 있다고 생각합니다 . 그러나 CLR 형식 시스템을 설계 할 때는 일반 형식이 없었으므로 모든 개체를 저장할 수있는 범용 해시 테이블이 필요했습니다.

1 물론 Object#hashCode수는 여전히에서 충돌하지만 그렇게 노력 약간의 소요, 참조 : http://bugs.java.com/bugdatabase/view_bug.do?bug_id=6809470 자세한 내용 및 링크 된 버그 리포트; https://stackoverflow.com/questions/1381060/hashcode-uniqueness/1381114#1381114 에서이 주제를보다 심도있게 다룹니다.


그래도 자바 만이 아니다. 많은 동시대 인 (Ruby, Python,…)과 전임자 (Smalltalk,…)와 그 후임자도 Universal Equality와 Universal Hashability (단어입니까?)를 가지고 있습니다.
Jörg W Mittag

JörgWMittag @ 참조 programmers.stackexchange.com/questions/283194/...을 - 나는 자바에서 "UE"에 대해 동의 할했습니다; UE는 역사적으로 결코 Object디자인에 큰 관심이 없었습니다 . 해시 가능성이었습니다.
vaxquis

@vaxquis 나는 이것에 대해 하프하고 싶지 않지만 이전의 의견은 동시에 도달 가능한 두 객체가 동일한 (기본) 해시 코드를 가질 있음을 보여줍니다 .
Monica Reinstate Monica

1
@vaxquis OK. 나는 그것을 산다. 내 관심사는 배우는 사람이 이것을보고 등이 아닌 시스템 해시 코드를 사용하여 영리하다고 생각한다는 것입니다. 그렇게하는 경우 드문 경우를 제외하고는 제대로 작동하지 않을 것입니다. 문제를 안정적으로 재현 할 방법이 없습니다.
JimmyJames

1
수락 된 답변의 결론은 "모르겠습니다"
피닉스
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.