激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

Java的泛型

2023-06-23 01:01未知服務器之家 Java教程

泛型程序設計(Generic programming) 意味著編寫的代碼可以被很多不同類型的對象所重用。泛型對于集合類尤其有用,例如,ArrayList 就是一個無處不在的集合類。一個 ArrayList 類可以聚集任何類型的對象,這是一個泛型程序設計的實例

泛型程序設計(Generic programming) 意味著編寫的代碼可以被很多不同類型的對象所重用。泛型對于集合類尤其有用,例如,ArrayList 就是一個無處不在的集合類。一個 ArrayList 類可以聚集任何類型的對象,這是一個泛型程序設計的實例。

泛型是我們需要的程序設計手段。使用泛型機制編寫的程序代碼要比那些雜亂地使用 Object 變量,然后再進行強制類型轉換的代碼具有更好的安全性和可讀性。

至少在表面上看來,泛型很像 C++ 中的模板。與 Java —樣,在 C++ 中,模板也是最先被添加到語言中支持強類型集合的。但是,多年之后人們發現模板還有其他的用武之地。學習完本章的內容可以發現 Java 中的泛型在程序中也有新的用途。

為什么要使用泛型程序設計

泛型程序設計(Generic programming)意味著編寫的代碼可以被很多不同類型的對象所重用。例如,我們并不希望為聚集 String 和 File 對象分別設計不同的類。實際上,也不需要這樣做,因為一個 ArrayList 類可以聚集任何類型的對象。這是一個泛型程序設計的實例。

實際上,在 Java 增加泛型類之前已經有一個 ArrayList 類。下面來研究泛型程序設計的機制是如何演變的,另外還會講解這對于用戶和實現者來說意味著什么。

類型參數的好處

在 Java 中增加范型類之前,泛型程序設計是用繼承實現的。實現時使用通用類型(如 Object 或 Comparable 接口),在使用時進行強制類型轉換。

泛型對于集合類尤其有用,ArrayList 就是一個無處不在的集合類。ArrayList 類維護一個 Object 類型的數組(Object 類是所有類的父類):

// before generic classes
public class ArrayList {
    private Object[] elementData;
    // ...
    public Object get(int i) { ... }
    public void add(Object o) { ... }
}

