web-dev-qa-db-ja.com

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

私は最近、Java @ SafeVarargsアノテーションに出会いました。Java安全でなく、私を去った混乱している(ヒープ中毒?消去されたタイプ?)ので、いくつかのことを知りたい:

  1. @ SafeVarargsの意味で可変長のJava関数が安全ではない理由は何ですか(詳細な例の形式で説明するのが望ましい)?

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

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

166
Oren

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を呼び出しても問題ありません。コード内のその時点でTStringであることがわかっているため.

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

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

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

@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");
  }
}

ここでの問題は、T[]として返すためにargsのタイプがT[]に依存していることです。しかし実際には、実行時の引数の型はT[]のインスタンスではありません。

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

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

配列の実行時の型に依存するものには、型T[]として返す、型T[]のパラメーターへの引数として渡す、.getClass()を使用して配列型を取得する、 List.toArray()Arrays.copyOf()など、配列の実行時のタイプに依存するメソッドに渡す.

2)上記の区別は複雑すぎて、簡単に自動的に区別することはできません。

228
newacct

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

これがある場合:

_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
}
_

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

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

0
Fridge