Map.get (Object key)이 (완전히) 일반이 아닌 이유는 무엇입니까


405

무엇의 인터페이스에서 완전히 일반적인 get 메소드를 가지고 있지하는 결정 뒤에 이유가 있습니다 java.util.Map<K, V>.

질문을 명확히하기 위해 메소드의 서명은

V get(Object key)

대신에

V get(K key)

왜 그런지 궁금 remove, containsKey, containsValue합니다.


3
: 컬렉션에 대한 비슷한 질문 stackoverflow.com/questions/104799/...
AlikElzin-kilaka


1
놀랄 만한. 나는 20 년 이상 동안 Java를 사용하고 있으며 오늘이 문제를 알고 있습니다.
GhostCat

답변:


260

다른 사람들이 언급했듯이, get()검색하는 항목의 키가 전달하는 객체와 동일한 유형일 필요가 없기 때문에, 등이 일반적인 이유는 아닙니다 get(). 방법의 스펙은 그것들이 동일 할 필요가있다. 이는 equals()메소드가 오브젝트와 동일한 유형이 아니라 오브젝트를 매개 변수로 사용 하는 방법 에서 비롯 됩니다.

많은 클래스가 equals()자신의 객체가 자신의 클래스의 객체와 같을 수 있도록 정의한 것은 일반적으로 사실이지만 , Java에는 그렇지 않은 곳이 많이 있습니다. 예를 들어,에 대한 사양은 List.equals()두 List 객체가 모두 List이고 서로 다른 구현 인 경우에도 동일한 내용을 갖는 경우 동일하다고 말합니다 List. 그래서 방법의 사양에 따라,이 문제의 예를 다시 오는 것은을 가질 수 있습니다 Map<ArrayList, Something>나를 호출 할 수 get()로모그래퍼 LinkedList인수로하고,이 같은 내용 목록입니다 키를 검색합니다. get()일반적이고 인수 유형이 제한되어 있으면 불가능합니다 .


28
그렇다면 왜 V Get(K k)C #입니까?

134
문제는 전화를 원한다면 m.get(linkedList)m유형을 Map<List,Something>? 로 정의하지 않았 습니까? 인터페이스를 얻기 위해 유형을 m.get(HappensToBeEqual)변경하지 않고 호출하는 것이 적합한 유스 케이스를 생각할 수 없습니다 Map.
Elazar Leibovich

58
와우, 심각한 디자인 결함. 실수로 컴파일러 경고가 표시되지 않습니다. 나는 Elazar에 동의합니다. 이것이 정말로 유용하고 자주 의심되는 getByEquals (오브젝트 키)는 더 합리적으로 들립니다 ...
mmm

37
이 결정은 실용성이 아니라 이론적 순도에 기초하여 이루어진 것처럼 보입니다. 대부분의 사용법에서 개발자는 템플릿 유형에 의해 제한되는 주장을 보는 것보다는 newacct가 대답에서 언급 한 것과 같은 최첨단 사례를 무제한으로 지원하는 것보다 훨씬 낫습니다. 템플릿 화되지 않은 서명을 남겨두면 해결하는 것보다 더 많은 문제가 발생합니다.
Sam Goldberg

14
@newacct : "완벽한 유형 안전"은 런타임에 예기치 않게 실패 할 수있는 구문에 대한 강력한 주장입니다. 그와 함께 작동하는 해시 맵으로 시야를 좁히지 마십시오. TreeMap잘못된 유형의 객체를 get메소드에 전달하면 실패 하지만 때때로 (예 :지도가 비어있는 경우) 전달 될 수 있습니다. 그리고 더 악화하는 경우 공급 방법 (일반 서명이!) 어떤 선택하지 않은 경고없이 잘못된 유형의 인수를 호출 할 수 있습니다. 이것은 이다 깨진 행동. Comparatorcompare
Holger

105

Google의 멋진 Java 코더 인 Kevin Bourrillion 은 얼마 전에 블로그 게시물 에이 문제에 대해 정확히 썼습니다 (확실히 Set대신 Map). 가장 관련성이 높은 문장 :

통일적으로, Java Collections Framework (및 Google Collections Library)의 메소드는 콜렉션이 손상되는 것을 방지해야하는 경우를 제외하고는 매개 변수 유형을 제한하지 않습니다.