這種方法有兩個問題。

  • 當獲取一個值時,必須進行強制類型轉換。
  • 此外,這里沒有錯誤檢査。可以向數組列表中添加任何類的對象。對于 files.add(new File("..."); 這個調用,編譯和運行都不會出錯。然而在其他地方,如果將 get() 的結果強制類型轉換為 String 類型, 就會產生一個錯誤。
ArrayList files = new ArrayList();
String filename = (String) files.get(0);

泛型提供了一個更好的解決方案:類型參數(type parameters)。ArrayList 類有一個類型參數用來指示元素的類型:ArrayList<String> files = new ArrayList<String>();這使得代碼具有更好的可讀性。人們一看就知道這個數組列表中包含的是 String 對象。

在 Java7 及以后的版本中,構造函數中可以省略泛型類型:ArrayList<String> files = new ArrayList<>();省略的類型可以從變量的類型推斷得出。

編譯器也可以很好地利用這個信息。當調用 get() 方法的時候,不需要進行強制類型轉換,編譯器就知道返回值類型為 String,而不是 Object:String filename = files.get(0);

編譯器還知道 ArrayList 中 add() 方法有一個類型為 String 的參數?,F在, 編譯器可以進行檢査,避免插入錯誤類型的對象。例如下面的代碼是無法通過編譯的。這將比使用 Object 類型的參數安全一些,出現編譯錯誤比類在運行時出現類的強制類型轉換異常要好得多。

files,add(new File("...")); // can only add String objects to an ArrayList<String>

類型參數的魅力在于:使得程序具有更好的可讀性和安全性。

誰想成為泛型程序員

使用像 ArrayList 這樣的泛型類很容易。大多數 Java 程序員都使用 ArrayList 這樣的類型,就好像它們已經構建在語言之中,像 String[] 數組一樣。(當然, 數組列表比數組要好一些,因為數組列表可以自動擴容。)

但是,實現一個泛型類并沒有那么容易。對于類型參數,使用這段代碼的程序員可能想要內置(plugin)所有的類。他們希望在沒有過多的限制以及混亂的錯誤消息的狀態下,做所有的事情。因此,一個泛型程序員的任務就是預測出所用類的未來可能有的所有用途。

這一任務難到什么程度呢?下面是標準類庫的設計者們肯定產生爭議的一個典型問題。ArrayList 類的 addAll() 方法用來添加另一個集合的全部元素。程序員可能想要將 ArrayList 中的所有元素添加到 ArrayList 中去(Manager 類是 Employee 類的子類)。然而,反過來就不行了。如何設計才能只允許前一個調用,而不允許后一個調用呢?Java 語言的設計者發明了一個具有獨創性的新概念,通配符類型(wildcard type),它解決了這個問題。通配符類型非常抽象,然而,它們能讓庫的構建者編寫出盡可能靈活的方法。


泛型程序設計劃分為 3 個能力級別?;炯墑e是,僅僅使用泛型類:典型的是像 ArrayList 這樣的集合,不必考慮它們的工作方式與原因。大多數應用程序員將會停留在這一級別上,直到出現了什么問題。當把不同的泛型類混合在一起時,或是在與對類型參數一無所知的遺留的代碼進行銜接時,可能會看到含混不清的錯誤消息。如果這樣的話,就需要系統地學習 Java 泛型來解決這些問題,而不要胡亂地猜測。當然,最終可能想要實現自己的泛型類與泛型方法。

應用程序員很可能不喜歡編寫太多的泛型代碼。JDK 開發人員已經做出了很大的努力,為所有的集合類提供了類型參數。憑經驗來說,那些原本涉及許多來自通用類型(如 Object 或 Comparable 接口)的強制類型轉換的代碼一定會因使用類型參數而受益。

本章介紹實現自己的泛型代碼需要了解的各種知識。希望大多數讀者可以利用這些知識解決一些疑難問題,并滿足對于參數化集合類的內部工作方式的好奇心。

泛型類

泛型類(generic class)就是具有一個或多個類型參數的類。

本章使用一個簡單的 Pair 類作為例子。

public class Pair<T> {
    private T first;
    private T second;
    
    public Pair() { first = null ; second = null ; }
    public Pair(T first, T second) { this.first = first; this.second = second; }
    
    public T getFirst() { return first; }
    public T getSecond() { return second; }
    
    public void setFirst(T newValue) { first = newValue; }
    public void setSecond(T newValue) { second = newValue; }
}

Pair 類引入了一個類型參數 T,用尖括號(< >)括起來,并放在類名的后面。

泛型類可以有多個類型參數。如果有多個類型變量,多個類型變量之間用 “,”逗號分隔。例如,可以定義 Pair 類,其中第一個域和第二個域使用不同的類型:public class Pair<T, U> { ... }

類定義中的類型參數指定方法的返回類型以及域和局部變量的類型。例如,private T first;


用具體的類型替換類型參數就可以實例化泛型類型,例如:Pair 可以將結果想象成帶有構造器的普通類:

  • Pair()
  • Pair(String, String)

和方法:

  • String getFirst()、String getSecond()
  • void setFirst(String)、void setSecond(String)

換句話說,泛型類可看作普通類的工廠。

泛型方法

前面已經介紹了如何定義一個泛型類。實際上,還可以定義一個帶有類型參數的簡單方法。

class ArrayAlg {
    public static <T> T getMiddle(T... a) {
        return a[a.length / 2];
    }
}

這個方法是在普通類中定義的,而不是在泛型類中定義的。然而,這是一個泛型方法,可以從尖括號和類型參數看出這一點。注意,類型參數放在修飾符(這里是 public static)的后面,返回類型的前面。


泛型方法可以定義在普通類中,也可以定義在泛型類中。

當調用一個泛型方法時,在方法名前的尖括號中放入具體的類型:

String middle = ArrayAlg.<String>getMiddle("]ohn", "Q.", "Public");

在這種情況(實際也是大多數情況)下,方法調用中可以省略 類型參數。編譯器有足夠的信息能夠推斷出所調用的方法。它用 names 的類型(即 String[])與泛型類型 T[] 進行匹配并推斷出 T 一定是 String。也就是說,可以調用

String middle = ArrayAlg.getMiddle("]ohn", "Q.", "Public");

類型參數的限定

有時,類或方法需要對類型參數加以約束。下面是一個典型的例子。我們要計算數組中的最小元素:

class ArrayAIg {
    // almost correct
    public static <T> T min(T[] a) {
        if (a == null || a.length == 0) {
            return null;
        }
        T smallest = a[0];
        for (int i = 1; i < a.length; i++) {
            if (smallest.compareTo(a[i]) > 0) smallest = a[i];
        }
        return smallest;
    }
}

但是,這里有一個問題。請看一下 min() 方法的代碼內部。變量 smallest 類型為 T,這意味著它可以是任何一個類的對象。怎么才能確信 T 所屬的類有 compareTo() 方法呢?

解決這個問題的方案是將 T 限制為實現了 Comparable 接口(只含一個 compareTo() 方法的標準接口)的類。可以通過對類型參數 T 設置限定(bound)實現這一點:

public static <T extends Comparable> T min(T[] a) {}

現在,泛型的 min() 方法只能被實現了 Comparable 接口的類(如 String、LocalDate 等)的數組調用。

T extends 綁定類型表示 T 應該是綁定類型的子類型(subtype)。T 和綁定類型可以是類,也可以是接口。


一個類型參數或通配符可以有多個限定,多個限定之間用 “ &” 分隔,例如:T extends Comparable & Serializable。在Java的限定中,可以根據需要擁有多個接口限定,但至多有一個類限定。如果用一個類作為限定,它必須放在限定列表中的第一個位置。

// ok
T extends Object & Comparable & Serializable
// error
T extends Comparable & Serializable & Object

泛型代碼和虛擬機

類型擦除

類型擦除是Java泛型實現的一種方式。

類型擦除指的是:在編譯時,將泛型類型擦除成其原始類型。

虛擬機沒有泛型類型對象,所有對象都屬于普通類。無論何時定義一個泛型類型,都自動提供了一個相應的原始類型(raw type)。類型參數用第一個限定的類型來替換,如果沒有給定限定就用 Object 替換。例如:

  • 類 Pair 中的類型參數沒有顯式的限定,因此,原始類型用 Object 替換 T。
  • 類 Interval<T extends Comparable & Serializable> 中第一個限定的類型為 Comparable,因此,原始類型用 Comparable 替換 T。

Pair 的原始類型如下所示。結果是一個普通的類,就好像泛型引入 Java 語言之前已經實現的那樣。在程序中可以包含不同類型的 Pair,例如,Pair 或 Pair。而擦除類型后就變成原始的 Pair 類型了。

public class Pair {
    private Object first;
    private Object second;
    
    public Pair(Object first, Object second) {
        this.first = first;
        this.second = second;
    }
    
    public Object getFirst() { return first; }
    public Object getSecond() { return second; }
    
    public void setFirst(Object newValue) { first = newValue; }
    public void setSecond(Object newValue) { second = newValue; }
}

翻譯泛型表達式

當程序調用泛型方法時,如果擦除返回類型,編譯器插入強制類型轉換。例如,下面這個語句序列:

Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();

擦除 getFirst() 方法的返回類型后,getFirst() 方法將返回 Object 類型。編譯器自動插入 Employee 的強制類型轉換。也就是說,編譯器把這個方法調用翻譯為兩條虛擬機指令:

  • 對 Pair#getFirst() 原始方法的調用。
  • 將返回的 Object 類型強制轉換為 Employee 類型。

當存取一個泛型域時也要插入強制類型轉換。假設 Pair 類的 first 域和 second 域都是公有的,表達式:Employee buddy = buddies.first;也會在結果字節碼中插入強制類型轉換。

翻譯泛型方法

類型擦除也會出現在泛型方法中。程序員通常認為下述的泛型方法 public static <T extends Comparable> T min(T[] a)是一個完整的方法族,而擦除類型之后,只剩下一個方法:public static Comparable min(Comparable[] a)

注意,類型參數 T 已經被擦除了,只留下了限定類型 Comparable。


類型擦除帶來了兩個復雜問題。看一看下面這個示例:

class DateInterval extends Pair<LocalDate> {
    public void setSecond(LocalDate second) {
        if (second.compareTo(getFirst()) >= 0) {
            super.setSecond(second);
        }
    }
    ...
}

一個日期區間是一對 LocalDate 對象,并且需要覆蓋這個方法來確保第二個值永遠不小于第一個值。這個類擦除后變成

// after erasure
class DateInterval extends Pair {
    public void setSecond(LocalDate second) { ... }
    ...
}

令人感到奇怪的是,存在另一個從 Pair 繼承的 setSecond() 方法,即 public void setSecond(Object second)這顯然是一個不同的方法,因為它有一個不同類型的參數 Object,而不是 LocalDate。然而,不應該不一樣??紤]下面的語句序列:

