2012-06-09 3 views
3

Java Generics에 대한 질문이 있습니다. 아래의 코드에서는 인터페이스 B를 인터페이스 A를 구현해야하는 다른 유형으로 매개 변수화했습니다.Java Generics - 설명 필요

이 코드는 올바른 코드입니다. 질문 : 다음 list() 메서드 선언과 함께 작동하지 않는 이유는 무엇입니까?

private <X extends A, Y extends B<X>> List<Y> list() 

근무 코드 :

public interface A { 
} 
public interface B<T extends A> { 
} 
public class Test { 

    private static class AA implements A {} 
    private static class BB implements B<AA> {} 

    private <R extends A, X extends R, Y extends B<X>> List<Y> list() { 
     return null; 
    } 

    private void test() { 
     List<BB> l = list(); 
    } 
} 

편집 : 나는 코드를 재 작업했습니다. 이제 우리는 그것이 만들어 낼 수있는 소리에 의해 새가 확인되었습니다. 질문은 useless_t가 필요한 이유입니까?

public class Test { 
    public interface Sound { 
    } 
    public interface Bird<T extends Sound> { 
    } 

    private static class Quack implements Sound {} 
    private static class Duck implements Bird<Quack> {} 


    private <useless_t extends Sound, sound_t extends useless_t, bird_t extends Bird<sound_t>> List<bird_t> list() { 
      return null; 
    } 

    private void test() { 
      List<Duck> l = list(); 
    } 
} 
+2

달성하려는 목표는 무엇입니까? – krock

+0

책을 사서 제네릭을 뒤죽박죽. 당신은 완전히 그들의 요점을 놓칩니다. 나는 자바 헤드를 먼저 제안한다. –

+0

그 두 번째. 해당 목록 유형에서 전체 상속 계층 구조를 지정하려고합니다. 왜 그렇게하고 싶지는 모르겠지만 일을하는 가장 좋은 방법은 거의 아닙니다. 성취하려는 것에 대해 더 많은 맥락을 제시하면 더 나은 대답을 얻을 수 있습니다. – Jochen

답변

3

내 Eclipse IDE는 코드 예제를 그대로 컴파일하지 않습니다. 그러나 추가 유형 힌트가 주어지면 컴파일됩니다. 두 번째 예에서는, 또는 유형 매개 변수 useless_t없이, 다음 줄이 나를 위해 컴파일되지 않습니다 :

List<Duck> l = list(); 

을하지만 나를 위해 컴파일 않습니다 다음을 수행하십시오 useless_t

List<Duck> l = this.<Sound, Quack, Duck> list(); 

밖으로 고려, 다음 컴파일도 가능합니다 :

List<Duck> l = this.<Quack, Duck> list(); 

기본적으로 컴파일러는 형식 매개 변수를 올바르게 가져 오지 않아도됩니다. 유형을 명시 적으로 지정합니다.

UPDATE : 당신이 정말로 useless_t 차이를 만들어 추가, 프로그램을 통해 제공된 경우, 당신은 안전하지 않은 지형에 있으며, 지정되지 않은 컴파일러의 동작에 의존하고 있습니다.

다른 컴파일러가 다르게 동작하는 문제가 발생했습니다. 즉 유형 추정입니다. 컴파일러가 형식을 추측해야하는 위치와 추론을 거부해야하는 위치에 대한 JLS는 완전히 명확하지 않으므로 여기에 흔들림 공간이 있습니다. Eclipse 컴파일러의 다른 버전과 javac의 다른 버전은 유형을 추론하는 위치가 다릅니다. javac의 경우 다른 1.5.0_x 버전을 비교할 때도 마찬가지이며 Eclipse 컴파일러는 일반적으로 javac 이상을 추론 할 수 있습니다.

모든 공통 컴파일러가 성공한 유형 유추에만 의존하고 유형 힌트를 제공해야합니다. 때로는 임시 변수를 도입하는 것만 큼 간단하지만 가끔씩 (예에서와 같이) var.<Types>method() 구문을 사용해야합니다. 주석에 관한

