Collection.remove (Object o)가 일반 이 아닌 이유는 무엇 입니까?
Collection<E>
할 수있는 것 같습니다boolean remove(E o);
그런 다음 실수 Set<String>
로 각 개별 문자열 대신 (예 :)을 제거하려고하면 Collection<String>
나중에 디버깅 문제 대신 컴파일 시간 오류가 발생합니다.
Collection.remove (Object o)가 일반 이 아닌 이유는 무엇 입니까?
Collection<E>
할 수있는 것 같습니다boolean remove(E o);
그런 다음 실수 Set<String>
로 각 개별 문자열 대신 (예 :)을 제거하려고하면 Collection<String>
나중에 디버깅 문제 대신 컴파일 시간 오류가 발생합니다.
답변:
Josh Bloch와 Bill Pugh는 Java Puzzlers IV : Phantom Reference Menace, Clone의 Attack 및 Revenge of the Shift 에서이 문제를 언급합니다 .
조쉬 블로흐 (Josh Bloch)는 (6:41) 맵의 get 메소드를 생성하고 메소드 및 기타를 제거하려고 시도했지만 "단순히 작동하지 않았다"고 말합니다.
콜렉션의 일반 유형 만 매개 변수 유형으로 허용하는 경우 생성 할 수없는 합리적인 프로그램이 너무 많습니다. 그에게 주어진 예는의 교차로 List
의 Number
의와
List
의 Long
의.
remove()
( Map
및뿐만 아니라 Collection
)은 모든 유형의 객체를에 전달할 수 있어야하기 때문에 일반적이지 않습니다 remove()
. 제거 된 객체는 전달한 객체와 동일한 유형일 필요는 없습니다 remove()
. 단지 동일해야합니다. 의 사양에서 remove()
, remove(o)
개체 제거 e
등 (o==null ? e==null : o.equals(e))
입니다 true
. 필요 아무것도 없다는 것을 유의 o
과 e
같은 유형이 될 수 있습니다. 이는 equals()
메소드가 Object
오브젝트와 동일한 유형이 아니라 as 매개 변수를 사용 한다는 사실에서 비롯 됩니다.
그러나 많은 클래스가 equals()
자신의 객체가 자신의 클래스의 객체와 같을 수 있도록 정의한 것이 일반적 일 수도 있지만, 반드시 그런 것은 아닙니다. 예를 들어,에 대한 사양은 List.equals()
두 List 객체가 모두 List이고 서로 다른 구현 인 경우에도 동일한 내용을 갖는 경우 동일하다고 말합니다 List
. 이 문제의 예를 다시오고 그래서,을 가질 수 있습니다 Map<ArrayList, Something>
나를 호출 할 수 remove()
로모그래퍼 LinkedList
인수로하고,이 같은 내용 목록입니다 키를 제거해야합니다. remove()
일반적이고 인수 유형이 제한되어 있으면 불가능합니다 .
equals()
방법을 일반화하지 않았는지를 묻는 것에 만 적합합니다 . 이 "자유 주의자"접근 방식 대신에 유형 안전에 대한 더 많은 이점을 볼 수있었습니다. 현재 구현 된 대부분의 경우는 remove()
메소드가 가져 오는 이 유연성에 대한 기쁨보다는 코드에 들어가는 버그에 대한 것이라고 생각 합니다.
equals()
방법을 일반화"란 무슨 뜻 입니까?
T
클래스에서 형식 매개 변수로 선언해야하며 형식 매개 변수 Object
가 없습니다. "선언 클래스"를 참조하는 형식을 가질 수있는 방법은 없습니다.
Equality<T>
과 함께 equals(T other)
. 그런 다음 당신은 할 수 remove(Equality<T> o)
와 o
서로 비교 될 수 그냥 객체입니다 T
.
type 매개 변수가 와일드 카드 인 경우 일반 remove 메소드를 사용할 수 없습니다.
Map의 get (Object) 메소드를 사용 하여이 질문에 부딪힌 것을 기억합니다. 이 경우 get 메소드는 일반적이지 않지만, 첫 번째 유형 매개 변수와 동일한 유형의 오브젝트가 전달 될 것으로 예상해야합니다. 와일드 카드를 첫 번째 유형 매개 변수로 사용하여지도를 전달하는 경우 해당 인수가 일반적인 경우 해당 메소드를 사용하여지도에서 요소를 가져올 수있는 방법이 없다는 것을 깨달았습니다. 컴파일러가 형식이 올바른지 보장 할 수 없기 때문에 와일드 카드 인수를 실제로 만족시킬 수 없습니다. add가 일반적인 이유는 컬렉션에 형식을 추가하기 전에 형식이 올바른지 확인해야하기 때문입니다. 그러나 객체를 제거 할 때 유형이 올바르지 않으면 아무 것도 일치하지 않습니다.
아마 잘 설명하지 않았지만 그것은 나에게 논리적으로 보입니다.
다른 답변 외에도 메소드 Object
가 술어를 허용 해야하는 또 다른 이유 가 있습니다. 다음 샘플을 고려하십시오.
class Person {
public String name;
// override equals()
}
class Employee extends Person {
public String company;
// override equals()
}
class Developer extends Employee {
public int yearsOfExperience;
// override equals()
}
class Test {
public static void main(String[] args) {
Collection<? extends Person> people = new ArrayList<Employee>();
// ...
// to remove the first employee with a specific name:
people.remove(new Person(someName1));
// to remove the first developer that matches some criteria:
people.remove(new Developer(someName2, someCompany, 10));
// to remove the first employee who is either
// a developer or an employee of someCompany:
people.remove(new Object() {
public boolean equals(Object employee) {
return employee instanceof Developer
|| ((Employee) employee).company.equals(someCompany);
}});
}
}
요점은 remove
메소드 에 전달되는 객체가 메소드 정의를 담당한다는 equals
것입니다. 이렇게하면 술어 작성이 매우 간단 해집니다.
yourObject.equals(developer)
Collections API에 설명 된 대로 다음 과 같이 구현됩니다 . java.sun.com/javase/6/docs/api/java/util/…
equals
메소드 의 계약 , 즉 대칭을 위반하므로 악용 됩니다. 객체가 equals / hashCode 사양을 충족하는 한 remove 메서드는 해당 사양에만 바인딩되므로 모든 구현에서 다른 방법으로 비교할 수 있습니다. 또한 술어 객체는 .hashCode()
메소드를 구현하지 않으므로 (일관되게 동일하게 구현할 수 없음) 제거 호출은 Hash 기반 컬렉션 (HashSet 또는 HashMap.keys ())에서 절대 작동하지 않습니다. 그것이 ArrayList와 함께 작동한다는 것은 순수한 행운입니다.
Collection.remove
하고 계약을 위반하지 않고 (주문이 동일하면) 완전히 구현 합니다. 그리고 equals 호출을 돌리는 다양한 ArrayList (또는 AbstractCollection, 생각합니다)는 여전히 계약을 올바르게 구현합니다. 계약을 위반했기 때문에 의도 한대로 작동하지 않으면 오류 equals
입니다.
하나의 모음이 가정 Cat
및 유형의 일부 객체 참조 Animal
, Cat
, SiameseCat
,와 Dog
. 컬렉션에 참조 Cat
또는 SiameseCat
참조로 참조 된 개체가 포함되어 있는지 묻는 것이 합리적입니다. 참조에 의해 Animal
참조 된 객체가 포함되어 있는지 묻는 것은 어리석은 것처럼 보일 수 있지만 여전히 완벽하게 합리적입니다. 문제의 객체는 결국이며 Cat
컬렉션에 나타날 수 있습니다.
또한 객체가가 아닌 다른 객체 일지라도 Cat
컬렉션에 표시되는지 여부를 말하는 데 아무런 문제가 없습니다. 간단히 "아니요, 그렇지 않습니다"라고 대답하십시오. 어떤 유형의 "조회 스타일"컬렉션은 의미있는 수퍼 타입의 참조를 수용하고 개체가 컬렉션 내에 존재하는지 여부를 결정할 수 있어야합니다. 전달 된 객체 참조가 관련이없는 유형 인 경우 컬렉션에이를 포함 할 수있는 방법이 없으므로 쿼리는 의미가 없습니다 (항상 "아니오"로 응답 함). 그럼에도 불구하고 매개 변수를 하위 유형 또는 수퍼 유형으로 제한하는 방법은 없으므로 컬렉션의 유형과 관련이없는 개체에 대해 모든 유형을 수락하고 "아니오"라고 대답하는 것이 가장 실용적입니다.
Comparable
비교할 수있는 유형에 대한 매개 변수화 방법과 유사 ). 그런 다음 사람들이 관련이없는 유형의 것을 전달하는 것은 합리적이지 않습니다.
A
와 B
하나 개의 유형은,와 X
그리고 Y
서로 같은 것을 A
> B
및 X
> Y
. 하나 A
> Y
와 Y
< A
나 X
> B
와 B
< X
. 이러한 관계는 크기 비교가 두 유형에 대해 알고있는 경우에만 존재할 수 있습니다. 반대로, 객체의 동등성 비교 방법은 문제의 다른 유형에 대해 아무것도 알 필요없이 단순히 다른 유형의 것과 다른 것으로 선언 할 수 있습니다. 유형의 객체는 Cat
그것이 어떤 것인지 모를 수도 있습니다.
FordMustang
이지만, 그러한 객체와 같은지 여부를 말하는 데 어려움이 없을 것입니다 (답은 분명히 "아니오"임).
타협이었습니다. 두 방법 모두 장점이 있습니다.
remove(Object o)
remove(E e)
반바지 목록에서 정수를 실수로 제거하는 것과 같이 컴파일 타임에 미묘한 버그를 감지하여 대부분의 프로그램이 원하는 것에 더 많은 유형 안전을 제공합니다.이전 버전과의 호환성은 Java API를 발전시킬 때 항상 주요 목표 였으므로 기존 코드를보다 쉽게 생성 할 수 있도록 remove (Object o)를 선택했습니다. 이전 버전과의 호환성에 문제가 없다면 디자이너가 remove (E e)를 선택했을 것입니다.
제네릭이 아닌 컬렉션을 사용하는 기존 코드가 여전히 컴파일되고 여전히 동일한 동작을 갖도록 Remove는 일반적인 방법이 아닙니다.
세부 사항 은 http://www.ibm.com/developerworks/java/library/j-jtp01255.html 을 참조하십시오.
편집 : 주석자가 add 메소드가 일반적인 이유를 묻습니다. [... 내 설명을 제거했습니다 ...] 두 번째 논평자는 firebird84의 질문에 대해 나보다 훨씬 잘 대답했습니다.
또 다른 이유는 인터페이스 때문입니다. 다음은이를 보여주는 예입니다.
public interface A {}
public interface B {}
public class MyClass implements A, B {}
public static void main(String[] args) {
Collection<A> collection = new ArrayList<>();
MyClass item = new MyClass();
collection.add(item); // works fine
B b = item; // valid
collection.remove(b); /* It works because the remove method accepts an Object. If it was generic, this would not work */
}
remove()
공변량이 아니기 때문에 멀리 갈 수있는 것을 보여주고 있습니다 . 그러나 문제는 이것이 허용 되어야 하는지 여부 입니다. ArrayList#remove()
참조 평등이 아닌 가치 평등의 방식으로 작동합니다. 왜 a와 B
같을 것으로 기대 A
하십니까? 귀하의 예에서는 가능 하지만 예상과는 다릅니다. 차라리 당신이 MyClass
여기 에 인수 를 제공하는 것을 봅니다 .
기존 (Java 5 이전) 코드를 손상시킬 수 있기 때문입니다. 예를 들어
Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);
이제 위의 코드가 잘못되었다고 말할 수도 있지만 o 이기종 객체 집합 (예 : 문자열, 숫자, 객체 등)에서 비롯된 것으로 가정합니다. 제거는 문자열이 아닌 문자열을 동일하지 않기 때문에 무시하기 때문에 모든 일치 항목을 제거하려고합니다. 그러나 제거하면 (String o) 더 이상 작동하지 않습니다.