DateInterval interval = new DateInterval(...);
Pair<LocalDate> pair = interval; // OK assignment to superclass
pair.setSecond(aDate);

這里,希望對 setSecond() 的調用具有多態性,并調用最合適的那個方法。由于 pair 引用 Datelnterval 對象,所以應該調用 Datelnterval.setSecond()。問題在于類型擦除與多態發生了沖突。要解決這個問題,就需要編譯器在 Datelnterval 類中生成一個橋方法(bridge method):

public void setSecond(Object second) { setSecond((Date) second); }

有關泛型的事實

需要記住有關 Java 泛型轉換的事實:

  • 虛擬機中沒有泛型,只有普通的類和方法。
  • 所有的類型參數都用它們的限定類型替換。
  • 橋方法被合成來保持多態。
  • 為保持類型安全性,必要時插入強制類型轉換。

類 A 是類 B 的子類,但是 G 和 G 不具有繼承關系,二者是并列關系。

public static void printBuddies(Pair<Employee> p) { ... }

// Manager 類是 Employee 類的子類
Pair<Manager> pair = new Pair<>();
// error(固定的泛型類型系統的局限,通配符類型解決了這個問題)
printBuddies(pair);

泛型一般有三種使用方式:泛型類、泛型方法、泛型接口。

// 泛型類
public class Pair<T>
// 實例化泛型類
Pair<String> pair = new Pair<>();
// 繼承泛型類,指定類型
class DateInterval extends Pair<LocalDate>

