IT이야기

'super'키워드로 제네릭 경계 지정

cyworld 2021. 5. 1. 09:33
반응형

'super'키워드로 제네릭 경계 지정


super유형 매개 변수가 아닌 와일드 카드에만 사용할 수있는 이유는 무엇 입니까?

예를 들어 Collection인터페이스에서 toArray메소드가 이렇게 작성되지 않은 이유는 무엇입니까?

interface Collection<T>{
    <S super T> S[] toArray(S[] a);
}

super<S super T>와일드 카드 (예 :) 와 반대로 명명 된 유형 매개 변수 (예 ) 를 바인딩하는 <? super T>것은 허용되지 않더라도 원하는대로 수행하지 않기 때문에 불법 입니다. 모든 참조 유형 Object의 궁극 이기 때문에 super그리고 모든 것이 Object, 사실상 바운드가 없습니다 .

특정 예에서 참조 유형의 모든 배열은 Object[](자바 배열 공분산에 의해 <S super T> S[] toArray(S[] a)) 컴파일 타임에 인수로 사용할 수 있으며 (해당 바인딩이 합법적 인 경우) ArrayStoreException실행시 방지되지 않습니다. 시각.

당신이 제안하려는 것은 주어진 것입니다.

List<Integer> integerList;

가상의 super 경계가 주어지면 toArray:

<S super T> S[] toArray(S[] a) // hypothetical! currently illegal in Java

컴파일러는 다음 만 컴파일하도록 허용해야합니다.

integerList.toArray(new Integer[0]) // works fine!
integerList.toArray(new Number[0])  // works fine!
integerList.toArray(new Object[0])  // works fine!

그리고 다른 배열 유형 인수는 없습니다 ( Integer으로이 3 가지 유형 만 있기 때문에 super). 즉, 이것이 컴파일되는 것을 방지하려고합니다.

integerList.toArray(new String[0])  // trying to prevent this from compiling

왜냐하면 귀하의 주장에 의해 String는의가 아니기 때문 super입니다 Integer. 그러나 , Objectis a superof Integer, a String[]is an Object[], 따라서 컴파일러 는 가상적으로 할 수 있더라도 여전히 위의 컴파일을 허용합니다 <S super T>!

따라서 다음 은 여전히 ​​컴파일 되며 (지금과 같이) ArrayStoreException런타임에 제네릭 유형 경계를 사용하는 컴파일 타임 검사로 방지 할 수 없습니다.

integerList.toArray(new String[0])  // compiles fine!
// throws ArrayStoreException at run-time

제네릭과 배열은 섞이지 않으며, 이것이 보여주는 많은 장소 중 하나입니다.


배열이 아닌 예

다시, 다음과 같은 일반적인 메서드 선언이 있다고 가정 해 보겠습니다.

<T super Integer> void add(T number) // hypothetical! currently illegal in Java

그리고 다음과 같은 변수 선언이 있습니다.

Integer anInteger
Number aNumber
Object anObject
String aString

귀하의 의도는 <T super Integer>(합법적 인 경우) add(anInteger), 및 add(aNumber), 물론 허용해야 add(anObject)하지만 add(aString). 음, String입니다 Object때문에, add(aString)여전히 어쨌든 컴파일 것입니다.


또한보십시오

관련 질문

제네릭 입력 규칙 :

사용 superextends:


누구도 만족할만한 답을 제공하지 않았기 때문에 정답은 "합당한 이유가 없다"는 것 같습니다.

polygenelubricants는 그 자체로 끔찍한 기능인 자바 배열 공분산에서 발생하는 나쁜 일들에 대한 좋은 개요를 제공했습니다. 다음 코드 조각을 고려하십시오.

String[] strings = new String[1];
Object[] objects = strings;
objects[0] = 0;

이 명백히 잘못된 코드는 "수퍼"구조에 의존하지 않고 컴파일되므로 배열 공분산을 인수로 사용해서는 안됩니다.

이제 여기 super에 명명 된 형식 매개 변수를 요구하는 완벽하게 유효한 코드 예제가 있습니다 .

class Nullable<A> {
    private A value;
    // Does not compile!!
    public <B super A> B withDefault(B defaultValue) {
        return value == null ? defaultValue : value;
    }
}

좋은 사용법을 잠재적으로 지원합니다.

Nullable<Integer> intOrNull = ...;
Integer i = intOrNull.withDefault(8);
Number n = intOrNull.withDefault(3.5);
Object o = intOrNull.withDefault("What's so bad about a String here?");

후자의 코드 조각은 B모두 제거하면 컴파일되지 않으므로 B실제로 필요합니다.

구현하려는 기능은 형식 매개 변수 선언의 순서를 반전하여 super제약 조건을 extends. 그러나 이것은 메서드를 정적 메서드로 다시 작성하는 경우에만 가능합니다.

// This one actually works and I use it.
public static <B, A extends B> B withDefault(Nullable<A> nullable, B defaultValue) { ... }

요점은이 Java 언어 제한이 실제로 다른 가능한 유용한 기능을 제한하고 있으며 추악한 해결 방법이 필요할 수 있다는 것입니다. withDefault가상이 필요하다면 어떻게 될지 궁금합니다 .

이제 polygenelubricants가 말한 것과 상관 관계를 맺기 위해 B여기에서 전달 된 객체 유형을 제한하지 않고 defaultValue(예제에서 사용 된 문자열 참조) 반환하는 객체에 대한 호출자 기대치를 제한합니다. 간단한 규칙 extends으로 원하는 유형과 super제공하는 유형을 사용합니다.


