程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java泛型詳解

Java泛型詳解

編輯:關於JAVA

Java泛型詳解。本站提示廣大學習愛好者:(Java泛型詳解)文章只能為提供參考,不一定能成為您想要的結果。以下是Java泛型詳解正文


Java泛型是JDK1.5加入的新特性。泛型是指參數化的能力。可以定義帶泛型的類型的類或者方法,編譯時期編譯器會用具體的類型來代替它。Java泛型有泛型類、泛型接口和泛型方法。泛型的主要優點是能夠在編譯時期而不是在運行時期就檢測出錯誤。

泛型的出現

在JDK1.5之前,java.lang.Comparable的定義如下所示:

public interface Comparable {
    
    public int comparaTo(Object o);
}

在JDK1.5之後,泛型的定義如下:

public interface Comparable<T> {
    
    public int comparaTo(T o);
}

這裡的<T>表示形式形式泛型類型,之後可以用一個實際的具體類型來替換它。替換泛型稱為泛型實例化。按照慣例,像E或T這樣的單個字母用於表示一個形式泛型類型。為了看到泛型的具體好處,我們來看具體的實例。
這裡寫圖片描述
圖1
這裡寫圖片描述
圖2
由於Date實現了Comparable接口,由Java的多態特性,我們可以用父類的指針指向子類,也就是我們可以new一個Date類型賦值給我們的Comparable接口類型。當我們調用Comparable接口的comparaTo()方法時。由於圖1沒有指定泛型,編譯時期不會出現提示,但是在運行時期會報出:java.lang.String cannot be cast to java.util.Date的錯誤,提示信息提示String類型不能轉換為Date進行比較。而使用了泛型了圖2,在編譯期間就提示錯誤,因為傳遞給compareTo方法的參數必須是Date類型。由於這個錯誤是在編譯器而不是運行期被檢測到,因而泛型使程序更加可靠。

泛型類、接口、方法的定義

現在我們來實現一個線性表list,命名為GenericArrayList,可以接收泛型數據。該類實現了add()添加元素的方法,size()獲取元素個數的方法,和獲取指定下標元素的get()方法。

public class GenericArrayList<E> {
    
  Object[] objects=new Object[10];

  int index=0;
  
  public GenericArrayList(){
      System.out.println("構造函數");
  }

  public void add(E o){

   if(index==objects.length){
      Object[] newObjects=new Object[objects.length*2];
      System.arraycopy(objects, 0, newObjects, 0, objects.length);
      objects=newObjects;
    }
     objects[index]=o;

     index++;
  }

    public int size(){
        
       return index;
    }

    public E get(int index) {
        return (E) objects[index];
    }
}

下面代碼片段將向list中添加三個城市名,然後再將城市名依次取出。

        GenericArrayList<String> ga1 = new GenericArrayList<String>();
        ga1.add("北京");
        ga1.add("貴陽");
        ga1.add("重慶");
        for(int i = 0; i < ga1.size(); i++) {
            System.out.println(ga1.get(i));
        }

同樣的,可以向list中添加如數字10086,然後再將數字依次取出。

        GenericArrayList<Integer> ga2 = new GenericArrayList<Integer>();
        ga2.add(1);
        ga2.add(0);
        ga2.add(0);
        ga2.add(8);
        ga2.add(6);
        for(int i = 0; i < ga2.size(); i++) {
            System.out.println(ga2.get(i));
        }

注意:

1.上面創建的兩個GenericArrayList對象ga1和ga2,他們創建的語法分別是:new GenericArrayList<String>()和new GenericArrayList<Integer>(),但是千萬不要認為我的GenericArrayList類中分別對應兩個這樣的構造方法。

  public GenericArrayList<String>(){
      System.out.println("構造函數");
  }
  public GenericArrayList<Integer>(){
      System.out.println("構造函數");
  }

而實際上,我的構造方法是在第7行定義的。

2.有時候泛型的參數有多個,那麼我們可以把所有的參數一起放在間括號裡面,如<E1,E2,E3>。

3.可以定義一個類或一個接口作為作為泛型或者接口的子類型。例如,在Java API中,java.lang.String類被定義為實現Comparable接口,如下所示:

public final class String implements java.io.Serializable, Comparable<String>, CharSequence

定義泛型方法:

public class Test2 {

    public static void main(String[] args) {
        Integer[] arr1 = {1, 0, 0, 8, 6, 1, 1};
        Test2.<Integer>pint(arr1);
        
        String[] names = {"馬雲", "馬化騰", "李彥宏"};
        Test2.<String>pint(names);
    }
    
    public static <E> void pint(E[] arr) {
        for(int i = 0; i < arr.length; i++) {
            System.out.print(arr[i] + " ");
        }
        System.out.println();
    }
}

上訴代碼定義了打印數組的print方法,arr1是一個整型的數組,而arr2是一個字符串類型的數組,當他們調用print時,分別將數組的內容輸出。

為了調用泛型方法,需要將實際類型放在間括號作為方法名的前綴。如,
Test2.

通配泛型

假設我們要定義一個泛型方法,找出list中的最大值。那麼代碼可以參考如下:

public class Test3 {