// 泛型方法
public static <T> T getMiddle(T... a)

// 泛型接口
public interface Generator<T>
// 實現泛型接口,指定類型
class GeneratorImpl implements Generator<String>

通配符類型

固定的泛型類型系統使用起來并沒有那么令人愉快,類型系統的研究人員知道這一點已經有一段時間了。Java 的設計者發明了一種巧妙的(仍然是安全的)“解決方案”:通配符類型。下面幾小節會介紹如何處理通配符。

通配符概念

通配符類型中,允許類型參數變化。例如,通配符類型 Pair<? extends Employee> 表示任何泛型 Pair 類型,它的類型參數是 Employee 的子類,如 Pair,但不是 Pair

假設要編寫一個打印雇員對的方法,像這樣:

public static void printBuddies(Pair<Employee> p) {
    Employee first = p.getFirst();
    Employee second = p.getSecond();
    System.out.println(first.getName() + " and " + second.getName() + " are buddies.");
}

正如前面講到的,不能將 Pair 傳遞給這個方法,這一點很受限制。解決的方法很簡單:使用通配符類型:

public static void printBuddies(Pair<? extends Employee> p)

類型 Pair 是 Pair<? extends Employee> 的子類型(如圖 8-3 所示)。

Java的泛型


使用通配符會通過 Pair<? extends Employee> 的引用破壞 Pair 嗎?

