「Tの一般的な配列はvarargsパラメーターに対して作成されます」コンパイラー警告を解決することは可能ですか?


153

これは問題のコードの簡略化されたバージョンです。あるジェネリッククラスはジェネリック型パラメーターを持つ別のクラスを使用し、varargsパラメーターを持つメソッドにジェネリック型の1つを渡す必要があります。

class Assembler<X, Y> {
    void assemble(X container, Y... args) { ... }
}

class Component<T> {
    void useAssembler(T something) {

        Assembler<String, T> assembler = new Assembler<String, T>();

        //generates warning:
        // Type safety : A generic array of T is
        // created for a varargs parameter
        assembler.assemble("hello", something);
    }

}

この警告に遭遇することなく、ジェネリックパラメーターをvarargsメソッドに渡す正しい方法はありますか?

もちろんのようなもの

assembler.assemble("hello", new T[] { something });

汎用配列を作成できないため、機能しません。


3
変なもの。コンパイラーは、ここで完全な型安全性を保証できるはずです。
エリクソン2009

3
アンジェリカランガーのJavaジェネリックのFAQで関連エントリー:angelikalanger.com/GenericsFAQ/FAQSections/...
フロー

回答:


88

を追加する以外@SuppressWarnings("unchecked")、私はそうは思いません。

このバグレポートには詳細情報がありますが、結局のところ、コンパイラはジェネリック型の配列を好まないのです。


3
@SuppressWarnings( "unchecked")を避けたかったことを忘れていました。そのバグレポートは私に少しの希望を与えます!
マットb

3
ジョシュア・ブロッホがそれをEffective Javaに入れているように、「ジェネリックと配列を混ぜてはいけない」。
Timmos 2014年

20
次に、暗黙的に:GenericsでVarargsを使用しないでください!そうです... varargsをコレクションではなく配列にマッピングするという決定は、Javaを永久に使い続けます。ゴスリング氏はうまくやった。
バーンスタイン

57

Tom Hawtinはコメントでこれを指摘しましたが、より明確にするために:はい、((潜在的に多くの)呼び出しサイトではなく)宣言サイトでこれを解決できます:JDK7に切り替えます。

Joseph Darcyのブログ投稿でわかるように、Java 7のいくつかの小さな言語改善を選択するためのプロジェクトコイン演習では、メソッド側のようなものがこの警告を解消することを許可されている状況で許可するというBob Leeの提案を受け入れ@SuppressWarnings("varargs")ました安全。

これは、このコミットでOpenJDKに実装されました。

これはプロジェクトに役立つ場合もあればそうでない場合もあります(多くの人は、JVMのプレリリース版の不安定バージョンに切り替えて喜んでいません!)おそらくそうです—あるいは、後でこの質問を見つけた人(JDK7のリリース後) )は便利だと思います。


7
言及されたプロジェクトコイン機能が利用可能になりました-Java 7の@SafeVarargsを参照してください
George Hawkins

ボブの提案の代替Eは魅力的です。
クリストファーペリー

Java 8は@SuppressWarnings( "varargs")ではなく@SafeVarargsを使用しているようです
Paul Wintz

17

流暢なタイプのインターフェースが必要な場合は、ビルダーパターンを試すことができます。可変引数ほど簡潔ではありませんが、タイプセーフです。

静的な総称型メソッドは、型の安全性を維持しながら、ビルダーの使用時に定型文の一部を排除できます。

ビルダー

public class ArgBuilder<T> implements Iterable<T> {

    private final List<T> args = new ArrayList<T>();

    public ArgBuilder<T> and(T arg) {
        args.add(arg);
        return this;
    }

    @Override
    public Iterator<T> iterator() {
        return args.iterator();
    }

    public static <T> ArgBuilder<T> with(T firstArgument) {
        return new ArgBuilder<T>().and(firstArgument);
    }
}

それを使う

import static com.example.ArgBuilder.*;

public class VarargsTest {

    public static void main(String[] args) {
        doSomething(new ArgBuilder<String>().and("foo").and("bar").and("baz"));
        // or
        doSomething(with("foo").and("bar").and("baz"));
    }

    static void doSomething(Iterable<String> args) {
        for (String arg : args) {
            System.out.println(arg);
        }
    }
}

1
作曲の力。これは可変引数よりもずっと好きで、より表現力があります。
クリストファーペリー

