1. 概要

Javaソースをコンパイルするときに、コンパイラが警告メッセージ「未チェックの変換」または「型リストの式には未チェックの変換が必要です」を出力する場合があります。

このチュートリアルでは、警告メッセージについて詳しく見ていきます。 この警告の意味、警告が引き起こす可能性のある問題、および潜在的な問題を解決する方法について説明します。

2. チェックなし警告オプションを有効にする

未チェックの変換」警告を調べる前に、この警告を出力するJavaコンパイラオプションが有効になっていることを確認しましょう。

Eclipse JDTコンパイラを使用している場合、この警告はデフォルトで有効になっています。

OracleまたはOpenJDKjavacコンパイラを使用している場合、コンパイラオプション-Xlint:unchecked。を追加することで、この警告を有効にできます。

通常、JavaプログラムはIDEで作成および構築します。 このオプションは、IDEのコンパイラ設定に追加できます。

たとえば、次のスクリーンショットは、 JetBrainsIntelliJでこの警告がどのように有効になっているのかを示しています。

Apache Maven は、Javaアプリケーションを構築するために広く使用されているツールです。 maven-compiler-plugincompilerArgumentsを構成して、このオプションを有効にすることができます。

<build>
...
    <plugins>
    ...
        <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-compiler-plugin</artifactId>
            ...
            <configuration>
                ...
                <compilerArguments>
                    <Xlint:unchecked/>
                </compilerArguments>
            </configuration>
        </plugin>
    </plugins>
</build>

Javaコンパイラでこの警告オプションが有効になっていることを確認したので、この警告を詳しく見てみましょう。

3. コンパイラはいつ警告しますか:「未チェックの変換」?

前のセクションでは、Javaコンパイラオプションを設定して警告を有効にする方法を学びました。 したがって、「未チェックの変換」がコンパイル時の警告であることを想像するのは難しいことではありません。通常、は、型チェックなしでraw型をパラメータ化された型に割り当てるときにこの警告が表示されます。

コンパイラは、ジェネリックスをサポートしない古いJavaバージョンとの下位互換性を維持するために、この割り当てを許可する必要があるため、この割り当てはコンパイラによって許可されます。

例で簡単に説明します。 生の型Listを返す簡単なメソッドがあるとしましょう。

public class UncheckedConversion {
    public static List getRawList() {
        List result = new ArrayList();
        result.add("I am the 1st String.");
        result.add("I am the 2nd String.");
        result.add("I am the 3rd String.");
        return result;
    }
...
}

次に、メソッドを呼び出し、その結果を次のタイプの変数に割り当てるテストメソッドを作成しましょう。 リスト

@Test
public void givenRawList_whenAssignToTypedList_shouldHaveCompilerWarning() {
    List<String> fromRawList = UncheckedConversion.getRawList();
    Assert.assertEquals(3, fromRawList.size());
    Assert.assertEquals("I am the 1st String.", fromRawList.get(0));
}

ここで、上記のテストをコンパイルすると、Javaコンパイラからの警告が表示されます。

Mavenを使用してプログラムをビルドしてテストしましょう。

$ mvn clean test
...
[WARNING] .../UncheckedConversionDemoUnitTest.java:[12,66] unchecked conversion
  required: java.util.List<java.lang.String>
  found:    java.util.List
...
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
...
[INFO] Tests run: 13, Failures: 0, Errors: 0, Skipped: 0
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
...

上記の出力が示すように、コンパイラの警告を再現しました。

実世界での典型的な例は、 Java Persistence APIQuery.getResultList()メソッドを使用する場合です。 このメソッドは、生のタイプListオブジェクトを返します。

ただし、生の型リストをパラメーター化された型のリストに割り当てようとすると、コンパイル時に次の警告が表示されます。

List<MyEntity> results = entityManager.createNativeQuery("... SQL ...", MyEntity.class).getResultList();

さらに、コンパイラーが何かを警告した場合、それは潜在的なリスクがあることを意味することを私たちは知っています。 上記のMaven出力を確認すると、「 uncheckedconversion 」という警告が表示されますが、テストメソッドは問題なく機能することがわかります。

当然、コンパイラがこのメッセージで警告する理由と、発生する可能性のある潜在的な問題について質問したい場合があります。

次に、それを理解しましょう。

4. Javaコンパイラが警告するのはなぜですか?

未チェックの変換」という警告が表示された場合でも、前のセクションでテスト方法はうまく機能します。 これは、 getRawList()メソッドが返されたリストにStringのみを追加するためです。

それでは、メソッドを少し変更しましょう。

public static List getRawListWithMixedTypes() {
    List result = new ArrayList();
    result.add("I am the 1st String.");
    result.add("I am the 2nd String.");
    result.add("I am the 3rd String.");
    result.add(new Date());
    return result;
}

新しいgetRawListWithMixedTypes()メソッドでは、返されたリストにDateオブジェクトを追加します。 任意の型を含むことができる生の型リストを返すので、それは許可されます。

次に、 getRawListWithMixedTypes()メソッドを呼び出して戻り値をテストする新しいテストメソッドを作成しましょう。

