Java SafeVarargsアノテーション、標準またはベストプラクティスは存在しますか?


175

最近、java @SafeVarargsアノテーションに遭遇しました。Javaの可変個関数が安全ではない理由をググリングすると、混乱してしまいました(ヒープポイズニング?消去された型?)。

  1. 可変個のJava関数をある@SafeVarargs意味で安全でないものにする理由(詳細な例の形式で説明することが望ましい)。

  2. なぜこの注釈はプログラマーの裁量に任されているのですか?これはコンパイラがチェックできるはずではありませんか?

  3. 彼の機能が本当に安全であることを保証するために遵守しなければならない標準がありますか?そうでない場合、それを確実にするためのベストプラクティスは何ですか?


1
JavaDocで例(および説明)を見ましたか?
jlordo

2
3番目の質問の場合、1つのプラクティスは常に最初の要素とその他の要素を持つことですretType myMethod(Arg first, Arg... others)。を指定しない場合first、空の配列が許可され、同じ名前で同じ戻り値の型を持ち、引数を取らないメソッドがある可能性があります。つまり、JVMは呼び出すメソッドを決定するのに苦労します。
2013年

4
私がやった@jlordoが、私は理解していない理由は、その機能を可変引数のいずれかが簡単にこのような状況の外をreplecateできるようコンテキストを可変引数で与えられた...(この検証期待タイプの安全上の警告と実行時にエラーが発生してコンパイル)
オレン

回答:


244

1)ジェネリックと可変引数の特定の問題について、インターネットとStackOverflowに多くの例があります。基本的には、型パラメーター型の可変数の引数がある場合です。

<T> void foo(T... args);

Javaでは、可変引数は、コンパイル時に単純な「再書き込み」を受ける構文上の砂糖です。型の可変引数パラメーターは、型のパラメーターにX...変換されますX[]。そして、このvarargsメソッドが呼び出されるたびに、コンパイラーはvarargsパラメーターに入るすべての「変数引数」を収集し、のように配列を作成しますnew X[] { ...(arguments go here)... }

これは、varargs型がのような具象である場合にうまく機能しますString...。のような型変数の場合T...Tその呼び出しの具象型であることがわかっている場合にも機能します。たとえば、上記のメソッドがクラスの一部でFoo<T>あり、Foo<String>参照がある場合、コードのその時点でfooわかってTいるので、それを呼び出しても問題ありませんString

ただし、「値」Tが別の型パラメーターの場合は機能しません。Javaでは、タイプパラメータコンポーネントタイプ(new T[] { ... })の配列を作成することはできません。したがって、Javaは代わりにnew Object[] { ... }(ここObjectにの上限がありTます。上限が何か異なる場合はの代わりにObjectそれを使用します)、コンパイラ警告を表示します。

では、new Object[]代わりに何かを作成することの何が問題になっていnew T[]ますか?まあ、Javaの配列は実行時にコンポーネントの型を知っています。したがって、渡された配列オブジェクトは、実行時に誤ったコンポーネントタイプになります。

おそらく最も一般的なvarargsの使用法では、単に要素を反復処理するだけであれば問題ありません(配列の実行時の型は問題ではありません)。したがって、これは安全です。

@SafeVarargs
final <T> void foo(T... args) {
    for (T x : args) {
        // do stuff with x
    }
}

ただし、渡された配列のランタイムコンポーネントタイプに依存するものについては、安全ではありません。安全ではなく、クラッシュする簡単な例を次に示します。

class UnSafeVarargs
{
  static <T> T[] asArray(T... args) {
    return args;
  }

  static <T> T[] arrayOfTwo(T a, T b) {
    return asArray(a, b);
  }

  public static void main(String[] args) {
    String[] bar = arrayOfTwo("hi", "mom");
  }
}

ここでの問題は、我々はの種類に依存することであるargsことにT[]ようにそれを戻すためにT[]。しかし実際には、実行時の引数の型はのインスタンスではありませんT[]

3)メソッドに型の引数がある場合T...(Tは任意の型パラメーター)、次のようになります。

  • 安全:あなたの方法が配列の要素がのインスタンスであるという事実のみに依存している場合 T
  • 安全でない:配列がインスタンスであるという事実に依存する場合 T[]

配列のランタイムタイプに依存するものには、typeとして返す、type T[]のパラメータに引数として渡す、をT[]使用して配列タイプを取得する、配列.getClass()のランタイムタイプに依存するメソッドに渡すなどがList.toArray()あります。Arrays.copyOf()、など

2)上記で説明した区別は複雑すぎて、自動的に簡単に区別できません。


7
さて、それはすべて理にかなっています。ありがとう。だから、私があなたを完全に理解していることを確認するためFOO<T extends Something>に、varargsメソッドのT []をSomthingsの配列として返すのが安全なクラスがあるとしましょう。
Oren

30
使用することが(おそらく)常に正しい1つのケースは@SafeVarargs、配列で行う唯一のことは、すでに注釈が付けられている別のメソッドに渡すことです(たとえば、私は頻繁にvarargメソッドを作成していることに気付きます)を使用して引数をリストに変換し、Arrays.asList(...)それを別のメソッドに渡すために存在します。このようなケースには、アノテーションが付いている@SafeVarargsためArrays.asList、常にアノテーションを付けることができます)。
ジュール

3
@newacctこれがJava 7の場合かどうかはわかりませんが、Java 8では@SafeVarargs、メソッドがstaticまたはでない限り許可finalされません。
アンディ

3
Java 9ではprivate、オーバーライドすることもできないメソッドでも許可されます。
Dave L.

4

ベストプラクティスについては、これを検討してください。

これがあれば:

public <T> void doSomething(A a, B b, T... manyTs) {
    // Your code here
}

これを次のように変更します。

public <T> void doSomething(A a, B b, T... manyTs) {
    doSomething(a, b, Arrays.asList(manyTs));
}

private <T> void doSomething(A a, B b, List<T> manyTs) {
    // Your code here
}

私は通常、varargsを追加するだけで、呼び出し元にとってより便利になります。ほとんどの場合、内部実装ではを使用する方が便利ですList<>。ですから、Arrays.asList()私を元に戻し、ヒープ汚染を導入する方法がないことを確認するために、これを実行します。

これはあなたの#3にしか答えないことを知っています。newacctは上記の#1と#2に素晴らしい答えを与えており、これをコメントとして残すだけの十分な評判はありません。:P

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.