2016-10-28 2 views
3

아래 SCCE는 인터페이스 마커를 구현하는 2 개의 클래스 (B 및 C)를 보여줍니다. Marker를 구현하는 각 클래스에는 일반 Handler 인터페이스 (B_Handler, C_Handler)를 구현하는 해당 클래스가 있습니다. 맵은, Pair.second의 Class 형을 관련 지을 수 있던 Handler에 관련 짓기 위해서 (때문에) 사용됩니다. 코드는 예상대로 실행됩니다. 그러나 컴파일 타임 경고가 나타납니다 :Java Generics 와일드 카드 캡처 경고

경고 : [선택하지 않음] 체크되지 않은 캐스트 처리기 h1 = (처리기) (dispatch.get (p1.second.getClass())); 필수 : ​​처리기 발견 : 처리기 여기서 CAP # 1은 새로운 유형 변수입니다. CAP # 1 캡처에서 마커를 확장합니까? extends Marker

@SuppressWarnings (value = "unchecked") 외에이 문제를 해결하는 가장 확실한 방법은 무엇입니까? 여기

List<? extends List> test1 = new ArrayList<>(); 
List<List> test2 = (List<List>) test1; 

우리가 경고를 얻을 :

package genericpair; 

import java.util.HashMap; 
import java.util.Map; 

import javax.swing.SwingUtilities; 

public class GenericPair 
{ 
    public class A 
    { 
    } 

    public interface Marker 
    { 
    } 

    public class B implements Marker 
    { 
    } 

    public class C implements Marker 
    { 
    } 

    public Pair<A, Marker> getTarget() 
    { 
     A a = new A(); 
     C c = new C(); 
     return new Pair<>(a, c); 
    } 

    public interface Handler<T extends Marker> 
    { 
     void handle(Pair<A, T> target); 
    } 

    public class B_Handler implements Handler<B> 
    { 
     @Override 
     public void handle(Pair<A, B> target) 
     { 
      System.out.println("B"); 
     } 
    } 

    public class C_Handler implements Handler<C> 
    { 
     @Override 
     public void handle(Pair<A, C> target) 
     { 
      System.out.println("C"); 
     } 
    } 

    public class Pair<F, S> 
    { 
     public final F first; 
     public final S second; 

     public Pair(F first, S second) 
     { 
      this.first = first; 
      this.second = second; 
     } 
    } 

    private void executeSCCE() 
    { 
     // register a handler for each Marker type 
     Map<Class, Handler<? extends Marker>> dispatch = new HashMap<>(); 
     dispatch.put(B.class, new B_Handler()); 
     dispatch.put(C.class, new C_Handler()); 

     // get a target (e.g., Pair<A,C>) 
     Pair<A, Marker> p1 = getTarget(); 

     // select handler based on the class type of the second parameter 
     Handler<Marker> h1 = (Handler<Marker>) (dispatch.get(p1.second.getClass())); 
     h1.handle(p1); 
    } 

    public static void main(String[] args) 
    { 
     SwingUtilities.invokeLater(() -> new GenericPair().executeSCCE()); 
    } 
} 

답변

1

몇 가지 문제가 있습니다.

첫 번째는 Map이 각 키와 값 사이의 형식 관계를 나타낼 수 없다는 것입니다. 따라서 Class<T>dispatch.get()에 전달하면 Handler<T>이 아니라 Handler<? extends Marker> 만 다시 나타납니다. 사실, 당신이 그 일을하기 위해 dispatch을 줄 수있는 유형이 없습니다. 대신, 자사의 API를 통해이 관계를 강화하기 위해 래퍼 클래스를 확인해야합니다 : 당신은 아직도이 클래스 내부 확인 경고를 억제해야합니까

public class ClassToHandlerMap 
{ 
    private final Map<Class<?>, Handler<?>> map = new HashMap<>(); 
    public <T extends Marker> void put(Class<T> clazz, Handler<T> handler) { 
     map.put(clazz, handler); 
    } 
    @SuppressWarnings("unchecked") 
    public <T extends Marker> Handler<T> get(Class<T> clazz) { 
     return (Handler<T>)map.get(clazz); 
    } 
} 

참고, 그러나 적어도 여기 당신이 그것을 기반으로,라도 유용 올바른 알고 어떻게 사물을지도에 넣을 수 있는지. 체크되지 않은 캐스트는이 클래스의 사용자가 알 필요가없는 구현 세부 사항입니다.

두 번째 문제는 getTarget()Pair<A, Marker> 대신 Pair<A, ? extends Marker>을 반환해야한다는 것입니다. 당신은 Handler s의 Marker을 가지고 있지 않습니다. 오히려 Handler의 특정 유형이 Marker입니다. 따라서 Marker의 특정 유형의 Pair 만 사용하는 것이 좋습니다.

public Pair<A, ? extends Marker> getTarget() 
{ 
    A a = new A(); 
    C c = new C(); 
    return new Pair<>(a, c); 
} 

기본적으로 자체에서 작동하도록 p1를 사용하는 함수의 마지막 부분, 그래서 우리는 캡처 도우미를 사용할 필요에 "캡처"우리가 필요로하는 무엇을위한 유용한 형태 변수로 p1의 종류에 ? 할 것.

그러나이 경우 .getClass()을 사용하고 있기 때문에 더 복잡합니다. foo.getClass()의 유형은 Class<? extends |X|>입니다. 여기서 |X|foo의 컴파일 타임 유형 삭제입니다. 따라서 p1의 유형이 Pair<A, ?> 또는 Pair<A, T> 인 경우에도 은 Class<? extends Marker> 유형을 반환합니다. 따라서 ?Pair<A, ?>에 캡처하는 것으로는 충분하지 않습니다. 대신, 우리는 .getClass()의 반환에 ?에 캡처해야합니다

@SuppressWarnings("unchecked") 
private static <T extends Marker> void captureHelper(Class<T> clazz, 
     Pair<A, ? extends Marker> p, ClassToHandlerMap dispatch) { 
    Pair<A, T> p1 = (Pair<A, T>)p; 

    Handler<T> h1 = dispatch.get(clazz); 
    h1.handle(p1); 
} 

불행하게도, 우리는 또한 여기에 검사되지 않은 캐스팅을해야 할 것입니다. .getClass()의 고유 한 반환 유형으로 인해 .getClass()의 반환 유형과 호출되는 표현식을 연결할 수 없습니다. 그리고 .cast()과 같은 런타임 형변환을 사용하여 매개 변수화 된 유형간에 캐스트 할 수 없습니다 (지정된 클래스의 인스턴스를 인수로 사용하는 경우에는 확인되지 않은 캐스트를 제거하기 위해 .cast()을 사용할 수 있지만 여기서는 그렇지 않습니다). 이것이 올바르지 않은 몇 가지 모호한 경우가있을 수 있지만 항상 Pair을 사용하고 두 번째 형식 인수를 최종 구현 클래스로 사용하는 한 올바른 것이어야합니다.

그리고 마지막으로 기본 방법은 다음과 같습니다

private void executeSCCE() 
{ 
    // register a handler for each Marker type 
    ClassToHandlerMap dispatch = new ClassToHandlerMap(); 
    dispatch.put(B.class, new B_Handler()); 
    dispatch.put(C.class, new C_Handler()); 

    // get a target (e.g., Pair<A,C>) 
    Pair<A, ? extends Marker> p1 = getTarget(); 

    // select handler based on the class type of the second parameter 
    captureHelper(p1.second.getClass(), p1, dispatch); 
} 
4

는 다음의 예를 고려 List<List>의 일반적인 제약 List<? extends List> 일치하는 것을 보장 할 수있는 방법이 없기 때문에

warning: [unchecked] unchecked cast 
     List<List> test2 = (List<List>) test1; 
             ^
    required: List<List> 
    found: List<CAP#1> 
    where CAP#1 is a fresh type-variable: 
    CAP#1 extends List from capture of ? extends List 

발생합니다 . 이 예를 다음과 같이 다시 작성한다고 가정 해보십시오.

List<? extends List> test1 = new ArrayList<ArrayList>(); 
List<List> test2 = (List<List>) test1; 
test1.add(new LinkedList<>());//ERROR no suitable method found for add(LinkedList<Object>) 
test2.add(new LinkedList<>());//Will work fine!! 

초기 계약이 파손 된 것이 분명합니다. ArrayList을 포함하도록 정의 된 목록은 이제 LinkedList을 포함합니다. 이것은 안전하지 않으며이 경고를받는 이유입니다. 따라서 Handler<? extends Marker>에서 Handler<Marker>으로 안전하게 전송할 수있는 방법은 없습니다.