Java Collections가 일반적인 메소드를 제거하지 않는 이유는 무엇입니까?


144

Collection.remove (Object o)가 일반 이 아닌 이유는 무엇 입니까?

Collection<E>할 수있는 것 같습니다boolean remove(E o);

그런 다음 실수 Set<String>로 각 개별 문자열 대신 (예 :)을 제거하려고하면 Collection<String>나중에 디버깅 문제 대신 컴파일 시간 오류가 발생합니다.


13
오토 박싱과 결합 할 때 실제로 물릴 수 있습니다. List에서 무언가를 제거하려고 시도하고 int 대신 Integer를 전달하면 remove (Object) 메소드를 호출합니다.
ScArcher2

2
:지도에 대한 비슷한 질문 stackoverflow.com/questions/857420/...
AlikElzin-kilaka

답변:


73

Josh Bloch와 Bill Pugh는 Java Puzzlers IV : Phantom Reference Menace, Clone의 Attack 및 Revenge of the Shift 에서이 문제를 언급합니다 .

조쉬 블로흐 (Josh Bloch)는 (6:41) 맵의 get 메소드를 생성하고 메소드 및 기타를 제거하려고 시도했지만 "단순히 작동하지 않았다"고 말합니다.

콜렉션의 일반 유형 만 매개 변수 유형으로 허용하는 경우 생성 할 수없는 합리적인 프로그램이 너무 많습니다. 그에게 주어진 예는의 교차로 ListNumber의와 ListLong의.


