Java 基礎篇之泛型

来源:https://www.cnblogs.com/Tianny/archive/2019/10/03/11620384.html
-Advertisement-
Play Games

背景 在沒有泛型前,一旦把一個對象丟進集合中,集合就會忘記對象的類型,把所有的對象都當成 Object 類型處理。當程式從集合中取出對象後,就需要進行強制類型轉換,這種轉換很容易引起 ClassCastException 異常。 定義 程式在創建集合時指定集合元素的類型。增加了泛型支持後的集合,可以 ...


背景

在沒有泛型前,一旦把一個對象丟進集合中,集合就會忘記對象的類型,把所有的對象都當成 Object 類型處理。當程式從集合中取出對象後,就需要進行強制類型轉換,這種轉換很容易引起 ClassCastException 異常。

定義

程式在創建集合時指定集合元素的類型。增加了泛型支持後的集合,可以記住集合中元素的類型,並可以在編譯時檢查集合中元素的類型,如果試圖向集合中添加不滿足類型要求的對象,編譯器就會報錯。

示例

集合使用泛型

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class DiamondTest {
    public static void main(String[] args) {
        List<String> books = new ArrayList<>();
        books.add("learn");
        books.add("java");
        books.forEach(book -> System.out.println(book.length()));

        Map<String, List<String>> schoolsInfo = new HashMap<>();
        List<String> schools = new ArrayList<>();
        schools.add("i");
        schools.add("love");
        schoolsInfo.put("java", schools);
        schoolsInfo.forEach((key, value) -> System.out.println(key + "--->" + value));
    }
}

類、介面使用泛型

public class Apple<T> {
    private T info;
    public Apple() {}
    public Apple(T info) {
        this.info = info;
    }
    public void setInfo(T info) {
        this.info = info;
    }
    public T getinfo() {
        return this.info;
    }
    public static void main(String[] args) {
        Apple<String> a1 = new Apple<>("Apple");
        System.out.println(a1.getinfo());
        Apple<Double> a2 = new Apple<>(5.67);
        System.out.println(a2.getinfo());
    }
}

類型通配符

需求分析

public void test(List<Object> c) {
    for (int i = 0; i < c.size(); i++) {
        System.out.prinln(c.get(i));
    }
}

這個方法聲明沒有任何問題,但是調用該方法實際傳入的參數值,可能會出錯。考慮如下代碼:

List<String> strList = new ArrayList<>();
test(strList); // 編譯出錯,表明 List<String> 對象不能被當成 List<Object> 對象使用,也就是說 List<String> 並不是 List<Object> 的子類。

問題解決

為了表示各種泛型 List 的父類,可以使用類型通配符。List<?> 表示元素類型未知的 List。這個 號被稱為通配符,它可以匹配任何類型。將上面的代碼,改為如下形式:

public void test(List<?> c) {
    for (int i = 0; i < c.size(); i++) {
        System.out.prinln(c.get(i));
    }
}

現在傳入任何類型的 List,程式可以正常列印集合 c 中的元素。

不過集合中元素的類型會被當成 Object 類型對待。

類型通配符的上限

需求分析

當使用 List<?> 時,表明它可以是任何泛型 List 的父類。如果我們只希望它代表某一類泛型 List 的父類,java 提供了被限制的泛型通配符。

看如下代碼:

public abstract class Shape {
    public abstract void draw(Canvas c);
}
public class Circle extends Shape {
    @Override
    public void draw(Canvas c) {
        System.out.println("在畫布上" + c + "上畫一個圓");
    }
}
public class Rectangle extends Shape {
    @Override
    public void draw(Canvas c) {
        System.out.println("把一個矩形畫在畫布" + c + "上");
    }
}
import java.util.List;

public class Canvas {
    public void drawAll(List<Shape> shapes) {
        for (Shape s : shapes) {
            s.draw(this);
        }
    }
}

下麵代碼將引起編譯錯誤,因為 List<Circle> 並不是 List<Shape> 的子類型,所以不能把 List<Circle> 對象當成 List<Shape> 類用。

// 錯誤示範
List<Circle> circleList = new ArrayList<>();
Canvas c = new Canvas();
c.drawAll(circleList); 

問題解決

方法一:通過類型通配符解決,即 List<?> 方式。

需要進行強制類型轉換,因為 List<?> 中元素預設為 Object 類型

import java.util.List;

public class Canvas {
    public void drawAll(List<?> shapes) {
        for (Object obj : shapes) {
            Shape s = (Shape)obj // 但是需要進行強制類型轉換,因為前面提到過 List<?> 中元素預設為 Object 類型
            s.draw(this);
        }
    }
}

方法二:使用被限制的泛型通配符

List<? extends Shape> 可以表示 List<Circle>List<Rectangle> 的父類。只要 List 尖括弧里的類型是 Shape 的子類即可。

import java.util.List;

public class Canvas {
    public void drawAll(List<? extends Shape> shapes) { // 使用被限制的泛型通配符
        for (Shape s : shapes) {
            s.draw(this);
        }
    }
}

形參類型上的應用

在定義類型形參時設定類型通配符上限。以此來表示傳遞給該類型形參的實際類型必須是該上限類型或者其子類。

public class Apple<T extends Number> {
    T col;
    public static void main(String[] args) {
        Apple<Integer> ai = new Apple<>();
        Apple<Double> ad = new Apple<>();
        Apple<String> as = new Apple<>(); // 編譯出錯,試圖將 String 類型傳給 T 形參,但是 String 不是 Number 的子類型
    }
}

泛型方法

定義

泛型方法就是在聲明方法時定義一個或多個類型形參。多個類型參數之間用逗號隔開。

修飾符 <T, S> 返回值類型 方法名(形參列表) {方法體}

需求分析

泛型方法解決了什麼問題?

static void fromArrayToCollection(Object[] a, Collection<Object> c) {
    for (Object o : a) {
        c.add(o)
    }
}

上面定義的方法沒有任何問題,關鍵在於方法中的 c 形參,它的數據類型是 Collection<Object>。假設傳入的實際參數的類型是 Collection<String>,因為 Collection<String> 並不是 Collection<Object> 的子類,所以這個方法的功能非常有限,它只能將 Object[] 數組的元素複製到元素為 Object 類型(Object 的子類不行)的 Collection 集合中。

如果使用通配符 Collection<?> 是否可行呢?顯然也不行,Collection 集合提供的的 add() 方法中有類型參數 E,而如果使用類型通配符,這樣程式無法確定 c 集合中的元素類型,所以無法正確調用 add 方法。

問題解決

泛型方法。

import java.util.ArrayList;
import java.util.Collection;

public class GenericMethodTest {
    // 聲明一個泛型方法
    static <T> void fromArryToCollection(T[] a, Collection<T> c) {
        for (T o : a) {
            c.add(o);
        }
    }
    public static void main(String[] args) {
        Object [] oa = new Object[100];
        Collection<Object> co = new ArrayList<>();
        fromArryToCollection(oa, co);

        String[] sa = new String[100];
        Collection<String> cs = new ArrayList<>();
        fromArryToCollection(sa, cs);
    }
}

進一步改造,如下

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class RightTest {
    static <T> void test(Collection<? extends T> from, Collection<T> to) {
        for (T ele : from) {
            to.add(ele);
        }
    }
    public static void main(String[] args) {
        List<String> as = new ArrayList<>();
        List<Object> ao = new ArrayList<>();
        test(as, ao);
    }
}

泛型構造器

和泛型方法類似,Java 也允許在構造器簽名中聲明類型形參,這就產生了所謂的泛型構造器

public class Foo {
    // 泛型構造器
    public <T> Foo(T t) {
        System.out.println(t);
    }
}

public class GenericConstructor {
    public static void main(String[] args) {
        new <String> Foo("crazy");
        new Foo("crazy"); // 與上面等價
        new <Sting> Foo(12.3) // 出錯
    }
}

類型通配符下限

需求分析

實現一個方法,將 src 集合里的元素複製到 dest 集合里,且返回最後一個被覆制的元素的功能。

因為 dest 集合可以保存 src 集合里的所有元素,所以 dest 集合元素的類型應該是 src 集合元素類型的父類。

為了表示兩個參數間的類型依賴,考慮同時使用之前介紹過的通配符、泛型參數來實現該方法,代碼如下:

public static <T> T copy(Collection<T> dest, Collection<? extends T> src) {
    T last = null;
    for (T ele : src) {
        last = ele;
        dest.add(ele);
    }
    return last;
}

