메소드는 다른 유형의 메소드와 동일한 지우기를 갖습니다.


381

같은 클래스에서 다음 두 가지 방법을 사용하는 것이 합법적이지 않은 이유는 무엇입니까?

class Test{
   void add(Set<Integer> ii){}
   void add(Set<String> ss){}
}

나는 compilation error

메소드 add (Set)는 Test 유형의 다른 메소드와 동일한 삭제 add (Set)를 갖습니다.

나는 그것을 해결할 수 있지만 javac가 왜 이것을 좋아하지 않는지 궁금했다.

많은 경우에,이 두 가지 방법의 논리는 매우 유사하고 단일로 대체 될 수 있음을 알 수 있습니다

public void add(Set<?> set){}

방법이지만 항상 그런 것은 아닙니다.

constructors인수를 취하는 두 개의 인수를 원한다면이 중 하나의 이름 만 변경할 수 없기 때문에 이것은 더욱 성가신 일입니다 constructors.


1
데이터 구조가 부족하고 여전히 더 많은 버전이 필요한 경우 어떻게해야합니까?
Omry Yadan

1
기본 버전에서 상속되는 사용자 정의 클래스를 만들 수 있습니다.
willlma

1
OP, 생성자 문제에 대한 해결책을 찾았습니까? 나는 두 종류를 받아 들여야 List하고 어떻게 처리해야할지 모르겠다.
Tomáš Zato-복원 모니카

9
Java로 작업 할 때 C #을 그리워합니다 ...
Svish

1
@ TomášZato, 생성자에 Boolean noopSignatureOverload라는 더미 매개 변수를 추가하여 문제를 해결했습니다.
Nthalk

답변:


357

이 규칙은 여전히 ​​원시 유형을 사용하는 레거시 코드에서 충돌을 피하기위한 것입니다.

다음 은 JLS에서 가져온 것이 허용되지 않는 이유를 보여줍니다 . 제네릭이 Java에 도입되기 전에 다음과 같은 코드를 작성했다고 가정 해보십시오.

class CollectionConverter {
  List toList(Collection c) {...}
}

다음과 같이 내 수업을 확장하십시오.

class Overrider extends CollectionConverter{
  List toList(Collection c) {...}
}

제네릭을 도입 한 후 라이브러리를 업데이트하기로 결정했습니다.

class CollectionConverter {
  <T> List<T> toList(Collection<T> c) {...}
}

업데이트 할 준비가되지 않았으므로 Overrider수업을 내버려 두십시오 . 이 toList()방법 을 올바르게 재정의하기 위해 언어 디자이너는 원시 유형이 생성 된 유형과 "재정의"한다고 결정했습니다. 즉, 메소드 서명이 더 이상 공식적으로 내 슈퍼 클래스의 서명과 같지 않지만 메소드는 여전히 재정의됩니다.

이제 시간이 흐르면서 수업을 업데이트 할 준비가되었습니다. 그러나 약간 망쳐 놓고 기존의 원시 toList()메소드 를 편집하는 대신 다음과 같은 새로운 메소드 를 추가 하십시오.

class Overrider extends CollectionConverter {
  @Override
  List toList(Collection c) {...}
  @Override
  <T> List<T> toList(Collection<T> c) {...}
}

원시 유형의 재정의 동등성으로 인해 두 메소드 모두 메소드를 대체 할 수있는 올바른 형식 toList(Collection<T>)입니다. 그러나 물론 컴파일러는 단일 방법을 해결해야합니다. 이러한 모호성을 제거하기 위해 클래스는 재정의와 동등한 여러 메소드, 즉 삭제 후 동일한 매개 변수 유형을 가진 여러 메소드를 가질 수 없습니다.

핵심은 이것이 원시 유형을 사용하여 이전 코드와의 호환성을 유지하도록 설계된 언어 규칙이라는 것입니다. 타입 파라미터의 소거에 의해 요구되는 제한은 아니다; 메서드 확인시 컴파일시 발생하기 때문에 메서드 식별자에 일반 형식을 추가하면 충분합니다.


3
훌륭한 답변과 예! 그러나 마지막 문장을 완전히 이해 한 경우 확실하지 않습니다 ( "메소드 확인은 컴파일 타임에 발생하므로 삭제하기 전에 유형 변환이 필요하지 않습니다"). 좀 더 자세히 설명해 주시겠습니까?
Jonas Eicher

2
맞는 말이다. 방금 템플릿 메서드에서 형식화에 대해 생각하는 데 시간이 걸렸지 만 컴파일러는 형식 지우기 전에 올바른 메서드가 선택되었는지 확인합니다. 아름다운. 레거시 코드 호환성 문제로 오염되지 않은 경우
Jonas Eicher

1
@daveloyall 아니요,에 대한 해당 옵션을 모릅니다 javac.
erickson

13
Java 오류가 발생하는 것은 아닙니다. 전혀 오류가 아니며 Java 작성자 만 다른 사람이 경고를 사용하는 경우 컴파일 할 수 있습니다. 그들 만이 모든 것을 더 잘 알고 있다고 생각합니다.
Tomáš Zato-복원 모니카

1
@ TomášZato 아니요, 그에 대한 깨끗한 해결 방법을 모르겠습니다. 더러워진 것을 원하면 List<?>and를 전달하여 enum유형을 나타냅니다. 형식 별 논리는 실제로 열거 형의 메서드에있을 수 있습니다. 또는 두 개의 다른 생성자가있는 하나의 클래스가 아닌 두 개의 다른 클래스를 만들 수도 있습니다. 공통적 인 논리는 두 유형이 모두 위임하는 수퍼 클래스 또는 헬퍼 오브젝트에 있습니다.
erickson

117

Java 제네릭은 유형 삭제를 사용합니다. 꺾쇠 괄호 ( <Integer><String>) 의 비트 가 제거되므로 동일한 서명 ( add(Set)오류에 표시되는) 이있는 두 가지 방법으로 끝납니다 . 런타임은 각 경우에 사용할 것을 알 수 없기 때문에 허용되지 않습니다.

Java가 제네릭 제네릭을 얻는다면, 당신은 이것을 할 수 있지만, 아마도 지금은 아닐 것입니다.


24
죄송하지만 귀하의 답변 (및 다른 답변)은 왜 여기에 오류가 있는지 설명하지 않습니다. 오버로드 확인은 컴파일 타임에 수행되며 컴파일러는 주소로 또는 메소드가 서명이 아닌 것으로 생각되는 바이트 코드에서 참조되는 모든 방법으로 링크 할 메소드를 결정하는 데 필요한 유형 정보를 가지고 있습니다. 심지어 일부 컴파일러는 이것을 컴파일 할 수 있다고 생각합니다.
Stilgar

5
@Stilgar 리플렉션을 통해 호출되거나 검사되는 메소드를 중지시키는 것은 무엇입니까? Class.getMethods ()에 의해 리턴 된 메소드리스트는 두 가지 동일한 메소드를 가지게되는데 이는 의미가 없습니다.
Adrian Mouat

5
리플렉션 정보에는 제네릭 작업에 필요한 메타 데이터가 포함될 수 있습니다. 이미 컴파일 된 라이브러리를 가져올 때 Java 컴파일러가 일반 메소드에 대해 어떻게 알 수 있습니까?
Stilgar

4
그런 다음 getMethod 메소드를 수정해야합니다. 예를 들어 오버로드 도입은 일반 오버로드를 지정하고 원래 메소드가 제네릭이 아닌 버전 만 리턴하도록하고 일반으로 주석이 달린 메소드는 리턴하지 않습니다. 물론 이것은 버전 1.5에서 수행되어야합니다. 지금 그렇게하면 메소드의 이전 버전과의 호환성이 손상됩니다. 나는 유형 삭제가이 행동을 지시하지 않는다는 나의 진술을지지한다. 제한된 자원으로 인해 충분한 작업을 얻지 못한 구현입니다.
Stilgar

1
이것은 정확한 대답은 아니지만 유용한 소설에서 문제를 신속하게 요약합니다. 메소드 서명이 너무 비슷하고 컴파일러가 차이점을 알지 못할 수 있으며 "해결되지 않은 컴파일 문제"가 발생합니다.
worc