6
add ()가 유형이 지정된 매개 변수를 사용할 수 있지만 remove ()는 여전히 내 이해력을 뛰어 넘을 수 없습니다. Josh Bloch는 컬렉션 질문에 대한 확실한 참조가 될 것입니다. 비슷한 컬렉션 구현을 시도하지 않고 얻을 수있는 모든 것일 수도 있습니다. :( 감사합니다.
Chris Mazzola

2
Chris-Java Generics Tutorial PDF를 읽고 이유를 설명합니다.
JeeBee

42
실제로, 그것은 매우 간단합니다! add ()가 잘못된 객체를 취하면 컬렉션이 손상됩니다. 그것은 의도하지 않은 것들을 포함 할 것입니다! remove () 또는 contains ()의 경우에는 해당되지 않습니다.
Kevin Bourrillion 2009

12
또한 컬렉션의 실제 손상 만 방지하기 위해 형식 매개 변수를 사용하는 기본 규칙은 전체 라이브러리에서 일관되게 준수됩니다.
Kevin Bourrillion 2009

3
@KevinBourrillion : "실제 손상"규칙이 존재한다는 사실을 깨닫지 못한 채 몇 년 동안 (Java와 C # 모두에서) 제네릭 작업을 해왔지만 이제는 100 % 완벽한 의미를 갖습니다. 물고기 고마워 !!! 지금을 제외하고는 돌아가서 구현을 살펴보고 일부 메소드가 퇴화 될 수 있는지 확인해야한다고 생각합니다. 한숨.
corlettk

74

remove()( Map및뿐만 아니라 Collection)은 모든 유형의 객체를에 전달할 수 있어야하기 때문에 일반적이지 않습니다 remove(). 제거 된 객체는 전달한 객체와 동일한 유형일 필요는 없습니다 remove(). 단지 동일해야합니다. 의 사양에서 remove(), remove(o)개체 제거 e(o==null ? e==null : o.equals(e))입니다 true. 필요 아무것도 없다는 것을 유의 oe같은 유형이 될 수 있습니다. 이는 equals()메소드가 Object오브젝트와 동일한 유형이 아니라 as 매개 변수를 사용 한다는 사실에서 비롯 됩니다.

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


1
그러나 Map을 Map <List, Something> (ArrayList 대신)로 정의했다면 LinkedList를 사용하여 제거 할 수 있었을 것입니다. 이 답변이 잘못되었다고 생각합니다.
AlikElzin-kilaka

3
대답은 정확하지만 불완전한 것으로 보입니다. 그것은 왜 도대체가 equals()방법을 일반화하지 않았는지를 묻는 것에 만 적합합니다 . 이 "자유 주의자"접근 방식 대신에 유형 안전에 대한 더 많은 이점을 볼 수있었습니다. 현재 구현 된 대부분의 경우는 remove()메소드가 가져 오는 이 유연성에 대한 기쁨보다는 코드에 들어가는 버그에 대한 것이라고 생각 합니다.
kellogs

2
@kellogs : " equals()방법을 일반화"란 무슨 뜻 입니까?
newacct February

5
@MattBall : "여기서 T는 선언 클래스입니다."그러나 Java에는 그러한 구문이 없습니다. T클래스에서 형식 매개 변수로 선언해야하며 형식 매개 변수 Object가 없습니다. "선언 클래스"를 참조하는 형식을 가질 수있는 방법은 없습니다.
newacct

3
나는 kellogs 평등 경우 일반적인 인터페이스를 무엇인지 말하고 생각 Equality<T>과 함께 equals(T other). 그런 다음 당신은 할 수 remove(Equality<T> o)o서로 비교 될 수 그냥 객체입니다 T.
weston

11

type 매개 변수가 와일드 카드 인 경우 일반 remove 메소드를 사용할 수 없습니다.

Map의 get (Object) 메소드를 사용 하여이 질문에 부딪힌 것을 기억합니다. 이 경우 get 메소드는 일반적이지 않지만, 첫 번째 유형 매개 변수와 동일한 유형의 오브젝트가 전달 될 것으로 예상해야합니다. 와일드 카드를 첫 번째 유형 매개 변수로 사용하여지도를 전달하는 경우 해당 인수가 일반적인 경우 해당 메소드를 사용하여지도에서 요소를 가져올 수있는 방법이 없다는 것을 깨달았습니다. 컴파일러가 형식이 올바른지 보장 할 수 없기 때문에 와일드 카드 인수를 실제로 만족시킬 수 없습니다. add가 일반적인 이유는 컬렉션에 형식을 추가하기 전에 형식이 올바른지 확인해야하기 때문입니다. 그러나 객체를 제거 할 때 유형이 올바르지 않으면 아무 것도 일치하지 않습니다.

아마 잘 설명하지 않았지만 그것은 나에게 논리적으로 보입니다.


1
이것에 대해 좀 더 자세히 설명해 주시겠습니까?
Thomas Owens

6

다른 답변 외에도 메소드 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것입니다. 이렇게하면 술어 작성이 매우 간단 해집니다.


투자자? (필러 필러 필러)
Matt R

3
이 목록은 yourObject.equals(developer)Collections API에 설명 된 대로 다음 과 같이 구현됩니다 . java.sun.com/javase/6/docs/api/java/util/…
Hosam Aly

13
이것은 나에게 남용처럼 보인다
RAY

7
술어 객체가 equals메소드 의 계약 , 즉 대칭을 위반하므로 악용 됩니다. 객체가 equals / hashCode 사양을 충족하는 한 remove 메서드는 해당 사양에만 바인딩되므로 모든 구현에서 다른 방법으로 비교할 수 있습니다. 또한 술어 객체는 .hashCode()메소드를 구현하지 않으므로 (일관되게 동일하게 구현할 수 없음) 제거 호출은 Hash 기반 컬렉션 (HashSet 또는 HashMap.keys ())에서 절대 작동하지 않습니다. 그것이 ArrayList와 함께 작동한다는 것은 순수한 행운입니다.
Paŭlo Ebermann

3
(나는 일반적인 유형의 질문에 대해 이야기하고 있지 않습니다-이것은 전에 제기되었습니다-여기에 술어에 대한 equals 만 사용하십시오.) 물론 HashMap과 HashSet은 해시 코드를 확인하고 TreeSet / Map은 요소의 순서를 사용합니다. . 그럼에도 불구 Collection.remove하고 계약을 위반하지 않고 (주문이 동일하면) 완전히 구현 합니다. 그리고 equals 호출을 돌리는 다양한 ArrayList (또는 AbstractCollection, 생각합니다)는 여전히 계약을 올바르게 구현합니다. 계약을 위반했기 때문에 의도 한대로 작동하지 않으면 오류 equals입니다.
Paŭlo Ebermann

5

하나의 모음이 가정 Cat및 유형의 일부 객체 참조 Animal, Cat, SiameseCat,와 Dog. 컬렉션에 참조 Cat또는 SiameseCat참조로 참조 된 개체가 포함되어 있는지 묻는 것이 합리적입니다. 참조에 의해 Animal참조 된 객체가 포함되어 있는지 묻는 것은 어리석은 것처럼 보일 수 있지만 여전히 완벽하게 합리적입니다. 문제의 객체는 결국이며 Cat컬렉션에 나타날 수 있습니다.

또한 객체가가 아닌 다른 객체 일지라도 Cat컬렉션에 표시되는지 여부를 말하는 데 아무런 문제가 없습니다. 간단히 "아니요, 그렇지 않습니다"라고 대답하십시오. 어떤 유형의 "조회 스타일"컬렉션은 의미있는 수퍼 타입의 참조를 수용하고 개체가 컬렉션 내에 존재하는지 여부를 결정할 수 있어야합니다. 전달 된 객체 참조가 관련이없는 유형 인 경우 컬렉션에이를 포함 할 수있는 방법이 없으므로 쿼리는 의미가 없습니다 (항상 "아니오"로 응답 함). 그럼에도 불구하고 매개 변수를 하위 유형 또는 수퍼 유형으로 제한하는 방법은 없으므로 컬렉션의 유형과 관련이없는 개체에 대해 모든 유형을 수락하고 "아니오"라고 대답하는 것이 가장 실용적입니다.


1
"전달 된 객체 참조가 관련이없는 유형 인 경우 컬렉션에이를 포함 할 수있는 방법이 없습니다." 그것과 동등한 것을 포함해야합니다. 다른 클래스의 객체는 동일 할 수 있습니다.
newacct

"피할 것 같지만 여전히 완벽하다" 어떤 유형의 객체가 다른 유형의 객체와 항상 동일한 지 검사 할 수없는 세계를 고려하십시오. 동일한 유형이 될 수있는 유형이 매개 변수화되어 있기 때문입니다 ( Comparable비교할 수있는 유형에 대한 매개 변수화 방법과 유사 ). 그런 다음 사람들이 관련이없는 유형의 것을 전달하는 것은 합리적이지 않습니다.
newacct

@newacct :가 기본 크기를 비교하고 동등 비교 차이이다 주어진 객체 AB하나 개의 유형은,와 X그리고 Y서로 같은 것을 A> BX> Y. 하나 A> YY< AX> BB< X. 이러한 관계는 크기 비교가 두 유형에 대해 알고있는 경우에만 존재할 수 있습니다. 반대로, 객체의 동등성 비교 방법은 문제의 다른 유형에 대해 아무것도 알 필요없이 단순히 다른 유형의 것과 다른 것으로 선언 할 수 있습니다. 유형의 객체는 Cat그것이 어떤 것인지 모를 수도 있습니다.
supercat

... 유형의 객체보다 "보다 큼"또는 "보다 작음" FordMustang이지만, 그러한 객체와 같은지 여부를 말하는 데 어려움이 없을 것입니다 (답은 분명히 "아니오"임).
supercat

4

필자는 항상 remove ()가 어떤 유형의 객체를 제공 할 지에 대한 이유가 없기 때문에 이것을 알았습니다. 어떤 객체에서든 equals ()를 호출 할 수 있기 때문에 해당 객체가 Collection에 포함 된 객체 중 하나인지 확인하는 것은 쉽습니다. add ()에서 유형을 확인하여 해당 유형의 객체 만 포함하는지 확인해야합니다.


0

타협이었습니다. 두 방법 모두 장점이 있습니다.

  • remove(Object o)
    • 더 유연합니다. 예를 들어 숫자 목록을 반복하여 long 목록에서 제거 할 수 있습니다.
    • 이 유연성을 사용하는 코드를보다 쉽게 ​​생성 할 수 있습니다
  • remove(E e) 반바지 목록에서 정수를 실수로 제거하는 것과 같이 컴파일 타임에 미묘한 버그를 감지하여 대부분의 프로그램이 원하는 것에 더 많은 유형 안전을 제공합니다.

이전 버전과의 호환성은 Java API를 발전시킬 때 항상 주요 목표 였으므로 기존 코드를보다 쉽게 ​​생성 할 수 있도록 remove (Object o)를 선택했습니다. 이전 버전과의 호환성에 문제가 없다면 디자이너가 remove (E e)를 선택했을 것입니다.


-1

제네릭이 아닌 컬렉션을 사용하는 기존 코드가 여전히 컴파일되고 여전히 동일한 동작을 갖도록 Remove는 일반적인 방법이 아닙니다.

세부 사항 은 http://www.ibm.com/developerworks/java/library/j-jtp01255.html 을 참조하십시오.

편집 : 주석자가 add 메소드가 일반적인 이유를 묻습니다. [... 내 설명을 제거했습니다 ...] 두 번째 논평자는 firebird84의 질문에 대해 나보다 훨씬 잘 대답했습니다.


2
그렇다면 add 메소드가 일반적인 이유는 무엇입니까?
밥 게티

@ firebird84 remove (Object)는 잘못된 유형의 객체를 무시하지만 remove (E)는 컴파일 오류를 유발합니다. 그것은 행동을 바꿀 것입니다.
노아

: shrug :-런타임 동작이 변경 되지 않습니다 . 컴파일 오류는 런타임 동작 이 아닙니다 . add 메소드의 "동작"은 이런 방식으로 변경됩니다.
Jason S

-2

또 다른 이유는 인터페이스 때문입니다. 다음은이를 보여주는 예입니다.

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여기 에 인수 를 제공하는 것을 봅니다 .
SEH

-3

기존 (Java 5 이전) 코드를 손상시킬 수 있기 때문입니다. 예를 들어

Set stringSet = new HashSet();
// do some stuff...
Object o = "foobar";
stringSet.remove(o);

이제 위의 코드가 잘못되었다고 말할 수도 있지만 o 이기종 객체 집합 (예 : 문자열, 숫자, 객체 등)에서 비롯된 것으로 가정합니다. 제거는 문자열이 아닌 문자열을 동일하지 않기 때문에 무시하기 때문에 모든 일치 항목을 제거하려고합니다. 그러나 제거하면 (String o) 더 이상 작동하지 않습니다.


4
List <String>을 인스턴스화하면 List.remove (someString); 만 호출 할 수 있습니다. 이전 버전과의 호환성을 지원해야하는 경우 원시 List-List <?>를 사용하면 list.remove (someObject)를 호출 할 수 있습니다.
Chris Mazzola

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