@Test(expected = ClassCastException.class)
public void givenRawList_whenListHasMixedType_shouldThrowClassCastException() {
    List<String> fromRawList = UncheckedConversion.getRawListWithMixedTypes();
    Assert.assertEquals(4, fromRawList.size());
    Assert.assertFalse(fromRawList.get(3).endsWith("String."));
}

上記のテストメソッドを実行すると、「 uncheckedconversion 」という警告が再度表示され、テストに合格します。

これは、 get(3)を呼び出して Date オブジェクトを取得し、その型を Stringにキャストしようとすると、ClassCastExceptionがスローされたことを意味します。[ X164X]

現実の世界では、要件によっては、例外がスローされるのが遅すぎる場合があります。

たとえば、 リスト strList = getRawListWithMixedTypes()。 それぞれについてのオブジェクト strList、 外部API呼び出しやトランザクションデータベース操作など、非常に複雑または高価なプロセスで使用するとします。

strListの要素でClassCastExceptionが発生すると、一部の要素が処理されました。 したがって、 ClassCastException は遅すぎて、追加の復元またはデータクリーンアッププロセスが発生する可能性があります。

これまでのところ、「未チェックの変換」警告の背後にある潜在的なリスクを理解しています。 次に、リスクを回避するために何ができるか見てみましょう。

5. 警告で何をしますか?

生の型コレクションを返すメソッドを変更できる場合は、それをジェネリックメソッドに変換することを検討する必要があります。 このようにして、型の安全性が確保されます。

ただし、「 uncheckedconversion 」という警告が表示された場合は、外部ライブラリのメソッドを使用している可能性があります。 この場合に何ができるか見てみましょう。

5.1. 警告の抑制

注釈SuppressWarnings( “unchecked”)を使用して、警告を抑制することができます。

ただし、 @SuppressWarnings( “unchecked”)アノテーションは、型キャストが安全であることが確実な場合にのみ使用する必要があります。これは、型チェックなしで警告メッセージを抑制するだけだからです。

例を見てみましょう:

Query query = entityManager.createQuery("SELECT e.field1, e.field2, e.field3 FROM SomeEntity e");
@SuppressWarnings("unchecked")
List<Object[]> list = query.list();

前述したように、JPAの Query.getResultList()メソッドは、生の型指定されたListオブジェクトを返します。 私たちのクエリに基づいて、生の型リストをにキャストできると確信していますリスト 。 したがって、割り当てステートメントの上に @SuppressWarnings を追加して、「uncheckedconversion」警告を抑制することができます。

5.2. Raw型コレクションを使用する前に型変換を確認する

警告メッセージ「uncheckedconversion」は、割り当ての前に変換を確認する必要があることを意味します。

型変換を確認するために、生の型コレクションを調べて、すべての要素をパラメーター化された型にキャストできます。 このように、間違ったタイプの要素がいくつかある場合、その要素を実際に使用する前にClassCastExceptionを取得できます。

型変換を行うための汎用メソッドを構築できます。 特定の要件に応じて、ClassCastExceptionをさまざまな方法で処理できます。

まず、間違ったタイプの要素を除外するとします。

public static <T> List<T> castList(Class<? extends T> clazz, Collection<?> rawCollection) {
    List<T> result = new ArrayList<>(rawCollection.size());
    for (Object o : rawCollection) {
        try {
            result.add(clazz.cast(o));
        } catch (ClassCastException e) {
            // log the exception or other error handling
        }
    }
    return result;
}

上記のcastList()メソッドを単体テストメソッドでテストしてみましょう。

@Test
public void givenRawList_whenAssignToTypedListAfterCallingCastList_shouldOnlyHaveElementsWithExpectedType() {
    List rawList = UncheckedConversion.getRawListWithMixedTypes();
    List<String> strList = UncheckedConversion.castList(String.class, rawList);
    Assert.assertEquals(4, rawList.size());
    Assert.assertEquals("One element with the wrong type has been filtered out.", 3, strList.size());
    Assert.assertTrue(strList.stream().allMatch(el -> el.endsWith("String.")));
}

テストメソッドをビルドして実行すると、「 uncheckedconversion 」という警告が消え、テストに合格します。

もちろん、必要に応じて、 castList()メソッドを変更して型変換を終了し、間違った型が検出されたらすぐにClassCastExceptionをスローできます。

public static <T> List<T> castList2(Class<? extends T> clazz, Collection<?> rawCollection) 
  throws ClassCastException {
    List<T> result = new ArrayList<>(rawCollection.size());
    for (Object o : rawCollection) {
        result.add(clazz.cast(o));
    }
    return result;
}

いつものように、 castList2()メソッドをテストするための単体テストメソッドを作成しましょう。

@Test(expected = ClassCastException.class)
public void givenRawListWithWrongType_whenAssignToTypedListAfterCallingCastList2_shouldThrowException() {
    List rawList = UncheckedConversion.getRawListWithMixedTypes();
    UncheckedConversion.castList2(String.class, rawList);
}

上記のテストメソッドは、実行すると合格します。 これは、 rawList に間違った型の要素があると、 castList2()メソッドが型変換を停止し、ClassCastException。をスローすることを意味します。

6. 結論

この記事では、「未チェックの変換」コンパイラ警告とは何かを学びました。 さらに、この警告の原因と潜在的なリスクを回避する方法について説明しました。

いつものように、この記事のコードはすべてGitHub利用できます。