Java泛型詳解

来源:http://www.cnblogs.com/shizongger/archive/2017/02/25/6443012.html
-Advertisement-
Play Games

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


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語言程式設計 進階篇



您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 0 Asp.Net Core 項目實戰之許可權管理系統(0) 無中生有 1 Asp.Net Core 項目實戰之許可權管理系統(1) 使用AdminLTE搭建前端 2 Asp.Net Core 項目實戰之許可權管理系統(2) 功能及實體設計 3 Asp.Net Core 項目實戰之許可權管理系統(3) 通過 ...
  • 前言 .NET Framework自2002年發佈以來,已經歷了十來個年頭。相應的,.NET平臺上的數據訪問技術也在不斷發展,從最基礎的ADO.NET,到SqlHelper簡單幫助類,到DAAB(Data Access Application Block),再到LINQ,最終演變為現在微軟主推的En ...
  • Entity Framework帶給我們的不僅僅是操作上的方便,而且使用上也很是考慮了用戶的友好交互,EF4.0與vs2010的完美融合也是我們選擇它的一個理由吧。相比Nhibernate微軟這方面做的的確是很不錯。 下麵我將使用CodeFirst方式來做個簡單是實際例子與大家分享。 我們還是和 前 ...
  • 四種引用類型的概念 強引用 StrongReference 如果一個對象具有強引用,那麼垃圾回收器絕對不會回收它,當記憶體不足時寧願拋出 OOM 錯誤,使得程式異常停止。 即是一個強引用。 軟引用 SoftReference 如果一個對象只具有軟引用,那麼垃圾回收器在記憶體充足的時候不會回收它,而在記憶體 ...
  • 一, java多線程 線程與進程 進程: 程式(任務)的執行過程,擁有資源(共用記憶體,共用資源)和線程(一個或者多個,至少一個)。 例如:打開任務管理器,qq,chrome,都屬於進程。 線程: 系統中最小的執行單元,同一進程中有多個線程,線程共用進程的資源。 例如:qq的聊天發送消息。 單線程:程 ...
  • 1. MyBaits基本概念:略 2. 案例:需求:建立資料庫查詢,一對一 3.開發步驟: (1).建立資料庫+表 (2).加包 (3).定義實體類 (4).編寫db.properties,將資料庫信息寫入其中;編寫MyBatis的配置文件conf.xml文件,引入db.properties;引入別 ...
  • 嗯,這篇講可用的多線程記憶體池。 零、上期彩蛋:不要重載全局new 或許,是一次很不愉快的經歷,所以在會有這麼一個“認識”。反正,大概就是:即使你足夠聰明,也不要自作聰明;在這就是不要重載全局new,無論你有著怎樣的目的和智商。因為: 這是一個不對稱的介面:只告訴了我們如何創建一個【堆】對象,但是釋放 ...
  • 1 package cn.temptation; 2 3 public class Sample01 { 4 public static void main(String[] args) { 5 System.out.println("第一個例子"); 6 } 7 } 1 package cn.te... ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...