    public static void main(String[] args) {
        GenericArrayList<Integer> ga = new GenericArrayList<Integer>();
        ga.add(1);
        ga.add(2);
        ga.add(3);
        Test3.max(ga);
    }

    public static double max(GenericArrayList<Number> list) {
        double maxValue = list.get(0).doubleValue();
        for(int i = 0; i < list.size(); i++) {
            double value = list.get(i).doubleValue();
            if(value > maxValue) {
                maxValue = value;
            }
        }
        return maxValue;
    }
}

首先new出一個list對象,並向list裡面添加元素1,2,3,然後調用max方法。max方法的邏輯是依次取出list裡面的元素,與我們的標記maxValue對比,如果大於maxValue當前元素值,就把當前元素值賦值給maxValue。
但是,上面的代碼編譯會錯誤,因為ga不是GenericArrayList<Number&glt; 的對象,所以不能調用max()方法。
這裡寫圖片描述
盡管Integer是Number的子類(除Integer之外,還有Short,Byte,Long,Float,Double等也是Number的子類),但是GenericArrayList<Integer>不是GenericArrayList<Number>的子類。
解決的方案是使用通配泛型。只需要把max的方法頭改寫如下即可:

public static <E> double max(GenericArrayList<? extends Number> list)

通配泛型有三種形式:

  1. ?
  2. ? extends T
  3. ? super T

第一種稱為非受限制通配,和? extends Oject是一樣的。第二種稱為受限制通配,表示T或T的一個未知子類型。第三種稱為下限通配,表示T或T的的一個父類。
第二種通配泛型上面的案例已經使用過,下面我們來看第一種類型。案例如下:

public class Test4 {

    public static void main(String[] args) {
        GenericArrayList<Integer> ga = new GenericArrayList<Integer>();
        ga.add(1);
        ga.add(2);
        ga.add(3);
        Test4.print(ga);
        
        GenericArrayList<Person> ga1 = new GenericArrayList<Person>();
        ga1.add(new Person("馬雲"));
        ga1.add(new Person("李彥宏"));
        ga1.add(new Person("馬化騰"));
        Test4.print(ga1);
    }
    
    public static void print(GenericArrayList<?> list) {
        for(int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
        System.out.println();
    }
}

Person類定義如下:

public class Person {
    private String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [name=" + name + "]";
    }
}

為了輸出我們的Person對象,需要對Person的toString()方法重寫。main方法中new了兩個GenericArrayList對象,一個的實際參數是Integer型list,另一個是Person對象的list。案例的輸出如下:

構造函數
1 2 3
構造函數
Person [name=馬雲] Person [name=李彥宏] Person [name=馬化騰]

這裡如果把?換成Object則報錯。眾所周知:無論是Integer還是Person都繼承自Object,因為Obejct是所有類的父類。但是,GenericArrayList<Person>不是GenericArrayList<Object>的子類。

現在來看看第三種通配泛型的用法。

public class Test5 {

    public static void main(String[] args) {
        GenericArrayList<Object> ga = new GenericArrayList<Object>();
        ga.add(1);
        ga.add(2);
        ga.add(3);
        
        GenericArrayList<Person> ga1 = new GenericArrayList<Person>();
        ga1.add(new Person("馬雲"));
        ga1.add(new Person("李彥宏"));
        ga1.add(new Person("馬化騰"));
        
        Test5.add(ga1, ga);
        //調用Test4的泛型輸出方法
        Test4.print(ga);
    }
    
    //該方法的功能是將list1添加到list2
    public static <T> void add(GenericArrayList<T> list1, GenericArrayList<? super T> list2) {
        for(int i = 0; i < list1.size(); i++) {
//          list1.add(list2.get(i));
            list2.add(list1.get(i));
        }
    }

}

上訴代碼,我們想將一個Person的List追加到Integer的List中去。先創建ga對象,該對象的實際類型是Object,賦值1,2,3的時候自動裝箱編程Integer,屬於Object的子類。ga1的實際類型是Person,屬於Object,符合Person super Object。控制台輸出如下:

構造函數
構造函數
1 2 3 Person [name=馬雲] Person [name=李彥宏] Person [name=馬化騰]

控制台的第一行和第二行“構造函數”是在我們new GenericArrayList對象的時候打印的。第三行,成功的將合並後的list打印出來,前三個元素是整型元素,後三個為Person對象的屬性值。

類型擦除

泛型是使用一種稱為類型擦除的方法來實現的,編譯器使用泛型類型信息來編譯代碼,然後會查擦除它。在生成的Java字節代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數,會被編譯器在編譯的時候去掉。這個過程就稱為類型擦除。如在代碼中定義的List<Object>和List<String>等類型,在編譯之後都會變成List。JVM看到的只是List,而由泛型附加的類型信息對JVM來說是不可見的。

ArrayList<String> list1 = new ArrayList<String>();
ArrayList<Integer> list2 = new ArrayList<Integer>();

盡管編譯時期,ArrayList<String>和ArrayList<Integer> 是兩個不同的類型,但是編譯成字節碼之後,只有一中類型ArrayList。因此以下兩行輸入都為true;

System.out.println(list1 instanceof ArrayList);
System.out.println(list2 instanceof ArrayList);

參考資料:
Java深度歷險(五)——Java泛型
Java語言程序設計 進階篇


  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved