泛型就這麼簡單

来源:https://www.cnblogs.com/Java3y/archive/2018/04/01/8687951.html
-Advertisement-
Play Games

前言 從今天開始進入Java基礎的複習,可能一個星期會有一篇的,我寫博文的未必都是正確的~如果有寫錯的地方請大家多多包涵並指正~ 今天要複習的是泛型,泛型在Java中也是個很重要的知識點,本文主要講解基礎的概念,並不是高深的知識,如果基礎好的同學可以當複習看看~ 一、什麼是泛型? Java泛型設計原 ...


前言

從今天開始進入Java基礎的複習,可能一個星期會有一篇的<十道簡單演算法>,我寫博文的未必都是正確的~如果有寫錯的地方請大家多多包涵並指正~

今天要複習的是泛型,泛型在Java中也是個很重要的知識點,本文主要講解基礎的概念,並不是高深的知識,如果基礎好的同學可以當複習看看~

一、什麼是泛型?

Java泛型設計原則:只要在編譯時期沒有出現警告,那麼運行時期就不會出現ClassCastException異常.

泛型:把類型明確的工作推遲到創建對象或調用方法的時候才去明確的特殊的類型

參數化類型:

  • 把類型當作是參數一樣傳遞
  • <數據類型> 只能是引用類型

相關術語:

  • ArrayList<E>中的E稱為類型參數變數
  • ArrayList<Integer>中的Integer稱為實際類型參數
  • 整個稱為ArrayList<E>泛型類型
  • 整個ArrayList<Integer>稱為參數化的類型ParameterizedType

二、為什麼需要泛型

早期Java是使用Object來代表任意類型的,但是向下轉型有強轉的問題,這樣程式就不太安全

首先,我們來試想一下:沒有泛型,集合會怎麼樣

  • Collection、Map集合對元素的類型是沒有任何限制的。本來我的Collection集合裝載的是全部的Dog對象,但是外邊把Cat對象存儲到集合中,是沒有任何語法錯誤的。
  • 把對象扔進集合中,集合是不知道元素的類型是什麼的,僅僅知道是Object。因此在get()的時候,返回的是Object。外邊獲取該對象,還需要強制轉換

有了泛型以後:

  • 代碼更加簡潔【不用強制轉換】
  • 程式更加健壯【只要編譯時期沒有警告,那麼運行時期就不會出現ClassCastException異常】
  • 可讀性和穩定性【在編寫集合的時候,就限定了類型】

2.1有了泛型後使用增強for遍歷集合

在創建集合的時候,我們明確了集合的類型了,所以我們可以使用增強for來遍歷集合!

        //創建集合對象
        ArrayList<String> list = new ArrayList<>();

        list.add("hello");
        list.add("world");
        list.add("java");

        //遍歷,由於明確了類型.我們可以增強for
        for (String s : list) {
            System.out.println(s);
        }

三、泛型基礎

3.1泛型類

泛型類就是把泛型定義在類上,用戶使用該類的時候,才把類型明確下來....這樣的話,用戶明確了什麼類型,該類就代表著什麼類型...用戶在使用的時候就不用擔心強轉的問題,運行時轉換異常的問題了。

  • 在類上定義的泛型,在類的方法中也可以使用!

/*
    1:把泛型定義在類上
    2:類型變數定義在類上,方法中也可以使用
 */
public class ObjectTool<T> {
    private T obj;

    public T getObj() {
        return obj;
    }

    public void setObj(T obj) {
        this.obj = obj;
    }
}
  • 測試代碼:

用戶想要使用哪種類型,就在創建的時候指定類型。使用的時候,該類就會自動轉換成用戶想要使用的類型了。


    public static void main(String[] args) {
        //創建對象並指定元素類型
        ObjectTool<String> tool = new ObjectTool<>();

        tool.setObj(new String("鐘福成"));
        String s = tool.getObj();
        System.out.println(s);


        //創建對象並指定元素類型
        ObjectTool<Integer> objectTool = new ObjectTool<>();
        /**
         * 如果我在這個對象里傳入的是String類型的,它在編譯時期就通過不了了.
         */
        objectTool.setObj(10);
        int i = objectTool.getObj();
        System.out.println(i);
    }

3.2泛型方法

前面已經介紹了泛型類了,在類上定義的泛型,在方法中也可以使用.....

現在呢,我們可能就僅僅在某一個方法上需要使用泛型....外界僅僅是關心該方法,不關心類其他的屬性...這樣的話,我們在整個類上定義泛型,未免就有些大題小作了。

  • 定義泛型方法....泛型是先定義後使用的
    ```java

    //定義泛型方法..
    public

    }

```

  • 測試代碼:

用戶傳遞進來的是什麼類型,返回值就是什麼類型了

    public static void main(String[] args) {
        //創建對象
        ObjectTool tool = new ObjectTool();

        //調用方法,傳入的參數是什麼類型,返回值就是什麼類型
        tool.show("hello");
        tool.show(12);
        tool.show(12.5);

    }

3.3泛型類派生出的子類

前面我們已經定義了泛型類,泛型類是擁有泛型這個特性的類,它本質上還是一個Java類,那麼它就可以被繼承

那它是怎麼被繼承的呢??這裡分兩種情況

  1. 子類明確泛型類的類型參數變數
  2. 子類不明確泛型類的類型參數變數

3.3.1子類明確泛型類的類型參數變數

  • 泛型介面

/*
    把泛型定義在介面上
 */
public interface Inter<T> {
    public abstract void show(T t);

}
  • 實現泛型介面的類.....

/**
 * 子類明確泛型類的類型參數變數:
 */

public class InterImpl implements Inter<String> {
    @Override
    public void show(String s) {
        System.out.println(s);

    }
}

3.3.2子類不明確泛型類的類型參數變數

  • 當子類不明確泛型類的類型參數變數時,外界使用子類的時候,也需要傳遞類型參數變數進來,在實現類上需要定義出類型參數變數

/**
 * 子類不明確泛型類的類型參數變數:
 *      實現類也要定義出<T>類型的
 *
 */
public class InterImpl<T> implements Inter<T> {

    @Override
    public void show(T t) {
        System.out.println(t);

    }
}

測試代碼:

    public static void main(String[] args) {
        //測試第一種情況
        //Inter<String> i = new InterImpl();
        //i.show("hello");

        //第二種情況測試
        Inter<String> ii = new InterImpl<>();
        ii.show("100");

    }

值得註意的是:

  • 實現類的要是重寫父類的方法,返回值的類型是要和父類一樣的!
  • 類上聲明的泛形只對非靜態成員有效

3.4類型通配符

為什麼需要類型通配符????我們來看一個需求.......

現在有個需求:方法接收一個集合參數,遍歷集合併把集合元素列印出來,怎麼辦?

  • 按照我們沒有學習泛型之前,我們可能會這樣做:

public void test(List list){


    for(int i=0;i<list.size();i++){
        
        System.out.println(list.get(i));
    
    }
}

上面的代碼是正確的,只不過在編譯的時候會出現警告,說沒有確定集合元素的類型....這樣是不優雅的...

  • 那我們學習了泛型了,現在要怎麼做呢??有的人可能會這樣做:

public void test(List<Object> list){


    for(int i=0;i<list.size();i++){
        
        System.out.println(list.get(i));
    
    }
}

這樣做語法是沒毛病的,但是這裡十分值得註意的是:該test()方法只能遍歷裝載著Object的集合!!!

強調:泛型中的<Object>並不是像以前那樣有繼承關係的,也就是說List<Object>List<String>是毫無關係的!!!!

那現在咋辦???我們是不清楚List集合裝載的元素是什麼類型的,List<Objcet>這樣是行不通的........於是Java泛型提供了類型通配符 ?

所以代碼應該改成這樣:


public void test(List<?> list){


    for(int i=0;i<list.size();i++){
        
        System.out.println(list.get(i));
    
    }
}

?號通配符表示可以匹配任意類型,任意的Java類都可以匹配.....

現在非常值得註意的是,當我們使用?號通配符的時候:就只能調對象與類型無關的方法,不能調用對象與類型有關的方法。

記住,只能調用與對象無關的方法,不能調用對象與類型有關的方法。因為直到外界使用才知道具體的類型是什麼。也就是說,在上面的List集合,我是不能使用add()方法的。因為add()方法是把對象丟進集合中,而現在我是不知道對象的類型是什麼。


3.4.1設定通配符上限

首先,我們來看一下設定通配符上限用在哪裡....

現在,我想接收一個List集合,它只能操作數字類型的元素【Float、Integer、Double、Byte等數字類型都行】,怎麼做???

我們學習了通配符,但是如果直接使用通配符的話,該集合就不是只能操作數字了。因此我們需要用到設定通配符上限

    List<? extends Number>

上面的代碼表示的是:List集合裝載的元素只能是Number的子類或自身


    public static void main(String[] args) {


        //List集合裝載的是Integer,可以調用該方法
        List<Integer> integer = new ArrayList<>();
        test(integer);

        //List集合裝載的是String,在編譯時期就報錯了
        List<String> strings = new ArrayList<>();
        test(strings);

    }


    public static void test(List<? extends Number> list) {
        
    }

3.4.2設定通配符下限

既然上面我們已經說瞭如何設定通配符的上限,那麼設定通配符的下限也不是陌生的事了。直接來看語法吧

    //傳遞進來的只能是Type或Type的父類
    <? super Type>

設定通配符的下限這並不少見,在TreeSet集合中就有....我們來看一下

    public TreeSet(Comparator<? super E> comparator) {
        this(new TreeMap<>(comparator));
    }

那它有什麼用呢??我們來想一下,當我們想要創建一個TreeSet<String>類型的變數的時候,並傳入一個可以比較String大小的Comparator。

那麼這個Comparator的選擇就有很多了,它可以是Comparator<String>,還可以是類型參數是String的父類,比如說Comparator<Objcet>....

這樣做,就非常靈活了。也就是說,只要它能夠比較字元串大小,就行了

值得註意的是:無論是設定通配符上限還是下限,都是不能操作與對象有關的方法,只要涉及到了通配符,它的類型都是不確定的!

3.5通配符和泛型方法

大多時候,我們都可以使用泛型方法來代替通配符的.....


    //使用通配符
    public static void test(List<?> list) {

    }

    //使用泛型方法
    public <T> void  test2(List<T> t) {
        
    }

上面這兩個方法都是可以的.....那麼現在問題來了,我們使用通配符還是使用泛型方法呢??

原則:

  • 如果參數之間的類型有依賴關係,或者返回值是與參數之間有依賴關係的。那麼就使用泛型方法
  • 如果沒有依賴關係的,就使用通配符,通配符會靈活一些.

3.6泛型擦除

泛型是提供給javac編譯器使用的,它用於限定集合的輸入類型,讓編譯器在源代碼級別上,即擋住向集合中插入非法數據。但編譯器編譯完帶有泛形的java程式後,生成的class文件中將不再帶有泛形信息,以此使程式運行效率不受到影響,這個過程稱之為“擦除”。

3.6.1相容性

JDK5提出了泛型這個概念,但是JDK5以前是沒有泛型的。也就是泛型是需要相容JDK5以下的集合的。

當把帶有泛型特性的集合賦值給老版本的集合時候,會把泛型給擦除了。

值得註意的是:它保留的就類型參數的上限。


        List<String> list = new ArrayList<>();

        //類型被擦除了,保留的是類型的上限,String的上限就是Object
        List list1 = list;

如果我把沒有類型參數的集合賦值給帶有類型參數的集合賦值,這又會怎麼樣??


        List list = new ArrayList();
        List<String> list2 = list;

它也不會報錯,僅僅是提示“未經檢查的轉換”


四、泛型的應用

當我們寫網頁的時候,常常會有多個DAO,我們要寫每次都要寫好幾個DAO,這樣會有點麻煩。

這裡寫圖片描述

那麼我們想要的效果是什麼呢??只寫一個抽象DAO,別的DAO只要繼承該抽象DAO,就有對應的方法了。

要實現這樣的效果,肯定是要用到泛型的。因為在抽象DAO中,是不可能知道哪一個DAO會繼承它自己,所以是不知道其具體的類型的。而泛型就是在創建的時候才指定其具體的類型。

  • 抽象DAO


public abstract class BaseDao<T> {

    //模擬hibernate....
    private Session session;
    private Class clazz;
    
    
    //哪個子類調的這個方法,得到的class就是子類處理的類型(非常重要)
    public BaseDao(){
        Class clazz = this.getClass();  //拿到的是子類
        ParameterizedType  pt = (ParameterizedType) clazz.getGenericSuperclass();  //BaseDao<Category>
        clazz = (Class) pt.getActualTypeArguments()[0];
        System.out.println(clazz);
        
    }
    

    public void add(T t){
        session.save(t);
    }
    
    public T find(String id){
        return (T) session.get(clazz, id);
    }
    
    public void update(T t){
        session.update(t);
    }
    
    public void delete(String id){
        T t = (T) session.get(clazz, id);
        session.delete(t);
    }
    
}
  • 繼承抽象DAO,該實現類就有對應的增刪改查的方法了。

CategoryDao


public class CategoryDao extends BaseDao<Category> {

}

BookDao


public class BookDao extends BaseDao<Book> {


}

五、最後

泛型的基礎就介紹到這裡了,如果以後有需要的話再進行深入研究吧~如果覺得該文章幫助到你,不妨點個贊,關註公眾號一波~

參考資料:

  • Core Java

如果文章有錯的地方歡迎指正,大家互相交流。習慣在微信看技術文章,想要獲取更多的Java資源的同學,可以關註微信公眾號:Java3y


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

-Advertisement-
Play Games
更多相關文章
  • 項目地址 : https://github.com/racaljk/yvm 虛擬機現在已可運行(不過還有很多待發現待修複的bugs),已支持語言特性有: + Java基本算術運算,流程式控制制語句,面向對象。 + RTTI + 字元串拼接(+,+=符號重載) + 異常處理 詳細內容請參見項目 "READ ...
  • PHP不支持的高級OPP特性 對象克隆 克隆實例 __clone()方法 繼承 類繼承 繼承和構造函數 繼承與延遲靜態綁定 介面 實現一個介面 實現多個介面 抽象類 命名空間介紹 ...
  • 前言 隨著spring boot2.0的發佈。項目組的API介面已經考慮向spring boot轉型。底層介面我們一直用的mybatis,所以這篇文章我特意練習了下在spring boot種集成mybatis。 一、準備工作 1、pom.xml 2、項目結構 配置文件依然放在resources目錄下 ...
  • 介紹: Mybatis-Plus(簡稱MP)是一個 Mybatis 的增強工具,在 Mybatis 的基礎上只做增強不做改變,為簡化開發、提高效率而生。(摘自mybatis-plus官網)Mybatis雖然已經給我們提供了很大的方便,但它還是有不足之處,MP的存在就是為了稍稍彌補Mybatis的不足 ...
  • 動態庫後續補充, 本身內容有點多, 這裡簡單分享一下. 希望有魚漁 : ) ...
  • 在日常 python 開發過程中,瞭解一些常用工具很有必要。例如pip,pydoc等 pip pydoc 1、pip 安裝Module 從python 2.7.9 之後,引入了pip工具,用於安裝module。 基本使用: 如果你的Python還沒有安裝pip,可以先通過下麵命令安裝pip: pyt ...
  • 一、Redis的安裝 xshell連上伺服器,依次輸入以下代碼: 如果不巧發生以下截圖中的錯誤: 說明未安裝gcc,如果是centos系統,輸入:yum install gcc安裝gcc即可,然後再次輸入make執行。 輸入make後,很不幸,再次發生如下截圖錯誤: 推測是因為編譯庫的問題。 將ma ...
  • 第二章、線性表 一、線性表的順序表示和實現 1、線性表中第i個數據元素ai的存儲位置: LOC(ai)=LOC(a1)+(i-1)*l l為每個元素需占用l個單元 2、有上式可知,線性表的順序結構是一種隨機存取的存儲結構,但其缺點為插入和刪除比較困難。 3、線性表的數據結構表示為: // 線性表的動 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...