1. 程式人生 > 其它 >kafka--Struct Streaming--hdfs案例

kafka--Struct Streaming--hdfs案例

技術標籤:Javajava程式語言pythontypescriptvue

型別擦除

編譯器會在編譯期間擦除泛型語法並相應的做出一些型別轉換動作,生成的Java位元組程式碼中是不包含泛型中的型別資訊的

如 下面變數t的型別轉換成了Object

public class Generic<T> {
    private T t;
}

再如

List<Integer> list1=new ArrayList<>();
List<String> list2=new ArrayList<>();
System.out.println(list1.getClass());//class java.util.ArrayList
System.out.println(list1.getClass()==list2.getClass());//true

泛型類並沒有自己獨有的Class類物件,比如並不存在List<String>.class或是List<Integer>.class,而只有List.class

型別擦除的基本過程:

  • 首先找到用來替換型別引數的具體類,這個具體類一般是Object,如果指定了型別引數的上界的話,則使用這個上界
  • 把程式碼中的型別引數都替換成具體的類,同時去掉出現的型別宣告,即去掉<>的內容

指定了上界的情況:

public class Generic<T extends Integer> {
    private T t;
}

注意:不能指定下界

思考:型別擦除之後,get()返回的應該是Object,那為什麼可以得到String型別

List<String> list=new ArrayList<>();
list.add("hh");
String s=list.get(0);

檢視反編譯結果


在型別擦除後,會自動生成 checkcast指令進行強制型別轉換
和直接 String s=(String)list.get(0);的位元組碼是一樣的

萬用字元和上下界

  • 在使用泛型類的時候,既可以指定一個具體的型別,如List<String>就聲明瞭具體的型別是String;也可以用萬用字元?來表示未知型別,如List<?>就聲明瞭List中包含的元素型別是未知的
  • 萬用字元所代表的其實是一組型別,但具體的型別是未知的。List<?>所宣告的就是所有型別都是可以的
  • 但是List<?>並不等同於List<Object>。List<Object>實際上確定了List中包含的是Object及其子類,在使用的時候都可以通過Object來進行引用。而List<?>則其中所包含的元素型別是不確定。其中可能包含的是String,也可能是 Integer。如果它包含了String的話,往裡面新增Integer型別的元素就是錯誤的
  • 正因為型別未知,就不能往List中新增元素,因為編譯器無法知道具體的型別是什麼。但是對於 List<?>中的元素確總是可以用Object來引用的,因為雖然型別未知,但肯定是Object及其子類
ArrayList<?> arrayList=new ArrayList<>();
Object o=arrayList.get(0);
arrayList.add(null);
arrayList.add(1);//編譯錯誤

因為對於List<?>中的元素只能用Object來引用,在有些情況下不是很方便。在這些情況下,可以使用上下界來限制未知型別的範圍。 如List<? extends Number>說明List中可能包含的元素型別是Number及其子類。而List<? super Number>則說明List中包含的是Number及其父類

引數擦除後,List<Integer>和List<Number>都是List型別,但在編譯過程中這兩個是不同的型別

List<Integer> list=new ArrayList<>();
List<Number> list1=new ArrayList<>();
list1=list;//編譯錯誤

Java中 陣列是協變的,即可以把子類陣列賦值給父類陣列

Number[] numbers = new Integer[10];

而泛型卻不可以

ArrayList<Number> numbers= new ArrayList<Integer>();//編譯錯誤

泛型也不能建立具體型別的陣列
如:

List<Integer>[] li2 = new ArrayList<Integer>[];//編譯錯誤
List<Boolean> li3 = new ArrayList<Boolean>[];//編譯錯誤

List<Integer>List<Boolean>在 jvm 中等同於List<Object>,所有的型別資訊都被擦除,程式也無法分辨一個數組中的元素型別具體是 List<Integer>型別還是 List<Boolean>型別

但是藉助無限定萬用字元<?>卻可以