.NET은 올바른 키 유형을 요구하는 것이 좋을 것 같습니다.하지만 블로그 게시물의 추론을 따르는 것이 좋습니다. .NET에 대해 언급했지만 .NET에서 문제가되지 않는 이유 중 일부는 .NET에서 차이가 더 제한적이므로 더 문제 가 있음을 설명하는 것이 좋습니다.)


4
Apocalisp : 사실이 아닙니다. 상황은 여전히 ​​동일합니다.
Kevin Bourrillion 2009

9
@ user102008 아니요, 게시물이 잘못되었습니다. 더 불구 Integer하고이 Double서로 동일 할 수 없다, 그것은 여부를 물어 공정한 질문 아직 Set<? extends Number>값을 포함은 new Integer(5).
Kevin Bourrillion 2016 년

33
에 회원 자격을 한 번도 확인하고 싶지 않았습니다 Set<? extends Foo>. 지도의 키 유형을 매우 자주 변경 한 후 컴파일러가 코드를 업데이트해야하는 모든 위치를 찾을 수 없다는 것에 좌절했습니다. 나는 이것이 올바른 트레이드 오프라고 확신하지 못한다.
Porculus

4
@ EarthEngine : 항상 고장났습니다. 요점은 코드가 깨졌지만 컴파일러가 그것을 잡을 수 없다는 것입니다.
Jon Skeet

1
그리고 그것은 여전히 ​​깨졌고 방금 버그를 일으켰습니다 ... 멋진 대답.
GhostCat

28

계약은 다음과 같이 표현됩니다.

보다 공식적으로,이 맵에 키 k에서 값 v 로의 매핑이 포함되어 있으면 (key == null? k == null : key.equals (k) )이 메소드는 v를 반환합니다. 그렇지 않으면 null을 반환합니다. 이러한 매핑은 최대 하나만있을 수 있습니다.

(내 강조)

따라서 성공적인 키 조회는 입력 키의 동등성 방법 구현에 따라 다릅니다. 반드시 k의 클래스에 의존 하지는 않습니다 .


4
또한에 의존합니다 hashCode(). hashCode ()의 적절한 구현이 없다면, equals()이 경우 에는 훌륭하게 구현 되는 것이 쓸모가 없습니다.
rudolfson

5
equals () 및 hashCode ()가 올바르게 구현되는 한 전체 키를 다시 만드는 것이 실용적이지 않으면 원칙적으로 키에 가벼운 프록시를 사용할 수 있다고 생각합니다.
Bill Michell

5
@rudolfson : 내가 아는 한, 올바른 버킷을 찾기 위해 HashMap 만 해시 코드에 의존합니다. 예를 들어 TreeMap은 이진 검색 트리를 사용하며 hashCode ()를 신경 쓰지 않습니다.
Rob

4
엄밀히 말하면 접촉을 만족시키기 위해 get()유형의 인수를 취할 필요는 없습니다 Object. get 메소드가 키 유형으로 제한되었다고 가정하십시오 K. 계약은 여전히 ​​유효합니다. 물론, 컴파일 시간 유형이 서브 클래스가 아닌 곳에서의 사용 K은 이제 컴파일에 실패하지만 계약이 무효화되지는 않습니다. 계약은 코드가 컴파일되면 어떻게되는지 암시 적으로 논의하기 때문입니다.
BeeOnRope 2016 년

16

그것은 Postel 's Law의 적용이다. "당신이하는 일에 보수적이며, 다른 사람들로부터 받아 들여지는 것에있어 자유 롭다."

유형에 관계없이 평등 검사를 수행 할 수 있습니다. 이 equals메소드는 Object클래스 에 정의 Object되어 있으며 매개 변수로 허용합니다 . 따라서 키 동등성 및 키 동등성 기반 조작이 모든 Object유형 을 승인하는 것이 좋습니다.

맵이 키 값을 반환하면 type 매개 변수를 사용하여 가능한 한 많은 유형 정보를 보존합니다.


4
그렇다면 왜 V Get(K k)C #입니까?

