広告

Java : @SafeVarargs (アノテーション) - API使用例

SafeVarargs (Java SE 20 & JDK 20) の使用例まとめです。
@SafeVarargs アノテーションは、メソッドの可変パラメータに対して "安全ではない操作を実行しない" ことを明示します。
API仕様のおともにどうぞ。


概要

注釈の付いたメソッドやコンストラクタの本体が自身の可変パラメータに対して安全でない可能性のある操作を実行しないことを示す、プログラマ・アサーションです。

クラス構成

@SafeVarargs は、

  • メソッドの可変パラメータに対して 安全ではない操作 は実行しない

ということを明示するためのアノテーションです。

メソッドで可変パラメータを使うと、その定義によってはコンパイルで警告が出ることがあります。
その警告の意味をよく理解して、それでもその定義を使いたい … という場合に @SafeVarargs を使うと、警告を抑止することができます。


警告が出る例

警告が出る例を見てみましょう。

ジェネリクス(総称型) の Foo クラスです。
m メソッドは T 型の可変パラメータを受け取ります。

public class Foo<T> {
    public void m(T... args) {
        System.out.println(Arrays.toString(args));
    }
}

さて、この Foo クラスをコンパイルすると、次のような警告が出ます。

> javac -Xlint:unchecked Foo.java
Foo.java:6: 警告: [unchecked] パラメータ化された可変引数型Tからのヒープ汚染の可能性があります
    public void m(T... args) {
                       ^
  Tが型変数の場合:
    クラス Fooで宣言されているT extends Object
警告1個

ちなみに、m メソッドは 安全ではない操作 はしていません。
しかし、コンパイルのたびに警告が出るのは、ちょっといやですよね…

そんなときに使えるのが @SafeVarargs アノテーションです。

public class Foo<T> {
    @SafeVarargs
    public final void m(T... args) {
        System.out.println(Arrays.toString(args));
    }
}

m メソッドに @SafeVarargs をつけました。

あと、final もつけています。
これは @SafeVarargs を使う条件の1つです。static でも OK です。

つまり、m メソッドをオーバーライドして 安全ではない操作 を実行する … ということがないようにですね。

それでは、修正した Foo クラスをコンパイルしてみましょう。

> javac -Xlint:unchecked Foo.java
 <エラー、警告なし>

無事に、警告なしでコンパイルができました。

ただし、警告が出るからといって、安易に @SafeVarargs を使うことはやめたほうがよいでしょう。

  • 本当に警告を抑止してよいのか?
  • メソッドのパラメータ定義を変更して改善できないか?

などをよく検討して、どうしても必要であれば @SafeVarargs を使いましょう。

安全ではない操作の例

安全ではない操作のコード例です。

public class Bar {
    public static List<String>[] m(List<String>... args) {
        if (0 < args.length) {
            Object[] objects = args;
            objects[args.length - 1] = List.of(123);
        }

        return args;
    }
}

この Bar クラスの m メソッドは、意図的に 安全ではない操作 をしています。

パラメータの args は List<String> の配列ですが、

Object[] objects = args;
objects[args.length - 1] = List.of(123); // List<String> 以外の型を代入!

この操作で、List<Integer> 型のオブジェクトを代入しています。

それでは、Bar.m メソッドを使ってみましょう。

List<String>[] listArray = Bar.m(
        List.of("aaa"),
        List.of("bbb"),
        List.of("ccc")
);

try {
    System.out.println("-- listArray --");
    for (final var list : listArray) {
        if (!list.isEmpty()) {
            String str = list.get(0);
            System.out.println(str);
        }
    }
} catch (ClassCastException e) {
    System.out.println("ClassCastException!");
}

// 結果
// ↓
//-- listArray --
//aaa
//bbb
//ClassCastException!

使う側は、とくにおかしな操作はしていませんが、ClassCastException が発生してしまいました。
このように、ジェネリクス(総称型)の配列は、操作によっては安全でないことがあります。

そのため、警告が出る例 で見たように、Java のコンパイラが警告で知らせてくれているわけですね。


関連記事

ページの先頭へ