日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > java >内容正文

java

一篇文章教你学会Java泛型

發布時間:2025/1/21 java 43 豆豆
生活随笔 收集整理的這篇文章主要介紹了 一篇文章教你学会Java泛型 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

文章目錄

      • 一、原生態類型
        • 1、什么是原生態類型
        • 2、使用原生態類型 有什么不好
        • 3、泛型的子類型規則
        • 4、泛型的可擦除性
      • 二、泛型常用形式
        • 1、泛型方法
        • 2、泛型單例工廠
      • 三、有限制的通配符類型
      • 四、類型安全的異構容器

寫在前面: 我是「境里婆娑」。我還是從前那個少年,沒有一絲絲改變,時間只不過是考驗,種在心中信念絲毫未減,眼前這個少年,還是最初那張臉,面前再多艱險不退卻。
寫博客的目的就是分享給大家一起學習交流,如果您對 Java感興趣,可以關注我,我們一起學習。

前言:Java 1.5之前是沒有泛型的,以前從集合中讀取每個對象都必須先進行轉換,如果不小心存入集合中對象類型是錯的,運行過程中轉換處理會報錯。有了泛型之后編譯器會自動幫助轉換,使程序更加安全,但是要正確使用泛型才能取得事半功倍的效果。因此學會使用Java泛型,可以避免寫那種冗余的代碼。

一、原生態類型

1、什么是原生態類型

原生態類型(Raw type),即不帶任何實際類型參數的泛型名稱。如與List對應的原生態類型List。不推薦List list = new ArrayList()這樣的方式,主要就會丟掉安全性(為什么不安全呢?具體請往下看),應使用List list = new ArrayList()明確類型。或者使用List(那么List與List有啥區別呢?具體可以看泛型的子類型規則部分)

2、使用原生態類型 有什么不好

我們使用原生態類型List創建一個集合,并往其中放入String類與int類,并迭代循環獲取List集合中的元素。

public class Test1 {public static void main(String[] args) {List list = new ArrayList<>();list.add("123");list.add(123);for (Iterator i = list.iterator(); i.hasNext();) {String str = i.next();}}}

必須使用Cast強轉,否則編譯會報錯,在編譯期報錯對于開發者來說是我們最希望看到的。

但是我們根據提示,增加Cast,好了編譯是不會報錯了,但是運行時期會報錯! Exception in thread “main” java.lang.ClassCastException: ,這就對我們開發者來說大大增加了難度。
由此可見,原生類型是不推薦使用,是不安全的!

  • 問1:那為什么Java還要允許使用原生態類型呢?

是為了提升兼容性,Java1.5之前已經存在很多的原生態類型的代碼,那么為了讓代碼保持合法,并且能夠兼容新代碼,因此Java才對原生態類型支持!

  • 問2:那我們使用List是不是就可以了呢,兩個有啥區別呢?

兩者都可以插入任意類型的對象。不嚴格來說,前者原生態類型List逃避了泛型檢查,后者參數化類型List明確告訴編譯器能夠持有任意類型的對象。但是兩個的區別主要是泛型存在子類型規則,具體請往下看

3、泛型的子類型規則

子類型規則,即任何參數化的類型是原生態類型的一個子類型,比如List是原生態類型List的一個子類型,而不是參數化List的子類型。

由于子類型規則的存在,我們可以將List傳遞給List類型的參數

public static void main(String[] args) {List<String> strings = new ArrayList<>();unsafeAdd(strings, new Integer(1));String s = strings.get(0);}private static void unsafeAdd(List list, Object o){list.add(o);}

雖然編譯器是沒有報錯的,但是編譯過程會出現以下提示,表明編寫了某種不安全的未受檢的操作

但是我們不能將List傳遞給List類型參數

public static void main(String[] args) {List<String> strings = new ArrayList<>();unsafeAdd(strings, new Integer(1));String s = strings.get(0);}private static void unsafeAdd(List<Object> list, Object o){list.add(o);}

4、泛型的可擦除性

我們先看一下代碼,看看結果:

public static void main(String[] args) {List<String> l1 = new ArrayList<String>();List<Integer> l2 = new ArrayList<Integer>();// 輸出為true,擦除后的類型為ListSystem.out.println(l1.getClass() == l2.getClass());}

結果為true,這是因為:泛型信息可以在運行時被擦除,泛型在編譯期有效,在運行期被刪除,也就是說所有泛型參數類型在編譯后都會被清除掉。歸根結底不管泛型被參數具體化成什么類型,其class都是RawType.class,比如List.class,而不是List.class或List.class

事實上,在類文字中必須使用原生態類型,不準使用參數化類型(雖然允許使用數組類型和基本類型),也就是List.class、String[].class和int.class都是合法的,而List.class和List<?>.class不合法

二、泛型常用形式

1、泛型方法

常用的形式:

訪問修飾符 [static][final] <類型參數列表> 返回值類型 方法名([形式參數列表])

備注:[] 代可有可無的意思,泛型方法可以是實例方法或靜態方法,類型參數可以在靜態方法中,這是與泛型類最大的區別。

2、泛型單例工廠

有時候我們需要創建不可變但又適合許多不同類型的對象。之前的單例模式滿足不可變,但不適合不同類型對象,這次我們可以利用泛型做到這點。

/*** apply方法接收與返回某個類型T的值* @param <T>*/ public interface UnaryFunction<T> {T apply(T arg); }

現在我們需要一個恒等函數(Identity function,f(x)=x,簡單理解輸入等于返回的函數,會返回未被修改的參數),如果每次需要的時候都要重新創建一個,這樣就會很浪費,如果泛型被具體化了,每個類型都需要一個恒等函數,但是它們被擦除后,就只需要一個泛型單例。

public class SingleGeneric {/*** 返回未被修改的參數arg*/private static UnaryFunction<Object> IDENTITY_FUNCTION = (Object arg) -> {return arg;};/*** 泛型方法identityFunction:* 返回類型:UnaryFunction<T>* 類型參數列表;<T>* 忽略強制轉換未受檢查的警告:* 因為返回未被修改的參數arg,所以我們知道無論T的值是什么,都是類型安全的* @param <T>* @return*/@SuppressWarnings("unchacked")public static <T> UnaryFunction<T> identityFunction(){return (UnaryFunction<T>) IDENTITY_FUNCTION;}public static void main(String[] args) {String[] strings = {"hello","world"};UnaryFunction<String> sameString = identityFunction();for (String s: strings) {System.out.println(sameString.apply(s));}Number[] numbers = {1,2.0};UnaryFunction<Number> sameNumber = identityFunction();for (Number n: numbers) {System.out.println(sameNumber.apply(n));}} }

三、有限制的通配符類型

之前提到過的無限制的通配符類型就提到過,無限制的通配符單純只使用"?"(如Set<?>),而有限制的通配符往往有如下形式,通過有限制的通配符類型可以大大提升API的靈活性。

(1)E的某種超類集合(接口):Collection<? super E>、Interface<? super E>、

(2)E的某個子類集合(接口):Collection<? extends E>、Interface<? extends E>

  • 問1:那么什么時候使用extends關鍵字,什么什么使用super關鍵字呢?

有這樣一個PECS(producer-extends, consumer-super)原則:如果參數化類型表示一個T生產者,就使用<? extends T>,如果表示消費者就是<? super T>。可以這樣助記

  • 問2:什么是生產者,什么是消費者

1)生產者:產生T不能消費T,針對collection,對每一項元素操作時,此時這個集合時生產者(生產元素),使用Collection<? extends T>。只能讀取,不能寫入

2)消費者:不能生產T,只消費使用T,針對collection,添加元素collection中,此時集合消費元素,使用Collection<? super T>,只能添加T的子類及自身,用Object接收讀取到的元素

舉例說明:生產者

1)你不能在List<? extends Number>中add操作,因為你增加Integer可能會指向List,你增加Double可能會指向Integer。根本不能確保列表中最終保存的是什么類型。換句話說Number的所有子類從類關系上來說都是平級的,毫無聯系的。并不能依賴類型推導(類型轉換),編譯器是無法確實的實際類型的!

2)但是你可以讀取其中的元素,并保證讀取出來的一定是Number的子類(包括Number),編譯并不會報錯,換句話說編譯器知道里面的元素都是Number的子類,不管是Integer還是Double,編譯器都可以向下轉型

  • 舉例說明:消費者

1)編譯器不知道存入列表中的Number的超類具體是哪一個,只能使用Object去接收。因為你增加Integer可能會指向List,你增加Double可能會指向Integer。根本不能確保列表中最終保存的是什么類型。換句話說Number的所有子類從類關系上來說都是平級的,毫無聯系的。并不能依賴類型推導(類型轉換),編譯器是無法確實的實際類型的!