List<Number> ln = new ArrayList<>();
List<Integer> li = new ArrayList<>();
// 下麵代碼會引起編譯錯誤
Integer last = copy(ln, li);

上面的代碼有一個問題,ln 的類型是 List<Number>,那麼 T 的實際類型就是 Number,即返回值 last 類型是 Number 類型。但實際上最後一個複製元素的類型一定是 Integer。也就是說,程式在複製集合元素的過程中,丟失了 src 集合元素的類型。

問題解決

為瞭解決這個問題,引入通配符下限,<? super Type>。表示必須是 Type 本身或者 Type 的父類。改寫後的完整代碼,如下:

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

public class MyUtils {
    // 使用通配符下限
    public static <T> T copy(Collection<? super T> dest, Collection<T> src) {
        T last = null;
        for (T ele : src) {
            last = ele;
            dest.add(ele);
        }
        return last;
    }
    public static void main(String[] args) {
        List<Number> ln = new ArrayList<>();
        List<Integer> li = new ArrayList<>();
        li.add(5);
        // 此時可以準確知道最後一個被覆制的元素是 Integer 類型,而不是籠統的 Number 類型
        Integer last = copy(ln, li);
        System.out.println(last);
        System.out.println(ln);
    }
}

泛型擦除

泛型基本上都是在編譯器這個層次來實現的。在生成的 Java 位元組代碼中是不包含泛型中的類型信息的。使用泛型的時候加上的類型參數, 會被編譯器在編譯的時候去掉。這個過程就稱為泛型擦除。如在代碼中定義的 List<Object>List<String> 等類型, 在編譯之後都會變成 List, JVM 看到的只是 List, 泛型附加的類型信息對 JVM 來說是不可見的。

歡迎關註我的公眾號


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

-Advertisement-
Play Games
更多相關文章
  • 正則表達式是一個特殊的字元序列,它能幫助你方便的檢查一個字元串是否與某種模式匹配。 一、元字元 1) . --匹配任意字元(不包括換行符) 2) ^ --匹配開始位置,多行模式下匹配每一行的開始 3) $ --匹配結束位置,多行模式下匹配每一行的結束 4) * --匹配前一個元字元0到多次 5) + ...
  • 目錄 php常用自定義函數類下載 php 設置字元編碼為utf-8 GB2312和utf8相互轉換 路徑格式化(替換雙斜線為單斜線) 獲取當前文件的目錄 列印輸出 api返回信息 字元串截取 方法一: 方法二: 字元串make_by_id轉成makeById 數組 字元串 對象 json格式的字元串 ...
  • 據網上資料,RSA加密演算法是一種非對稱加密演算法。在公開密鑰加密和電子商務中RSA被廣泛使用。RSA是1977年由羅納德·李維斯特(RON RIVEST)、阿迪·薩莫爾(ADI SHAMIR)和倫納德·阿德曼(LEONARD ADLEMAN)一起提出的。當時他們三人都在麻省理工學院工作。RSA就是他們 ...
  • Django請求執行流程圖: 中間件 中間件的概念 中間件顧名思義,是介於request與response處理之間的一道處理過程,相對比較輕量級,並且在全局上改變django的輸入與輸出。因為改變的是全局,所以需要謹慎使用,用不好會影響到性能。 Django的中間件官方定義: 如果你想修改請求,例如 ...
  • https://blog.csdn.net/libbyandhelen/article/details/78808959 https://www.cnblogs.com/nineep/p/9475297.html https://www.jianshu.com/p/7426bad2f688 ...
  • 工程pom中公共依賴 1、Eureka Server工程 啟動4個實例,配置兩個zone,即zone1、zone2,每個zone都要2個eureka server實例,這個2個zone配置在同一個region上,即region-east。 1.1、eureka-server工程pom文件: 1.2、 ...
  • 首先要使用的第類庫有 urllib下的request 以及urllib下的parse 以及 time包 random包 之後我們定義一個名叫BaiduSpider類用來爬取信息 屬性有 url:用來爬取的網址 headers:請求頭 之後我們定義三個方法 不涉及清洗數據 獲取頁面 保存數據 主函數 ...
  • 第一步:初始化coda 命令:sudo gedit ~/.bashrc 第二部:複製其中這樣一段代碼 第三部:在結尾處添加 conda activate pyn_env ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...