무엇의 인터페이스에서 완전히 일반적인 get 메소드를 가지고 있지하는 결정 뒤에 이유가 있습니다 java.util.Map<K, V>
.
질문을 명확히하기 위해 메소드의 서명은
V get(Object key)
대신에
V get(K key)
왜 그런지 궁금 remove, containsKey, containsValue
합니다.
무엇의 인터페이스에서 완전히 일반적인 get 메소드를 가지고 있지하는 결정 뒤에 이유가 있습니다 java.util.Map<K, V>
.
질문을 명확히하기 위해 메소드의 서명은
V get(Object key)
대신에
V get(K key)
왜 그런지 궁금 remove, containsKey, containsValue
합니다.
답변:
다른 사람들이 언급했듯이, get()
검색하는 항목의 키가 전달하는 객체와 동일한 유형일 필요가 없기 때문에, 등이 일반적인 이유는 아닙니다 get()
. 방법의 스펙은 그것들이 동일 할 필요가있다. 이는 equals()
메소드가 오브젝트와 동일한 유형이 아니라 오브젝트를 매개 변수로 사용 하는 방법 에서 비롯 됩니다.
많은 클래스가 equals()
자신의 객체가 자신의 클래스의 객체와 같을 수 있도록 정의한 것은 일반적으로 사실이지만 , Java에는 그렇지 않은 곳이 많이 있습니다. 예를 들어,에 대한 사양은 List.equals()
두 List 객체가 모두 List이고 서로 다른 구현 인 경우에도 동일한 내용을 갖는 경우 동일하다고 말합니다 List
. 그래서 방법의 사양에 따라,이 문제의 예를 다시 오는 것은을 가질 수 있습니다 Map<ArrayList, Something>
나를 호출 할 수 get()
로모그래퍼 LinkedList
인수로하고,이 같은 내용 목록입니다 키를 검색합니다. get()
일반적이고 인수 유형이 제한되어 있으면 불가능합니다 .
m.get(linkedList)
왜 m
유형을 Map<List,Something>
? 로 정의하지 않았 습니까? 인터페이스를 얻기 위해 유형을 m.get(HappensToBeEqual)
변경하지 않고 호출하는 것이 적합한 유스 케이스를 생각할 수 없습니다 Map
.
TreeMap
잘못된 유형의 객체를 get
메소드에 전달하면 실패 하지만 때때로 (예 :지도가 비어있는 경우) 전달 될 수 있습니다. 그리고 더 악화하는 경우 공급 방법 (일반 서명이!) 어떤 선택하지 않은 경고없이 잘못된 유형의 인수를 호출 할 수 있습니다. 이것은 이다 깨진 행동. Comparator
compare
Google의 멋진 Java 코더 인 Kevin Bourrillion 은 얼마 전에 블로그 게시물 에이 문제에 대해 정확히 썼습니다 (확실히 Set
대신 Map
). 가장 관련성이 높은 문장 :
통일적으로, Java Collections Framework (및 Google Collections Library)의 메소드는 콜렉션이 손상되는 것을 방지해야하는 경우를 제외하고는 매개 변수 유형을 제한하지 않습니다.
.NET은 올바른 키 유형을 요구하는 것이 좋을 것 같습니다.하지만 블로그 게시물의 추론을 따르는 것이 좋습니다. .NET에 대해 언급했지만 .NET에서 문제가되지 않는 이유 중 일부는 .NET에서 차이가 더 제한적이므로 더 큰 문제 가 있음을 설명하는 것이 좋습니다.)
Integer
하고이 Double
서로 동일 할 수 없다, 그것은 여부를 물어 공정한 질문 아직 Set<? extends Number>
값을 포함은 new Integer(5)
.
Set<? extends Foo>
. 지도의 키 유형을 매우 자주 변경 한 후 컴파일러가 코드를 업데이트해야하는 모든 위치를 찾을 수 없다는 것에 좌절했습니다. 나는 이것이 올바른 트레이드 오프라고 확신하지 못한다.
계약은 다음과 같이 표현됩니다.
보다 공식적으로,이 맵에 키 k에서 값 v 로의 매핑이 포함되어 있으면 (key == null? k == null : key.equals (k) )이 메소드는 v를 반환합니다. 그렇지 않으면 null을 반환합니다. 이러한 매핑은 최대 하나만있을 수 있습니다.
(내 강조)
따라서 성공적인 키 조회는 입력 키의 동등성 방법 구현에 따라 다릅니다. 반드시 k의 클래스에 의존 하지는 않습니다 .
hashCode()
. hashCode ()의 적절한 구현이 없다면, equals()
이 경우 에는 훌륭하게 구현 되는 것이 쓸모가 없습니다.
get()
유형의 인수를 취할 필요는 없습니다 Object
. get 메소드가 키 유형으로 제한되었다고 가정하십시오 K
. 계약은 여전히 유효합니다. 물론, 컴파일 시간 유형이 서브 클래스가 아닌 곳에서의 사용 K
은 이제 컴파일에 실패하지만 계약이 무효화되지는 않습니다. 계약은 코드가 컴파일되면 어떻게되는지 암시 적으로 논의하기 때문입니다.
그것은 Postel 's Law의 적용이다. "당신이하는 일에 보수적이며, 다른 사람들로부터 받아 들여지는 것에있어 자유 롭다."
유형에 관계없이 평등 검사를 수행 할 수 있습니다. 이 equals
메소드는 Object
클래스 에 정의 Object
되어 있으며 매개 변수로 허용합니다 . 따라서 키 동등성 및 키 동등성 기반 조작이 모든 Object
유형 을 승인하는 것이 좋습니다.
맵이 키 값을 반환하면 type 매개 변수를 사용하여 가능한 한 많은 유형 정보를 보존합니다.
V Get(K k)
그것은 또한 의미가 있기 때문에 C #으로. Java와 .NET 접근법의 차이점은 실제로 비 일치 항목을 차단하는 사람입니다. C #에서는 컴파일러이고 Java에서는 컬렉션입니다. 나는 한 동안 .NET의 일관성 컬렉션 클래스 번에 대한 분노 만 Get()
하고 Remove()
만 확실히 일치 유형을 받아들이는 실수로 잘못된 값을 전달하지 못하도록합니다.
contains : K -> boolean
.
나는 Generics Tutorial 의이 섹션에서 상황을 설명한다고 생각합니다 (내 강조).
"일반 API가 지나치게 제한적이지 않아야합니다. API의 원래 계약을 계속 지원해야합니다. java.util.Collection의 예제를 다시 고려하십시오. 일반적인 API는 다음과 같습니다.
interface Collection {
public boolean containsAll(Collection c);
...
}
그것을 생성하려는 순진한 시도는 다음과 같습니다.
interface Collection<E> {
public boolean containsAll(Collection<E> c);
...
}
이것은 확실히 유형 안전하지만 API의 원래 계약을 준수하지는 않습니다. containsAll () 메서드는 모든 종류의 들어오는 컬렉션에서 작동합니다. 들어오는 컬렉션에 실제로 E 인스턴스 만 포함 된 경우에만 성공하지만 다음과 같습니다.
containsAll( Collection< ? extends E > c )
?
containsAll
로모그래퍼 A는 수퍼 의 . 이 경우 허용되지 않습니다 . 또한, 같은 되는 명시 적 실시 예에서 설명한, 그것은 (반환 값은 다음으로되는 다른 유형의 집합을 전달할 합법 ). Collection<S>
S
E
containsAll( Collection< ? extends E > c )
false
그 이유는 밀폐에 의해 결정된다는 것이다 equals
및 hashCode
그 위에있는 방법 Object
및 가지고 두 Object
파라미터. 이것은 Java 표준 라이브러리의 초기 설계 결함이었습니다. Java 유형 시스템의 제한 사항과 함께 equals 및 hashCode에 의존하는 모든 것을 강제로 가져옵니다 Object
.
자바 형태 보증 된 해시 테이블과 평등을 할 수있는 유일한 방법은 삼가고하는 것입니다 Object.equals
및 Object.hashCode
및 일반 대체를 사용합니다. 기능 자바는 바로 이러한 목적을 위해 형 클래스와 함께 제공 : Hash<A>
와 Equal<A>
. 래퍼 HashMap<K, V>
소요가 제공된다 Hash<K>
및 Equal<K>
생성자입니다. 따라서이 클래스 get
와 contains
메소드는 유형의 일반 인수를 사용합니다 K
.
예:
HashMap<String, Integer> h =
new HashMap<String, Integer>(Equal.stringEqual, Hash.stringHash);
h.add("one", 1);
h.get("one"); // All good
h.get(Integer.valueOf(1)); // Compiler error
적합성.
제네릭을 사용할 수 있기 전에 get (Object o) 만있었습니다.
그들이이 방법을 get (<K> o)으로 변경했다면 작업 코드를 다시 컴파일하기 위해 Java 사용자에게 대규모 코드 유지 관리를 강제했을 것입니다.
그들은 수 도입 된 한 추가 방법을 get_checked 말 (<K> O)과 온화한 전환 경로가 있었다, 그래서 이전의 get () 메소드를 중단합니다. 그러나 어떤 이유로 든 이것이 이루어지지 않았습니다. 현재 상황은 get () 인수와지도의 선언 된 키 유형 <K> 사이의 유형 호환성을 확인하기 위해 findBugs와 같은 도구를 설치해야한다는 것입니다.
.equals ()의 의미와 관련된 인수는 가짜입니다. (기술적으로는 정확하지만 여전히 가짜라고 생각합니다. o1과 o2에 공통 수퍼 클래스가없는 경우 올바른 마음의 어떤 디자이너도 o1.equals (o2)를 적용하지 않습니다.)
한 가지 더 중요한 이유가 있는데, 기술적으로 맵을 깨뜨릴 수 없기 때문입니다.
Java는 다음과 같은 다형성 일반 구조를 가지고 <? extends SomeClass>
있습니다. 이러한 참조는로 표시된 유형을 가리킬 수 있습니다 <AnySubclassOfSomeClass>
. 그러나 다형성 제네릭은 해당 참조를 읽기 전용으로 만듭니다 . 컴파일러를 사용하면 제네릭 형식을 반환 형식의 메서드 (예 : 단순 게터)로만 사용할 수 있지만 제네릭 형식이 인수 인 메서드 (보통 세터)는 사용하지 않습니다. 즉, 작성 Map<? extends KeyType, ValueType>
하면 컴파일러에서 메소드 호출을 허용하지 않으므로 get(<? extends KeyType>)
맵이 쓸모가 없습니다. 유일한 해결책은이 방법을 일반화하지 않는 것 get(Object)
입니다.
나는 이것을보고 그들이 왜 이런 식으로했는지 생각했습니다. 기존 답변 중 왜 새로운 일반 인터페이스가 키에 적합한 유형 만 허용하도록 할 수 없었는지 설명하지는 않습니다. 실제 이유는 제네릭을 도입했지만 새 인터페이스를 만들지 않았기 때문입니다. Map 인터페이스는 일반 및 비 제네릭 버전의 역할을하는 기존의 비 제네릭 맵과 동일합니다. 이 방법으로 일반이 아닌 맵을 허용하는 메소드가 있으면 전달할 수 있으며 Map<String, Customer>
여전히 작동합니다. 동시에 get 계약은 Object를 수락하므로 새 인터페이스도이 계약을 지원해야합니다.
제 생각에 그들은 새로운 인터페이스를 추가하고 기존 컬렉션에 모두 구현해야했지만 get 메소드의 디자인이 더 나쁘더라도 호환 가능한 인터페이스를 선호하기로 결정했습니다. 컬렉션 자체는 기존의 메서드와 호환되며 인터페이스만으로는 불가능합니다.
우리는 지금 큰 리팩토링을하고 있으며 이전 유형의 get ()을 놓치지 않았는지 확인하기 위해 강력하게 유형이 지정된 get ()이 누락되었습니다.
그러나 컴파일 시간 검사를위한 해결 방법 / 추악한 트릭을 발견했습니다 : 강력한 형식의 get, containsKey, remove ...를 사용하여 Map 인터페이스를 만들고 프로젝트의 java.util 패키지에 넣으십시오.
get ()을 호출하는 것만으로 컴파일 오류가 발생합니다. ... 잘못된 유형으로, 다른 모든 것은 컴파일러에 적합합니다 (적어도 일식 케플러 내부).
빌드 확인 후이 인터페이스를 삭제하는 것을 잊지 마십시오. 런타임에서 원하는 것이 아닙니다.