1
@ChristopherPerryは、コードベースも考慮する必要があります。基礎となるCollection(この場合はArrayList)は呼び出し元に強制されますが、a LinkedListがより適切であること、または不変の配列自体(OP質問の可変引数など)を知っている場合があります。特殊なユースケースではこれが適切な場合がありますが、ある意味、これを取り巻くコードとユーザーのニーズによっては、これも制限であることを指摘するだけです。
searchengine27

5

varargメソッドの呼び出しでパラメーターをObjectに明示的にキャストすると、@ SuppressWarningsに頼らずにコンパイラーを満足させることができます。

public static <T> List<T> list( final T... items )
{
    return Arrays.asList( items );
}

// This will produce a warning.
list( "1", 2, new BigDecimal( "3.5" ) )

// This will not produce a warning.
list( (Object) "1", (Object) 2, (Object) new BigDecimal( "3.5" ) )

// This will not produce a warning either. Casting just the first parameter to 
// Object appears to be sufficient.
list( (Object) "1", 2, new BigDecimal( "3.5" ) )

ここでの問題は、コンパイラーが作成する配列の具体的なタイプを理解する必要があることだと思います。メソッドがジェネリックでない場合、コンパイラはメソッドからの型情報を使用できます。メソッドがジェネリックの場合、呼び出し時に使用されたパラメーターに基づいて配列タイプを把握しようとします。パラメータタイプが同種である場合、その作業は簡単です。それらが異なる場合、コンパイラーは私の考えでは賢すぎようとし、共用体型の汎用配列を作成します。それからそれについてあなたに警告することは強いられます。より簡単な解決策は、型をより適切に絞り込むことができない場合にObject []を作成することでした。上記のソリューションはそれを強制します。

これをよりよく理解するには、次のlist2メソッドと比較して、上記のlistメソッドの呼び出しをいじってください。

public static List<Object> list2( final Object... items )
{
    return Arrays.asList( items );
}

これは、たとえば次のように機能します。Iterator <?> it = Arrays.asList((Object)t).iterator; if(if、hasNext()){class = it.next()。getClass(); たとえば、不明なタイプの配列からオブジェクトのクラスを取得します。
ggb667 2013年

2

Java 7以降では@SafeVarargsをメソッドに追加でき、クライアントコードに注釈を付ける必要はありません。

class Assembler<X, Y> {

    @SafeVarargs
    final void assemble(X container, Y... args) {
        //has to be final...
    }
}

1

メソッドをオーバーロードすることができます。これはあなたの問題を解決しませんが、警告の数を最小限に抑えます(そしてはい、それはハックです!)

class Assembler<X, Y> {
  void assemble(X container, Y a1) { ... }
  void assemble(X container, Y a1, Y a2) { ... }
  void assemble(X container, Y a1, Y a2, Y a3) { ... }
  void assemble(X container, Y a1, Y a2, Y a3, Y a4) { ... }
  void assemble(X container, Y... args) { ... }
}

23
ええ。これは、varargsが防止するはずのハッキングの一種です。
アマンダS

1
これは有効なアプローチです。たとえば、GuavaのImmutableSet.ofを見てください。
ジョナサン

1

解決するのは非常に簡単な問題List<T>です。

参照型の配列は避けてください。

現在のバージョンのJava(1.7)では@SafeVargs、呼び出し元から警告を削除するメソッドにマークを付けることができます。ただし、これには注意してください。レガシーアレイがなくても、作業は簡単です。

参照してください可変引数メソッドと非Reifiable仮パラメータ使用時の改善されたコンパイラの警告とエラーにテクニカルノートを。


6
これはvarargsパラメーターでは避けられませんね。
マットb

4
JDK7には、警告の抑制を、その使用方法ではなくvarargsメソッドの宣言で許可するという提案があります。
トム・ホーティン-タックライン

11
これは、作者の質問の重要な側面を完全に無視します-varargsパラメーターは配列を作成するため、この警告が生成されます。
Daniel Yankowsky、2010

2
@Tom Hawtin-タックラインに同意します。詳細については、Bloch << Effecive Java >>の項目25:配列よりもリストを優先するを参照してください。
スタンクリリン

2
私は一般的にこれについてブロッホに同意しますが、varargsはルールの明確な例外です...
Joeri Hendrickx

0

ジェネリック型の配列を操作するとき、ジェネリック型への参照を渡す必要があります。これで、java.lang.reflect.Arrayを使用して、実際に汎用コードを実行できます。

http://java.sun.com/javase/6/docs/api/java/lang/reflect/Array.html


私はジェネリック型の配列を操作していませんが、直接ではなく、ジェネリック型の可変引数のみを使用しています。
マットb
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.