2)但是你可以讀取其中的元素,并保證讀取出來的一定是Number的子類(包括Number),編譯并不會報錯,換句話說編譯器知道里面的元素都是Number的子類,不管是Integer還是Double,編譯器都可以向下轉型

  • 舉例說明:消費者

1)編譯器不知道存入列表中的Number的超類具體是哪一個,只能使用Object去接收

2)但是只可以添加Interger及其子類(因為Integer子類也是Integer,向上轉型),不能添加Object、Number。因為插入Number對象可以指向List對象,你插入Object,因為可能會指向List對象

注意:Comparable/Comparator都是消費者,通常使用Comparator<? Super T>),可以將上述的max方法進行改造:

public static <T extends Comparable<? super T>> T max(List<? extends T> list) {Iterator<? extends T> iterator = list.iterator();T result = iterator.next();while (iterator.hasNext()) {T t = iterator.next();if (t.compareTo(result) > 0) {result = t;}}return result;}

四、類型安全的異構容器

泛型一般用于集合,如Set和Map等,這些容器都是被參數化了(類型已經被具體化了,參數個數已被固定)的容器,只能限制每個容器只能固定數目的類型參數,比如Set只能一個類型參數,表示它的元素類型,Map有兩個參數,表示它的鍵與值。

但是有時候你會需要更多的靈活性,比如關系數據庫中可以有任意多的列,如果以類型的方式所有列就好了。有一種方法可以實現,那就是使用將鍵進行參數化而不是容器參數化,然后將參數化的鍵提交給容器,來插入或獲取值,用泛型來確保值的類型與它的鍵相符。

我們實現一個Favorite類,可以通過Class類型來獲取相應的value值,鍵可以是不同的Class類型(鍵Class<?>參數化,而不是Map<?>容器參數化)。利用Class.cast方法將鍵與鍵值的類型對應起來,不會出現 favorites.putFavorite(Integer.class, “Java”) 這樣的情況。

public class Favorites {private Map<Class<?>, Object> favorites = new HashMap<>();public <T> void putFavorite(Class<T> type, T instance){if (type == null) {throw new NullPointerException("Type is null");}favorites.put(type, type.cast(instance));}public <T> T getFavorite(Class<T> type){return type.cast(favorites.get(type));} }

Favorites實例是類型安全(typesafe)的,你請求String時,不會返回給你Integer,同時也是異構(heterogeneous)的,不像普通map,它的鍵都可以是不同類型的。因此,我們將Favorites稱之為類型安全的異構容器(typesafe heterogeneous container)。

public static void main(String[] args) {Favorites favorites = new Favorites();favorites.putFavorite(String.class, "Java");favorites.putFavorite(Integer.class, 64);favorites.putFavorite(Class.class, Favorites.class);String favoriteString = favorites.getFavorite(String.class);Integer favoriteInteger = favorites.getFavorite(Integer.class);Class<?> favoriteClass = favorites.getFavorite(Class.class);// 輸出 Java 40 FavoritesSystem.out.printf("%s %x %s%n", favoriteString, favoriteInteger, favoriteClass.getSimpleName());}

Favorites類局限性在于它不能用于在不可具體化的類型中,換句話說你可以保存String,String[],但是你不能保存List,因為你無法為List獲取一個Class對象:List.class是錯誤的,不管是List還是List都會公用一個List.class對象。

  • 附1:相關泛型術語
      1)參數化的類型:List
      2)實際類型參數:String
      3)泛型:List
      4)形式類型參數:E
      5)無限制通配符類型:List<?>
      6)原生態類型:List
      7)遞歸類型限制:<T extends Comparable>
      8)有限制的通配符類型:List<? extends Number>
      9)泛型方法:static List union()
      10)類型令牌:String.class

  • 附2:常用的形式類型參數
      1)T 代表一般的任何類。
      2)E 代表 Element 的意思,或者 Exception 異常的意思。
      3)K 代表 Key 的意思。
      4)V 代表 Value 的意思,通常與 K 一起配合使用。
      5)S 代表 Subtype 的意思
      
    參考文章:如何正確使用Java泛型

總結

以上是生活随笔為你收集整理的一篇文章教你学会Java泛型的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。