2016-10-28 15 views
3

以下のSCCEは、イン​​ターフェイスマーカーを実装する2つのクラス(BおよびC)を示しています。 Markerを実装する各クラスには、汎用ハンドラインタフェース(B_Handler、C_Handler)を実装する対応するクラスがあります。マップは、Pair.secondのクラス型をそれに関連付けられたHandlerに関連付けるために使用されます。コードは予期したとおりに実行されます。ただし、コンパイル時の警告が表示されます。Java Genericsワイルドカードキャプチャ警告

警告:[未チェック]未チェックキャスト ハンドラh1 =(ハンドラ)(dispatch.get(p1.second.getClass())); 必須:ハンドラー が見つかりました:ハンドラー ここで、CAP#1は新しいタイプ変数です。 CAP#1はマーカーのキャプチャーを延長しますか?マーカーを伸ばす

@SuppressWarnings(value = "unchecked")以外にこれを解決する最もクリーンな方法は何ですか?

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>を返すべきです。あなたはHandlerMarkerを持っていることはありません。むしろ、特定のタイプのHandlerMarkerとしています。したがって、特定の種類のMarkerPairも使用することが理にかなっています。

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を使用し、2番目の引数が最終実装クラスである限り、正しいはずです。

そして最後に主な方法は、次のようになります。

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<? extends List> test1 = new ArrayList<>(); 
List<List> test2 = (List<List>) test1; 

は、ここでは、警告を受ける: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>に安全にキャストする方法はありません。