web-dev-qa-db-ja.com

Javaジェネリックスと可変引数

ジェネリックと可変引数の両方を使用して関数を実装したいと思います。

public class Question {
    public static <A> void doNastyThingsToClasses(Class<A> parent, Class<? extends A>... classes) {
        /*** something here ***/
    }
    public static class NotQuestion {
    }
    public static class SomeQuestion extends Question {
    }
    public static void main(String[] args) {
        doNastyThingsToClasses(Object.class, Question.class, SomeQuestion.class); // OK
        doNastyThingsToClasses(Question.class, SomeQuestion.class); // OK
        doNastyThingsToClasses(Question.class, Object.class, SomeQuestion.class); // compilation failure
    }
}

ここでの意図は、この関数に渡されるすべてのパラメーターが、最初のパラメーターとして指定されたクラスを拡張するClassオブジェクトであることを表明することです。したがって、メインメソッドの最初の2行はコンパイルされ、3行目はエラーを生成します。

私の質問は: 最初の2行で「型の安全性:クラスの汎用配列が可変引数パラメーターに対して作成されています」というメッセージが表示されるのはなぜですか?

ここで何か不足していますか?

追加の質問: この警告が「doNastyThingsToClasses」関数を呼び出すすべての行に表示されないように再設計する方法「doNastyThingsToClasses(Class <A> parent、Class <?> ... classes)」に変更して警告を取り除くことができますが、これによりコンパイル時の型チェックも削除されます---この関数の正しい使用を保証します。より良い解決策はありますか?

32
Chris

ほとんどいつものように、アンジェリカランガーのJavaジェネリックFAQ それを非常に詳しく説明する 。(なぜコンパイラーが時々問題を起こすのか「varargs」メソッドを呼び出すと、チェックされていない警告が表示されますか?」-IDが正しく機能しません。)

基本的に、あなたは通常よりも悪い方法で情報を失うことになります。 Java Generics :(

38
Jon Skeet

Jon Skeetの答えは(もちろん)正しいです。大きな「if」を使用してこの警告を回避できることを指摘して、少し詳しく説明します。 Java 7.を使用してプロジェクトをビルドすることにコミットする場合は、この警告を回避できます。

ボブ・リーは プロジェクトコイン の一部として、この警告を使用場所ではなくメソッド宣言サイトで抑制するように 提案 を書いた。

この提案はJDK7で受け入れられました(構文は少し変更されましたが、@SuppressWarnings("varargs"))。興味があれば、 このサポートをJDKに追加したコミット を参照してください。

必ずしもあなたに役立つわけではありませんが、Java 7以降の世界に住んでいる幸運な将来の読者のために、これを別の答えにするつもりだと思いました。

13
Cowan

余談ですが、警告はJava 7の新しい@SafeVarargsアノテーションを使用して抑制できます。

@SafeVarargs
public static <A> void func( Class<A> parent, Class<? extends A>... classes ) {
    // Do func...
}
9
Scott

この問題に対する私の解決策は

  1. クラスNastierを作成する
  2. doNastyThingsToClassesから...を削除
  3. doNastyThingsToClassesを静的メソッドにしない
  4. 名前を短くする
  5. これを返す
  6. 反復的な引数をクラスプロパティに移動する

    class Nastier {
      private final Class<A> parent;
    
      public Nastier(Class<A> parent) {
         this.parent = parent;
      }
    
      public <A, C extends A> Nastier do(Class<? extends A> clazz) {
         System.out.println(clazz);
         return this;
      }
    }
    
    public static void main(String[] args) {   
      Nastier nastier = new Nastier(Object.class);
      nastier.do(Question.class).do(SomeQuestion.class).do(NotQuestion.class);
    }
    

私はコードがきれいに見えて私は幸せだと思います... :)

5
LuisKarlos

さて、最後に可変引数を捨ててしまいます。

public class Question {

    public static <A, C extends A> void doNastyThingsToClasses(Class<A> parent, List<Class<? extends A>> classes) {
        /******/
        for(Class<? extends A> clazz : classes) {
            System.out.println(clazz);
        }
    }

    public static class NotQuestion {
    }
    public static class SomeQuestion extends Question {
    }

    public static void main(String[] args) {

        ArrayList<Class<? extends Object>> classes = new ArrayList<Class<? extends Object>>();
        classes.add(Question.class);
        classes.add(SomeQuestion.class);
        classes.add(NotQuestion.class);
        doNastyThingsToClasses(Object.class, classes);

        ArrayList<Class<? extends Question>> clazzes = new ArrayList<Class<? extends Question>>();
        clazzes.add(Question.class);
        clazzes.add(SomeQuestion.class);
        clazzes.add(NotQuestion.class); // yes, this will _not_ compile
        doNastyThingsToClasses(Question.class, clazzes);

    }

}

唯一の欠点は、関数の引数を運ぶために使用されるコレクションを生成するための長いコードです。

1
Chris

2番目の引数_Class<? extends A>_...最初の引数と同じクラスを拡張する必要があります(たとえば、引数1はQuestionなので、2番目の引数はQuestionを拡張するものです。

内訳:
NastyThingsToClasses(Object.class, Question.class, SomeQuestion.class); // OK
すべてがObjectを拡張するため、2番目の引数は正しいです。

NastyThingsToClasses(Question.class, SomeQuestion.class); // OK
SomeQuestionQuestionを拡張するので、公平なゲームです。

NastyThingsToClasses(Question.class, Object.class, SomeQuestion.class);
ObjectQuestionを拡張しないため、エラーが発生します。


うまくいけば、物事は片付けられました。

-ブレット

0
Brett