: 내가 방법 Duck.getSound()가 꽥를 반환 할 경우, 제네릭을 사용하여 소리하지 무엇 ?

public interface Bird<T extends Sound> { 
    T getSound(); 
} 

그런 다음 당신이 그렇게처럼 구현할 수 :

는 새 인터페이스는 다음과 같은 방법을 가지고 가정

private static class Duck implements Bird<Quack> { 
    public Quack getSound() { return new Quack(); } 
} 

이 제네릭에 대해 하나의 유스 케이스입니다 - 구현 콘크리트 유형을 지정할 수 있습니다 따라서 수퍼 클래스조차도 해당 유형을 사용할 수 있습니다. (조류 인터페이스는 setSound(T)을 가질 수있다, 또는 T.의 구체적인 유형 모른 채, T와 다른 물건을 할) 발신자는 인스턴스 유형 Bird<? extends Sound>의 것을 알았다면

을, 그는과 같이 getSound 전화를 할 것입니다 : 발신자에 대한 Quack을 알고

Sound birdSound = bird.getSound(); 

경우, 그는 instanceof 테스트를 수행 할 수있다. 그러나 발신자는 새가 정말 Bird<Quack>했다, 심지어 즉 그는이 쓸 수하는 Duck 것을 알고는 원하는대로 컴파일하는 경우 :

Quack birdSound = bird.getSound(); 

을하지만 조심 : 인터페이스에서 사용 Generifying 너무 많은 종류의 또는 수퍼 클래스는 시스템을 과도하게 복잡하게 만들 수 있습니다. Slanec이 작성한대로 정말 많은 제네릭이 필요한지 여부를 확인하기 위해 실제 디자인을 다시 생각해보십시오.

나는 한 번이 같은 인터페이스를 기반으로, 너무 멀리 갔다, 그리고 인터페이스 계층과이 구현 계층 구조 결국 :

interface Tree<N extends Node<N>, 
       T extends Tree<N, T>> { ... } 

interface SearchableTree<N extends SearchableNode<N>, 
         S extends Searcher<N>, 
         T extends SearchableTree<N, S, T>> 
    extends Tree<N, T> { ... } 

나는 그 예를 따라하지 않는 것이 좋습니다. ;-)

+0

고정. 'useless_t'는 실제로 쓸모가 없다. 또한,'private > List list()'가 바로 컴파일됩니다. –

+0

즉,이 예제에서 'Duck'은 생성자와 내부 필드에서 acks 스를 알고 있어야하며 일반 유형이 아닙니다. 정말 많은 generics가 필요한지 여부를 확인하기 위해 실제 디자인을 다시 생각해보십시오. –

+1

재미있게도, 내 Eclipse (3.7.2, Java6)로 컴파일되지만 javac에서는 실패합니다. 그러나 Maven에서 컴파일되면 작동합니다. 디자인 주석에 관해서는 - 만약 Duck.getSound() 메소드가 Generic을 사용하는 Sound가 아닌 Quack을 돌려 주길 원한다면? –

1