46

Java Generics가 Type Erasure 로 구현 되었기 때문 입니다.

컴파일 타임에 메소드는 다음과 같이 변환됩니다.

메서드 확인은 컴파일 타임에 발생하며 형식 매개 변수를 고려하지 않습니다. ( 에릭슨의 답변 참조 )

void add(Set ii);
void add(Set ss);

두 메소드 모두 유형 매개 변수없이 동일한 서명을 가지므로 오류가 발생합니다.


21

문제는이다 Set<Integer>Set<String>실제로으로 처리됩니다 SetJVM을에서. 집합의 유형을 선택하면 (문자열 또는 정수) 컴파일러에서 사용하는 구문 설탕 일뿐입니다. JVM은 구별 할 수 없습니다 Set<String>Set<Integer>.


7
JVM 런타임 에는 각각을 구별하기위한 정보가 Set없지만, 메소드 분석은 컴파일시 발생하므로 필요한 정보가 사용 가능할 때 관련이 없습니다. 문제는 이러한 오버로드를 허용하는 것이 원시 유형에 대한 허용과 충돌하므로 Java 구문에서 불법이되었다는 것입니다.
erickson

@erickson 컴파일러가 호출 할 메소드를 알고 있더라도 바이트 코드에서와 같이 둘 다 동일하게 보이지 않습니다. (Ljava/util/Collection;)Ljava/util/List;작동하지 않으므로 메소드 호출을 지정하는 방법을 변경해야합니다 . 을 사용할 수는 (Ljava/util/Collection<String>;)Ljava/util/List<String>;있지만 호환되지 않는 변경 사항이며 지워진 유형이있는 곳에서는 해결할 수없는 문제가 발생합니다. 삭제를 완전히 삭제해야 할 수도 있지만 상당히 복잡합니다.
maaartinus

@maaartinus 예, 메소드 지정자를 변경해야한다는 데 동의합니다. 나는 그 시도를 포기하게 만든 해결할 수없는 문제 중 일부를 얻으려고 노력하고 있습니다.
erickson

7

다음과 같은 유형없이 단일 메소드를 정의하십시오. void add(Set ii){}

선택에 따라 메소드를 호출하는 동안 유형을 언급 할 수 있습니다. 모든 유형의 세트에서 작동합니다.


3

컴파일러가 Java 바이트 코드에서 Set (Integer)를 Set (Object)로 변환 할 수 있습니다. 이 경우 Set (Integer)은 구문 검사를 위해 컴파일 단계에서만 사용됩니다.


5
기술적으로는 원시 유형 Set입니다. 제네릭은 바이트 코드에 존재하지 않으며 캐스팅을위한 구문 설탕이며 컴파일 타임 유형 안전을 제공합니다.
Andrzej Doyle

1

나는 다음과 같은 것을 쓰려고 할 때 이것에 부딪쳤다. Continuable<T> callAsync(Callable<T> code) {....} 그리고 Continuable<Continuable<T>> callAsync(Callable<Continuable<T>> veryAsyncCode) {...} 그들은 컴파일러의 두 가지 정의가된다. Continuable<> callAsync(Callable<> veryAsyncCode) {...}

타입 삭제는 문자 그대로 제네릭에서 타입 인수 정보를 삭제하는 것을 의미합니다. 이것은 매우 성가신 일이지만 이것은 Java와 관련하여 제한 사항입니다. 생성자의 경우에는 할 수있는 일이 거의 없으며, 예를 들어 생성자에서 다른 매개 변수로 특수화 된 2 개의 새로운 하위 클래스가 있습니다. 또는 다른 이름으로 초기화 메소드를 사용하십시오 (가상 생성자?).

비슷한 작업 방법의 경우 이름을 바꾸는 것이 도움이 될 것입니다.

class Test{
   void addIntegers(Set<Integer> ii){}
   void addStrings(Set<String> ss){}
}

또는 좀 더 설명하는 이름으로, 오유 경우에 같은 자기 문서화 addNamesaddIndexes또는.

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