1. 程式人生 > >java ,不要太相信範型,範型失效的兩種情況

java ,不要太相信範型,範型失效的兩種情況

 範型在java程式設計中有許多好處,也是我們平常經常使用的一種減少執行時錯誤的方式;但是在複雜的應用中,尤其是一個底層的模組中,我們不要太相信範型給我門反回的值。

首先:

     Java中範型是編譯時檢查型別,實際在位元組碼檔案中沒有對範型的描述,如果中間有沒有範型的引用做橋樑或者執行時新增資料(通過java反射),那麼範型將會失去效用。下面的程式碼在debug時檢視會發現integers裡面儲存的資料時String型別,這個就是一個天坑。能夠執行且不會執行出錯。

先來驗證 確實沒有存在位元組碼中,我是通過javap -verbose class命令確認的

package flycat;


import org.junit.Test;

import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.util.ArrayList;
import java.util.List;

/**
 * Unit test for simple App.
 */
public class AppTest 
{
    
    /**
     * Rigorous Test :-)
     */
    @Test
    public void test()
    {
        List<Integer> ls  = new ArrayList<>();
        ls.add(4);
    }

}

 javap -verbose AppTest.class

 public void test();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=2, args_size=1
         0: new           #25                 // class java/util/ArrayList 建立物件
         3: dup
         4: invokespecial #26                 // Method java/util/ArrayList."<init>":()V  初始化物件
         7: astore_1
         8: aload_1
         9: iconst_4
        10: invokestatic  #27                 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;將4這個整型物件Integer執行static方法
        13: invokeinterface #28,  2           // InterfaceMethod java/util/List.add:(Ljava/lang/Object;)Z    執行List的add方法
        18: pop
        19: return
      LineNumberTable:
        line 81: 0
        line 82: 8
        line 83: 19
      LocalVariableTable:
        Start  Length  Slot  Name   Signature
            0      20     0  this   Lflycat/AppTest;
            8      12     1    ls   Ljava/util/List;
      LocalVariableTypeTable:
        Start  Length  Slot  Name   Signature
            8      12     1    ls   Ljava/util/List<Ljava/lang/Integer;>;
    RuntimeVisibleAnnotations:
      0: #72()

上面部分程式碼看出,其中沒有看到泛型的影子,List<Integer> 和List是一樣的。

下面是“坑”的程式碼:

public void test()
    {
        List<String> strings = new ArrayList<String>();
        strings.add("sdfsd");
        List objects = strings;
        List<Integer> integers = (List<Integer>) objects;
        System.out.println(integers);
    }

執行結果是

[sdfsd]

這兒明顯是錯誤的,程式碼中泛型宣告是整數型別,但是最後是字串型別。

下面程式碼是一個通過執行時新增資料:

public void test()
    {

        List<Integer> strings = new ArrayList<Integer>();
        strings.add(12);
        Class clazz = null;
        try {
            clazz = Class.forName("java.util.ArrayList");
            Method addMethod = clazz.getMethod("add", Object.class); //必須時Object,因為E範型在java.util.ArrayList上面的為Object
            addMethod.invoke(strings, "asdfasd");
            System.out.println(strings);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

執行結果是

[12, asdfasd]

這兒明顯是錯誤的,程式碼中泛型宣告是整數型別,但是最後是字串型別。

總結:

使用泛型造成錯誤一般都是書寫不規範造成的,而且都是在發生“掩蓋”的過程。

比如上上面的System.out.println(strings)這個程式碼 隱含著把所有資料轉換成String型別,原本的Integer型別轉換成String去了,String也會轉換成String不會發生錯誤,造成這種現象。

怎麼避免:

使用泛型的時候不要使用Object這種廣義的物件,要使用泛型對應的物件,這樣在執行過程中會對值進行Object轉泛型對應的型別,強轉失敗就會報錯。

ps:想要達到執行時新增資料造成範型約束失效,只有通過反射獲取Method物件執行。為什麼不能執行時通過使用者輸入資料新增資料物件讓範型失效,因為使用者輸入資料都是String型別,底層時char的,所有得到的int或者其它的時通過型別轉換得到的,其中沒有使用到範型,這兒新手容易混淆。