1
그것은이다 V Get(K k)그것은 또한 의미가 있기 때문에 C #으로. Java와 .NET 접근법의 차이점은 실제로 비 일치 항목을 차단하는 사람입니다. C #에서는 컴파일러이고 Java에서는 컬렉션입니다. 나는 한 동안 .NET의 일관성 컬렉션 클래스 번에 대한 분노 만 Get()하고 Remove()만 확실히 일치 유형을 받아들이는 실수로 잘못된 값을 전달하지 못하도록합니다.
Wormbo

26
Postel 's Law의 잘못된 적용입니다. 당신이 다른 사람들로부터 받아들이는 것에 자유를 주지만 너무 자유주의는 아닙니다. 이 바보 API는 "컬렉션에 없음"과 "정적 타이핑 실수를했다"의 차이점을 알 수 없음을 의미합니다. get : K-> boolean을 사용하면 수천 시간의 프로그래머 시간을 잃을 수 있습니다.
판사 정신

1
물론 그렇습니다 contains : K -> boolean.
판사 정신


13

나는 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 인스턴스 만 포함 된 경우에만 성공하지만 다음과 같습니다.

  • 들어오는 컬렉션의 정적 형식은 호출자가 전달되는 컬렉션의 정확한 형식을 알지 못하거나 컬렉션이 Collection <S>이고 S가 E의 하위 유형이기 때문에 다를 수 있습니다.
  • 다른 유형의 컬렉션으로 containsAll ()을 호출하는 것이 합법적입니다. 이 루틴은 작동해야하며 false를 반환합니다. "

2
왜 안돼 containsAll( Collection< ? extends E > c )?
판사 정신

1
허용 할 필요가 상기 @JudgeMental은, 비록 예를 들어 주어지지 containsAll로모그래퍼 A는 수퍼 의 . 이 경우 허용되지 않습니다 . 또한, 같은 되는 명시 적 실시 예에서 설명한, 그것은 (반환 값은 다음으로되는 다른 유형의 집합을 전달할 합법 ). Collection<S>SEcontainsAll( Collection< ? extends E > c )false
davmac 2016 년

E의 수퍼 타입 ​​콜렉션에 containsAll을 허용 할 필요는 없습니다. 버그를 방지하기 위해 정적 유형 검사로 호출을 허용하지 않아야한다고 주장합니다. 그것은 어리석은 계약이므로 원래 질문의 요점이라고 생각합니다.
판사 정신

6

그 이유는 밀폐에 의해 결정된다는 것이다 equalshashCode그 위에있는 방법 Object및 가지고 두 Object파라미터. 이것은 Java 표준 라이브러리의 초기 설계 결함이었습니다. Java 유형 시스템의 제한 사항과 함께 equals 및 hashCode에 의존하는 모든 것을 강제로 가져옵니다 Object.

자바 형태 보증 된 해시 테이블과 평등을 할 수있는 유일한 방법은 삼가고하는 것입니다 Object.equalsObject.hashCode및 일반 대체를 사용합니다. 기능 자바는 바로 이러한 목적을 위해 형 클래스와 함께 제공 : Hash<A>Equal<A>. 래퍼 HashMap<K, V>소요가 제공된다 Hash<K>Equal<K>생성자입니다. 따라서이 클래스 getcontains메소드는 유형의 일반 인수를 사용합니다 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

4
'Object'는 항상 K의 조상이므로 'key.hashCode ()'는 여전히 유효하기 때문에 이것 자체로는 'get'유형이 "V get (K key)"로 선언되는 것을 막지 않습니다.
finnw

1
그것을 막지는 않지만 설명한다고 생각합니다. 클래스 equality를 강제하기 위해 equals 메소드를 전환 한 경우, 해당 메소드의 메소드 프로토 타입이 호환되지 않을 때 맵에서 오브젝트를 찾기위한 기본 메커니즘이 equals () 및 hashmap ()을 사용한다고 사람들에게 말할 수 없었습니다.
cgp

5

적합성.

제네릭을 사용할 수 있기 전에 get (Object o) 만있었습니다.

그들이이 방법을 get (<K> o)으로 변경했다면 작업 코드를 다시 컴파일하기 위해 Java 사용자에게 대규모 코드 유지 관리를 강제했을 것입니다.

그들은 도입 된 한 추가 방법을 get_checked 말 (<K> O)과 온화한 전환 경로가 있었다, 그래서 이전의 get () 메소드를 중단합니다. 그러나 어떤 이유로 든 이것이 이루어지지 않았습니다. 현재 상황은 get () 인수와지도의 선언 된 키 유형 <K> 사이의 유형 호환성을 확인하기 위해 findBugs와 같은 도구를 설치해야한다는 것입니다.