내가 말할 것 : AA는 당신이 B에게 그렇지 않은 <X>을 확장 할 것으로 예상) 목록을 <AA>리터 = 목록 (정의함으로써,를 구현합니다. 어쨌든, 당신은 얼마나 easly 같은 코드를 작성하여 혼란을 참조하십시오. 이것은 단지 너무 복잡합니다.

+0

내 잘못 - 내 예에서는 오류였습니다. 고정 –

0

Java Generics에 대해 약간의 오해가 있습니다. 기억해야 할 것은, 이것은 미묘한 것인데, List<Y>은 목록의 내용에 관한 것이 아니라 목록 자체의 수정입니다.

조금 외삽합시다. 내가 interface Animalinterface Dog extends Animalinterface Cat extends Animal라고 말합니다. (우리가 함께 가서 난 그냥 더 클래스와 인터페이스를 발명 수 있습니다.) 나는 List<Animal> createList()으로 동물을 반환하는 메서드를 선언하는 경우 이제 다음 코드로 아무 문제가 없습니다 : 개가 동물이기 때문이다

List<Animal> litter = createList(); 
Cat tabby = new Tabby(); 
litter.add(tabby); 
Dog poodle = new Poodle(); 
litter.add(poodle); 

, 그리고 고양이는 동물입니다. 추가 유형 List<Animal>의 메소드 서명은 add(Animal)입니다. 우리는 add을 Animal의 유효한 인스턴스로 예상대로 호출 할 수 있습니다. 그러나 List의 type 매개 변수는 목록의 내용을 수정하거나 제한하지 않으므로 목록 자체의 유형을 수정합니다. "고양이 목록"은 "동물 목록"이 아니며 "개 목록"도 아닙니다. createLitter() 메서드가 실제로 Parrot의 인스턴스 만 포함하는 new ArrayList<Animal>()을 반환하는 경우에도 위 코드는 문제가 없습니다. 그러나 할 수없는 일은 목록의 유형을 '좁히는'것입니다.이 허용 된 경우

List<Bird> birds = createList(); // does not compile 

상상하고, createList 우리의 얼룩 무늬가 포함 된 "동물의 목록"을 반환; 예를 들어,이 컴파일 오류입니다 다음은 클래스 캐스트 예외가 발생합니다.

Bird leaderOfTheFlock = birds.get(0); 

또한 목록의 유형을 확대 할 수 없습니다. 그것이 가능했다 상상해 :

List<Object> things = createList(); // does not compile 

이 하나 허용되지 않는 이유는 지금 new Integer(0)things에 추가 할 수있는 코드 - IntegerObject이기 때문이다. 분명히 그것은 우리가 원하거나 동물과 같은 이유에서 "대상 목록"이 아닙니다. List<Animal>에있는 "Animal"유형 매개 변수는 목록 자체의 유형을 수정하며 두 가지 유형의 목록을 말합니다. 이것은 우리가 그 점의 첫 번째 결과로 이끌어냅니다. 제네릭 타입은 상속 (is-a) 계층 구조를 따르지 않습니다.

당신이하고 싶은 것을 더 많이 알지 못하면 여기에서 나와 관련성을 유지하는 것이 어렵습니다. 나는 가혹한 것을 의미하지는 않지만 코드에서 제네릭을 던져서 뭔가 효과가 있는지보기 시작한 것 같습니다. 나는 제네릭으로 수년간 고생했다. 이 미묘한 점을 설명하는 블로그를 실행 한 후에도 규칙을 위반하면 클래스 캐스팅 예외로 끝날 것입니다 다양한 방법을 찾고, 교훈을 강화하기 위해 위의 몇 가지 유사 콘텐츠를 재현해야했습니다. 코드의 다른 부분은 소개하려고하는 엄격한 유형 시스템과 관련하여 잘 정의되어 있지 않으며, 일반적인 제네릭 문제는 그 증상 일뿐입니다. 제네릭을 줄이고 구성 및 상속에 더 의존하십시오. 나는 아직도 일반적인 깊은 곳에서 벗어나 때때로 발에서 자신을 쏜다. 제네릭의 요점은 형변환을 제거하는 것이 아니라 컴파일러가 형식을 다루는 코드의 정확성을 검증하는 데 도움이되도록 형식 정보를 사용할 수 있도록하는 것입니다. 즉 런타임 오류 (클래스 캐스트)를 소스/컴파일 타임 오류로 바꾸기 때문에 컴파일 타임에 어떤 유형 정보가 있는지 구분해야한다는 점에 유의해야합니다 (제한적이며 generics) 및 런타임에 어떤 유형 정보 (인스턴스의 전체 유형 정보인지)를 표시합니다.