Pair<Manager> managerBuddies = new Pair<>(ceo, cfo);
Pair<? extends Employee> wildcardBuddies = managerBuddies; // OK
wildcardBuddies.setFirst(lowlyEmployee); // compile-time error

這可能不會引起破壞。對 setFirst() 的調用有一個類型錯誤。要了解其中的緣由,請仔細看一看類型 Pair<? extends Employee>。其方法似乎是這樣的:

? extends Employee getFirst()
void setFirst(? extends Employee)

這樣將不可能調用 setFirst() 方法。編譯器只知道需要某個 Employee 的子類型,但不知道具體是什么類型。它拒絕傳遞任何特定的類型。畢竟 ? 不能用來匹配。

使用 getFirst() 就不存在這個問題:將 getFirst() 的返回值賦給一個 Employee 的引用完全合法。這就是引入有限定的通配符的關鍵之處。現在已經有辦法區分安全的訪問器方法和不安全的更改器方法了。

通配符的超類型限定

通配符限定與類型參數限定十分類似,但是,通配符限定還有一個附加的能力,即可以指定一個超類型限定(supertype bound),如下所示:? super Manager。

這個通配符限制為 Manager 的所有超類型。(已有的 super 關鍵字十分準確地描述了這種聯系)

參考資料

《Java核心技術卷一:基礎知識》(第10版)第 8 章:泛型程序設計

延伸 · 閱讀

精彩推薦
Weibo Article 1 Weibo Article 2 Weibo Article 3 Weibo Article 4 Weibo Article 5 Weibo Article 6 Weibo Article 7 Weibo Article 8 Weibo Article 9 Weibo Article 10 Weibo Article 11 Weibo Article 12 Weibo Article 13 Weibo Article 14 Weibo Article 15 Weibo Article 16 Weibo Article 17 Weibo Article 18 Weibo Article 19 Weibo Article 20 Weibo Article 21 Weibo Article 22 Weibo Article 23 Weibo Article 24 Weibo Article 25
主站蜘蛛池模板: 91真视频 | av大全在线免费观看 | 国产91一区二区三区 | 久久噜噜噜精品国产亚洲综合 | 深夜影院一级毛片 | 在线看一区二区三区 | h视频在线免费观看 | 一区国产在线观看 | 国产毛片在线高清视频 | 天天草天天干天天 | 国产乱xxxx| 午夜偷拍视频 | 一级毛片一区 | 欧美成人一区二区三区 | 日本人乱人乱亲乱色视频观看 | 天天碰天天操 | 福利四区| www亚洲成人| 国产亚色 | 欧美性生交大片 | 美国黄色小视频 | 黄色羞羞视频在线观看 | 日本高清com | 今井夏帆av一区二区 | 羞羞漫画无遮挡观看 | 极品销魂一区二区三区 | 成人三级电影网址 | 国产精品剧情一区二区三区 | 欧美一级视频网站 | www.99久 | 视频一区二区三区在线观看 | 91网页视频入口在线观看 | 久久国产不卡 | 99riav视频一区二区 | chinese18 xxxx videos| 久久艹国产精品 | 成年性羞羞视频免费观看无限 | 久在线播放 | 97干色 | 手机在线看片国产 | 欧美在线中文字幕 |