.equals ()의 의미와 관련된 인수는 가짜입니다. (기술적으로는 정확하지만 여전히 가짜라고 생각합니다. o1과 o2에 공통 수퍼 클래스가없는 경우 올바른 마음의 어떤 ​​디자이너도 o1.equals (o2)를 적용하지 않습니다.)


4

한 가지 더 중요한 이유가 있는데, 기술적으로 맵을 깨뜨릴 수 없기 때문입니다.

Java는 다음과 같은 다형성 일반 구조를 가지고 <? extends SomeClass>있습니다. 이러한 참조는로 표시된 유형을 가리킬 수 있습니다 <AnySubclassOfSomeClass>. 그러나 다형성 제네릭은 해당 참조를 읽기 전용으로 만듭니다 . 컴파일러를 사용하면 제네릭 형식을 반환 형식의 메서드 (예 : 단순 게터)로만 사용할 수 있지만 제네릭 형식이 인수 인 메서드 (보통 세터)는 사용하지 않습니다. 즉, 작성 Map<? extends KeyType, ValueType>하면 컴파일러에서 메소드 호출을 허용하지 않으므로 get(<? extends KeyType>)맵이 쓸모가 없습니다. 유일한 해결책은이 방법을 일반화하지 않는 것 get(Object)입니다.


왜 set 메소드가 강력하게 타이핑됩니까?
Sentenza

'put'을 의미하는 경우 : put () 메소드는 맵을 변경하며 <?와 같은 제네릭으로 사용할 수 없습니다. SomeClass>를 확장합니다. 호출하면 컴파일 예외가 발생합니다. 이러한지도는 "읽기 전용"입니다
Owheee

1

이전 버전과의 호환성이라고 생각합니다. Map(또는 HashMap) 여전히 지원해야합니다 get(Object).


13
그러나 동일한 put유형을 사용할 수 있습니다 (일반 유형을 제한 함). 원시 유형을 사용하여 이전 버전과의 호환성을 얻습니다. 제네릭은 "선택"입니다.
Thilo

개인적으로,이 디자인 결정의 가장 큰 이유는 이전 버전과의 호환성이라고 생각합니다.
geekdenz

1

나는 이것을보고 그들이 왜 이런 식으로했는지 생각했습니다. 기존 답변 중 왜 새로운 일반 인터페이스가 키에 적합한 유형 만 허용하도록 할 수 없었는지 설명하지는 않습니다. 실제 이유는 제네릭을 도입했지만 새 인터페이스를 만들지 않았기 때문입니다. Map 인터페이스는 일반 및 비 제네릭 버전의 역할을하는 기존의 비 제네릭 맵과 동일합니다. 이 방법으로 일반이 아닌 맵을 허용하는 메소드가 있으면 전달할 수 있으며 Map<String, Customer>여전히 작동합니다. 동시에 get 계약은 Object를 수락하므로 새 인터페이스도이 계약을 지원해야합니다.

제 생각에 그들은 새로운 인터페이스를 추가하고 기존 컬렉션에 모두 구현해야했지만 get 메소드의 디자인이 더 나쁘더라도 호환 가능한 인터페이스를 선호하기로 결정했습니다. 컬렉션 자체는 기존의 메서드와 호환되며 인터페이스만으로는 불가능합니다.


0

우리는 지금 큰 리팩토링을하고 있으며 이전 유형의 get ()을 놓치지 않았는지 확인하기 위해 강력하게 유형이 지정된 get ()이 누락되었습니다.

그러나 컴파일 시간 검사를위한 해결 방법 / 추악한 트릭을 발견했습니다 : 강력한 형식의 get, containsKey, remove ...를 사용하여 Map 인터페이스를 만들고 프로젝트의 java.util 패키지에 넣으십시오.

get ()을 호출하는 것만으로 컴파일 오류가 발생합니다. ... 잘못된 유형으로, 다른 모든 것은 컴파일러에 적합합니다 (적어도 일식 케플러 내부).

빌드 확인 후이 인터페이스를 삭제하는 것을 잊지 마십시오. 런타임에서 원하는 것이 아닙니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.