List<?>[] li3 = new ArrayList<?>[10];
li3[0] = new ArrayList<Integer>();
li3[1] = new ArrayList<String>();
List<?> v = li3[1];

List<Integer> list=new ArrayList<>();
List<Number> list1=new ArrayList<>();
list1=list;//假設編譯通過
list1.add(12.1);

假設 Java 允許泛型協變,那麼上述程式碼在編譯器看來是沒問題的,但執行時就會出現問題。這個 add 方法實際上就將一個浮點數放入了整型容器中了,雖然由於型別擦除並不會對程式執行造成問題,但顯然違背了泛型的設計初衷,容易造成邏輯混亂,所以 Java 乾脆禁止泛型協變。

假如有某種需求,方法既要支援子類泛型作為形參傳入,也要支援父類泛型作為形參傳入,可以採用萬用字元

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    List<Number> list1 = new ArrayList<>();
    f(list);
    f(list1);
}

void f(List<? extends Number> list) {
}

引入泛型之後的型別系統增加了兩個維度:一個是型別引數自身的繼承體系結構,另外一個是泛型類或介面自身的繼承體系結構。第一個指的是對於 List<String>List<Object>這樣的情況,型別引數String是繼承自Object的。而第二種指的是 List介面繼承自Collection介面。對於這個型別系統,有如下的一些規則:

相同型別引數的泛型類的關係取決於泛型類自身的繼承體系結構。即List<String>Collection<String> 的子型別,List<String>可以替換Collection<String>。這種情況也適用於帶有上下界的型別宣告。 當泛型類的型別宣告中使用了萬用字元的時候, 其子型別可以在兩個維度上分別展開。如對Collection<? extends Number>來說,其子型別可以在Collection這個維度上展開,即List<? extends Number>Set<? extends Number>等;也可以在Number這個層次上展開,即Collection<Double>Collection<Integer>等。如此迴圈下去,ArrayList<Long>HashSet<Double>等也都算是Collection<? extends Number>的子型別。 如果泛型類中包含多個型別引數,則對於每個型別引數分別應用上面的規則。

Collection<Number>可替換List<Number>
Collection<? extends Number>可替換List<? extends Number>
Collection<? extends Number>可替換List<Number>
Collection<? extends Number>可替換List<Integer>
Collection<? extends Number>可替換List<? extends Integer>
Collection<?>可替換List<Number>
理解了上面的規則之後,ArrayList<Number>是ArrayList<?>的子型別

ArrayList<Number> list = new ArrayList<>();
ArrayList<?> arrayList = list;//不會有編譯錯誤

上界 extends

public void testAdd(List<? extends Parent> list){
    list.add(new Parent());//編譯錯誤
    list.add(new Child1());//編譯錯誤
    list.add(new Child2());//編譯錯誤
    Parent parent = list.get(0);
}

傳入方法的引數可能是List<Parent>,也可能是List<Child1>List<Child2>
假如傳入的引數是List<Child1>,那麼會發生型別不相容的問題
但是卻可以取得List中的元素,因為總可以用Parent來接

下界 super

List<? super Animal> 是 List<? super Bird>的子型別
List<Animal> 是 List<? super Animal>的子型別

public void testAdd(List<? super Parent> list){
    list.add(new GrantParent1());//編譯錯誤
    list.add(new GrantParent2());//編譯錯誤
    list.add(new Parent());
    list.add(new Child());
    Object object = list.get(0);
}

傳入方法的引數可能是List<Parent>,也可能是List<GrantParent1>List<GrantParent2>
假如傳入的引數是List<GrantParent1>List<GrantParent2>List<GrantParent1>不相容,所以只能新增Parent及其子類
如果要獲取List中的元素,Parent的父類那麼多,只能用Object來接

歸根結底可以用一句話表示,那就是編譯器可以支援向上轉型,但不支援向下轉型
上界的list只能get,不能add(確切地說不能add出除null之外的物件,包括Object)add會報編譯錯誤
下界的list只能add,不能get