질문에 대한 "공식"답변은 Sun / Oracle 버그 보고서 에서 찾을 수 있습니다 .

BT2 : 평가

보다

http://lampwww.epfl.ch/~odersky/ftp/local-ti.ps

특히 섹션 3과 9 페이지의 마지막 단락이 있습니다. 하위 유형 제약 조건의 양쪽에 유형 변수를 허용하면 단일 최상의 솔루션이없는 유형 방정식 집합이 생성 될 수 있습니다. 따라서 기존 표준 알고리즘을 사용하여 유형 추론을 수행 할 수 없습니다. 이것이 타입 변수가 "확장"경계만을 갖는 이유입니다.

반면 와일드 카드는 유추 할 필요가 없으므로이 제약 조건이 필요하지 않습니다.

@ ###. ### 2004-05-25

예; 핵심은 와일드 카드가 캡처 된 경우에도 추론 프로세스의 입력으로 만 사용된다는 것입니다. 결과적으로 하한이있는 것은 아무것도 추론 할 필요가 없습니다.

@ ###. ### 2004-05-26

나는 문제를 본다. 그러나 추론하는 동안 와일드 카드에 대한 하한이있는 문제와 어떻게 다른지 알 수 없습니다. 예 :

목록 <? 수퍼 넘버> s;
부울 b;
...
s = b? s : s;

현재, 우리는 List <X>를 추론합니다. 여기서 X는 Object를 조건식의 유형으로 확장합니다. 즉, 할당이 불법임을 의미합니다.

@ ###. ### 2004-05-26

슬프게도 대화는 거기서 끝납니다. (현재 죽은) 링크가 가리키는 데 사용 된 논문은 Inferred Type Instantiation for GJ 입니다. 마지막 페이지를 살펴보면 다음과 같이 요약됩니다. 하한이 인정되면 유형 추론은 여러 솔루션을 생성 할 수 있으며 그중 어느 것도 원칙 이 아닙니다 .


다음이 있다고 가정합니다.

  • 기본 클래스 A> B> C 및 D

    class A{
        void methodA(){}
    };
    class B extends  A{
        void methodB(){}
    }
    
    class C extends  B{
        void methodC(){}
    }
    
    class D {
        void methodD(){}
    }
    
  • 작업 래퍼 클래스

    interface Job<T> {
        void exec(T t);
    }
    
    class JobOnA implements Job<A>{
        @Override
        public void exec(A a) {
            a.methodA();
        }
    }
    class JobOnB implements Job<B>{
        @Override
        public void exec(B b) {
            b.methodB();
        }
    }
    
    class JobOnC implements Job<C>{
        @Override
        public void exec(C c) {
            c.methodC();
        }
    }
    
    class JobOnD implements Job<D>{
        @Override
        public void exec(D d) {
            d.methodD();
        }
    }
    
  • 객체에 대한 작업을 실행하는 4 가지 접근 방식을 가진 하나의 관리자 클래스

    class Manager<T>{
        final T t;
        Manager(T t){
            this.t=t;
        }
        public void execute1(Job<T> job){
            job.exec(t);
        }
    
        public <U> void execute2(Job<U> job){
            U u= (U) t;  //not safe
            job.exec(u);
        }
    
        public <U extends T> void execute3(Job<U> job){
            U u= (U) t; //not safe
            job.exec(u);
        }
    
        //desired feature, not compiled for now
        public <U super T> void execute4(Job<U> job){
            U u= (U) t; //safe
            job.exec(u);
        }
    }
    
  • 사용법과 함께

    void usage(){
        B b = new B();
        Manager<B> managerB = new Manager<>(b);
    
        //TOO STRICT
        managerB.execute1(new JobOnA());
        managerB.execute1(new JobOnB()); //compiled
        managerB.execute1(new JobOnC());
        managerB.execute1(new JobOnD());
    
        //TOO MUCH FREEDOM
        managerB.execute2(new JobOnA()); //compiled
        managerB.execute2(new JobOnB()); //compiled
        managerB.execute2(new JobOnC()); //compiled !!
        managerB.execute2(new JobOnD()); //compiled !!
    
        //NOT ADEQUATE RESTRICTIONS     
        managerB.execute3(new JobOnA());
        managerB.execute3(new JobOnB()); //compiled
        managerB.execute3(new JobOnC()); //compiled !!
        managerB.execute3(new JobOnD());
    
        //SHOULD BE
        managerB.execute4(new JobOnA());  //compiled
        managerB.execute4(new JobOnB());  //compiled
        managerB.execute4(new JobOnC());
        managerB.execute4(new JobOnD());
    }
    

지금 execute4를 구현하는 방법에 대한 제안이 있습니까?

========== 편집 됨 =======

    public void execute4(Job<? super  T> job){
        job.exec( t);
    }

모두에게 감사드립니다 :)

========== 편집 됨 ==========

    private <U> void execute2(Job<U> job){
        U u= (U) t;  //now it's safe
        job.exec(u);
    }
    public void execute4(Job<? super  T> job){
        execute2(job);
    }

훨씬 더 나은, execute2 내부 U 코드

슈퍼 유형 U가 명명됩니다!

흥미로운 토론 :)

참조 URL : https://stackoverflow.com/questions/2800369/bounding-generics-with-super-keyword

반응형