Java警告“未經檢查的轉換”

    1.概述

    有時,當我們編譯Java源代碼時,編譯器可能會顯示警告消息“unchecked conversion”或“ The expression of type List needs unchecked conversion

    在本教程中,我們將更深入地研究警告消息。我們將討論此警告的含義,可能導致的問題以及如何解決潛在的問題。

    2.啟用Unchecked警告選項

    在研究“ unchecked conversion ”警告之前,我們要確保已啟用用於打印此警告的Java編譯器選項。

    如果我們使用的是Eclipse JDT Compiler ,那麼默認情況下會啟用此警告。

    當我們使用Oracle或OpenJDK javac編譯器時,可以通過添加編譯器選項-Xlint:unchecked.來啟用此警告-Xlint:unchecked.

    通常,我們在IDE中編寫和構建Java程序。我們可以在IDE的編譯器設置中添加此選項。

    例如,以下屏幕截圖顯示瞭如何在JetBrains IntelliJ中啟用此警告:

    Java警告“未經檢查的轉換”

    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.編譯器何時會警告我們:“ unchecked conversion ”?

    在上一節中,我們學習瞭如何通過設置Java編譯器選項來啟用警告。因此,不難想像**“unchecked conversion”是編譯時警告。通常,在將原始類型分配給參數化類型而不進行類型檢查的情況下我們會看到此警告。**

    編譯器允許此分配,因為編譯器必須允許此分配來保持與不支持泛型的較舊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;
    
     }
    
     ...
    
     }
    

    接下來,讓我們創建一個測試方法,該方法調用該方法並將結果分配給類型為List<String>的變量:

    @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] TESTS
    
     [INFO] -------------------------------------------------------
    
     ...
    
     [INFO] Tests run: 13, Failures: 0, Errors: 0, Skipped: 0
    
     [INFO] ------------------------------------------------------------------------
    
     [INFO] BUILD SUCCESS
    
     ...

    如上面的輸出所示,我們已復制了編譯器警告。

    現實世界中的一個典型示例是我們使用Java Persistence API的[Query.getResultList()](https://docs.oracle.com/javaee/7/api/javax/persistence/Query.html#getResultList--)方法。該方法返回原始類型List對象。

    但是,當我們嘗試將原始類型列表分配給具有參數化類型的列表時,我們會在編譯時看到以下警告:

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

    此外,我們知道,如果編譯器向我們發出警告,則意味著存在潛在風險。如果我們查看上面的Maven輸出,我們將看到,儘管我們收到“ unchecked conversion ”警告,但是我們的測試方法可以正常工作。

    自然,我們可能想問為什麼編譯器會用此消息警告我們,以及我們可能會遇到什麼潛在問題?

    接下來,讓我們弄清楚。

    4.為什麼Java編譯器會警告我們?

    即使我們收到“ unchecked conversion ”警告,我們的測試方法也可以在上一節中很好地運行。這是因為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."));
    
     }
    

    如果我們運行上面的測試方法,我們將再次看到“ unchecked conversion ”警告,測試將通過。

    這意味著當我們通過調用get(3)獲得Date對象並將其類型轉換為String.時,拋出了ClassCastException String.

    在現實世界中,根據要求,有時會拋出異常為時已晚。

    例如,我們分配List<String> strList = getRawListWithMixedTypes().對於strList,每個String對象strList,假設我們在相當複雜或昂貴的過程(例如外部API調用或事務性數據庫操作)中使用它。

    當我們在strList中的某個元素上遇到ClassCastException時,一些元素已被處理。因此, ClassCastException來不及,可能會導致一些額外的還原或數據清除過程。

    到目前為止,我們已經了解了“unchecked conversion”警告背後的潛在風險。接下來,讓我們看看如何避免這種風險。

    5.我們該如何處理警告?

    如果允許更改返回原始類型集合的方法,則應考慮將其轉換為通用方法。這樣,將確保類型安全。

    但是,很可能當我們遇到“ unchecked conversion ”警告時,我們正在使用外部庫中的方法。讓我們看看在這種情況下我們可以做什麼。

    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對象。根據查詢,我們確定原始類型列表可以轉換為List<Object[]> 。因此,我們可以在賦值語句上方添加@SuppressWarnings ,以禁止顯示“ unchecked conversion ”警告。

    5.2。在使用原始類型集合之前檢查類型轉換

    警告消息“ unchecked conversion ”表示我們應在分配之前檢查轉換。

    要檢查類型轉換,我們可以遍歷原始類型集合併將每個元素都轉換為我們的參數化類型。這樣,如果有些元素的類型錯誤,我們可以在真正使用該元素之前獲取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.")));
    
     }
    

    當我們構建並執行測試方法時,“ unchecked conversion ”警告消失了,測試通過了。

    當然,如果需要,我們可以更改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.

    六,結論

    在本文中,我們了解了“ unchecked conversion ”編譯器警告是什麼。此外,我們已經討論了此警告的原因以及如何避免潛在風險。