1. 程式人生 > >java8教程-泛型(Generics)

java8教程-泛型(Generics)

泛型(已更新)

在任何繁瑣的(nontrivial)軟體專案中,bug是家常便飯。細心的規劃,程式設計和測試可以幫助減少bug的普遍性(pervasiveness),但是無論如何,無論在哪裡,bug總會伺機悄悄溜進(creep)你的程式碼,因為很明顯,新的特性會不斷的被引入,並且你的程式碼基數會不斷變大和複雜。

幸運的是,一些bug相比其它比較容易檢測。編譯時bug可以在早期被檢測到;你可以利用編譯器的錯誤資訊查明是什麼問題並且解決,就在那時。然而,執行時bug會更加未預知,他們不會立即展示出來,不知道什麼時候發生,可能根本不在程式真正出現問題的點上。

泛型通過更多的在編譯時檢測bug為你的程式碼增加了穩定性。

為什麼要用泛型

簡言之,泛型能夠使型別(類和介面)在定義類,介面和方法的時候引數化。非常像方法定義時用到的形式引數(formal parameters),型別引數提供了一種你可以通過不同的輸入來複用同一段程式碼的方法。不同點是,形式引數輸入的是,而型別引數輸入的是型別

使用泛型比非泛型有很多好處:

  • 編譯時更強大的型別檢測

    Java編譯器對泛型應用了強大的型別檢測,如果程式碼違反了型別安全就會報錯。修復編譯時錯誤比修復執行時錯誤更加容易,因為執行時錯誤很難查詢到。

  • 消除型別轉換(Elimination of casts)

    以下程式碼片段沒有泛型需要轉型:

    List list = new ArrayList();
    list.add(“hello”);
    String s = (String) list.get(0);
    當我們重新用泛型編寫,程式碼就不需要型別轉換了:

    List list = new ArrayList();
    list.add(“hello”);
    String s = list.get(0); // no cast

  • 使開發者實現泛型演算法

通過泛型,開發者可以自己實現泛型演算法,應用到一系列的不同型別,可以自定義,並且型別安全,易讀。

泛型型別

泛型型別是泛型類或者介面被型別引數化。下面的Box類將被更改演示這個概念。

簡單的 Box 類

列舉一個簡單的非泛型 Box操作任意型別的object。它只需要提供兩個方法:set,新增一個obejct到box,get,獲取這個物件:

public class Box {
private Object object;

public void set(Object object) { this.object = object; }
public Object get() { return object; }
}

因為它的方法接收或返回一個物件,你可以任意傳入,只要傳入的不是原始資料型別。我們沒有在編譯時辨別clas如何使用的。一邊可能替換一個 Integer到box,另一邊獲取的不是Integer型別,而可能傳入一個String型別,結果會導致執行時錯誤。

泛型版本的Box

泛型類的定義形式如下:

class name<T1, T2, ..., Tn> { /* ... */ }

型別引數部分被一對尖括號(<>)劃分,緊跟類名,它指定了型別引數(也叫作型別變數)T1, T2, ….,和Tn.

把原Box類更新為泛型類,你要通過把“public class Box”改變為“public class Box”建立一個型別宣告。這會引入一個型別變數, T,你可以在類中任意地方使用。通過這個改變,Box類就變為:

/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
// T stands for "Type"
private T t;

public void set(T t) { this.t = t; }
public T get() { return t; }
}

你可以看到,所有Object出現的地方都被替換為T了。一個型別變數可以指定為任意非原始型別的型別:任意的類,任意的介面,任意的陣列,甚至其他的型別變數。同樣的技術可以應用到建立泛型介面上。

型別引數命名規則(Naming Conventions)

通過規則,型別引數是單獨的,大寫字母。這個表示鮮明區別了你已知的變數命名規則,一個好的理由是:沒有這個規則,你將很難區分型別變數和原生類或介面名的區別。

最普遍使用的型別引數是:

  • E -Element(Java Collections框架大量使用)
  • K -Key
  • N -Number
  • T -Type
  • V -Value
  • S,U,V 等 -第二,第三,第四個型別

你可以在JAVA SE API 看到這些名字的使用。

呼叫和例項化一個泛型型別

要在你的程式碼引用泛型類 Box,你必須執行 泛型型別呼叫,把T替換成具體的值,比如Integer:

Box<Integer> integerBox;

你可以認為泛型型別呼叫跟原生方法呼叫大致一樣,但是不是傳入一個引數到方法,而是傳入一個型別蠶食–這個情況下的Integer–給Box類本身。

Type ParameterType Argument術語(Terminology):
很多開發者交換使用這個兩個術語,但是這兩個術語並不同。敲程式碼時,
type argument 建立一個引數化型別,因此,Foo< T>中的T是type parameter,Foo< String> f中的String是一個type argument。

就想其他的變數定義,上面的程式碼不會真正建立一個新的 Box物件。它只是宣告,integerBox將持有一個“Box of Integer”的引用,用以讀取Box.泛型型別的呼叫通常稱為引數化型別。

為了例項化這個類,用new 關鍵字,把放在類名和括號之間。

Box<Integer> integerBox = new Box<Integer>();

The Diamond

在Java SE 7及以後版本,可以省去型別引數呼叫泛型類的建構函式,用一個空的型別引數(<>),編譯器可以通過上下文決定,或推測type arguments,這個尖括號非正式得叫作diamond(鑽石?這麼奇葩),你可以這樣建立Box< Integer>的一個例項:

Box<Integer> integerBox = new Box<>();

要檢視更多關於diamond 符號和型別推斷(inference),請看型別推斷

多型別引數

正如前面提到的,泛型類可以有多個型別引數。比如泛型 OrderedPair 類,實現了泛型介面 Pair:

public interface Pair<K, V> {
public K getKey();
public V getValue();
}

public class OrderedPair<K, V> implements Pair<K, V> {

private K key;
private V value;

public OrderedPair(K key, V value) {
this.key = key;
this.value = value;
}

public K getKey()   { return key; }
public V getValue() { return value; }
}

下面的語句建立了兩個OrderedPair的例項:

Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String>  p2 = new OrderedPair<String, String>("hello", "world");

new OrderedPair

引數化型別

你也可以用一個引數化的型別(ie List< String>)替換(substitute)型別引數(K ,V),例如用OrderedPair< K,V>:

OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>(...));

原型別(Raw Types)

原型別是指泛型類或泛型介面的名字沒有任何引數,比如,給出泛型類Box:

public class Box(T){
public void set(T t){
/* ...... */
}
}

你可以為形參T賦值一個真實的型別引數來建立一個引數化型別的 Box(T):

Box(Ingeter) intBox=new Box<>();

如果真實的型別引數被省略掉了,你就建立了一個原型別的Box:

Box rawBox =new Box();

因此,Box是Box的原型別。然而,非泛型類或非泛型介面沒有原型別。
原型別出現在遺贈的程式碼裡是因為大量的API類(比如Collections類)在JDK5之前不是泛型類。當使用原型別的時候,你本質上使用的是泛型之前的表現—Box ->Object.為了向後相容,賦值引數化型別給他的原型別是允許的:

Box<String> stringBox=new Box<>();
Box rawBox=stringBox;    //OK

但是如果你賦值一個原型別給一個引數化的型別,你將得到警告:

Box rawBox=new Box(); //rawBox是Box<T>()的原型別
Box<Integer> intBox=rawBox;  //warning:unchecked conversion

當你用原型別呼叫關聯的反省型別的泛型方法時,你也會得到警告:

Box<String> stringBox=new Box<>();
Box rawBox=stringBox;
rawBox.set(8);  //waring: unchecked invocation to set(T)

警告顯示原型別繞過泛型型別檢查,延遲捕獲不安全程式碼到執行時。因此,你需要避免使用原型別。型別擦除部分會有更多關於Java編譯器如何使用原型別的內容。

Unchecked Error Messages

正如上面提到的,當混合遺贈程式碼和泛型程式碼時,你可能會碰到跟下面相似的警告:

Note: Example.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.

這發生在當使用老的API操作原型別時,例如如下程式碼:

public class WarningDemo {
Box<Integer> bi;
bi=createBox();
}
static Box createBox(){
return new Box();
}

‘unchecked’指的是編譯器沒有足夠的型別資訊來執行所有必要的型別檢查以保證型別安全。

相關推薦

java8教程-Generics

泛型(已更新) 在任何繁瑣的(nontrivial)軟體專案中,bug是家常便飯。細心的規劃,程式設計和測試可以幫助減少bug的普遍性(pervasiveness),但是無論如何,無論在哪裡,bug總會伺機悄悄溜進(creep)你的程式碼,因為很明顯,

javaSEGenerics

運行時 str nts super 也有 get 基本類型 簡介 pre 前言 這幾天分享了怎麽搭建集群,這一篇給大家介紹的是泛型,在我們的很多java底層的源代碼都是有很多復雜的泛型的!那什麽是泛型呢? 泛型是Java SE 1.5的新特性,泛型的本質是參數化類型,

CLR via C#關於Generics 的摘錄

類庫 prope png param [] ron using 排序算法 相互 泛型,是CLR和編程語言提供的一種特殊機制,它支持另一種形式的代碼重用,即“算法重用”。 簡單的說,開發人員先定義好一個算法,比如排序、搜索、交換、比較或者轉換等。但是

基礎篇——Generics

寫程式碼的四點:      1.明確需求。要做什麼?      2.分析思路。要怎麼做?(1,2,3……)      3.確定步驟。每一個思路要用到哪些語句、方法和物件。      4.程式碼實現。用具體的語言程式碼將思路實現出來。 學習新技術的四點:      

對照Java學習Swift--Generics

簡介 泛型程式碼讓你能夠根據自定義的需求,編寫出適用於任意型別、靈活可重用的函式及型別。它能讓你避免程式碼的重複,用一種清晰和抽象的方式來表達程式碼的意圖。從Java1.5開始,引進了泛型,Swift和Java的泛型很類似,都很強大,學過Java的同學都知道。

Generics和集合

 -----------------------------------------------------------------------------------------------------------------------------------------

【Java】Generics

What    顧名思義,泛型:一般型別,也就是說可以為任何型別,泛型的本質是“引數化型別”,也就是說所操作的資料型別被指定為一個引數。泛型是在JDK1.5中引入的特性。 Why    泛型提供了編譯時型別安全檢測機制,該機制允

JAVA

強制 off 實例 emp 思想 void 成了 意義 依然 一. 泛型概念的提出(為什麽需要泛型)? 首先,我們看下下面這段簡短的代碼: 1 public class GenericTest { 2 3 public static void

CLR類設計之

where條件 之前 解釋 columns 文章閱讀 sin 自己的 讀書 spl 在上一篇文章中,介紹了什麽是泛型,以及泛型和非泛型的區別,這篇文章主要講一些泛型的高級用法,泛型方法,泛型泛型接口和泛型委托,協變和逆變泛型類型參數和約束性,泛型的高

Java:入門、原理、使用

core clas set out keyword getclass code 避免 post 遠在 JDK 1.4 版本的時候,那時候是沒有泛型的概念的。當時 Java 程序員們寫集合類的代碼都是類似於下面這樣: List list = new ArrayList();

Scala 語言學習之7

scala 泛型==> 泛型類 ---> T 可以代表任意類型class Person[T]{ private var name:T = _ def setName(name:T) = {this.name = name} def getName():T = {this

out print ret 包裝類 基本類型 使用 我們 urn red   面向對象的重要目標就是代碼的重用,支持這一目標的一個重要機制就是泛型。如果除去對象的基本類型外實現的方法是相同的,那麽我們就可以用泛型機制來描述這種基本的功能。 在1.5版本以前,Java並不直接

spa ring private 結果 最大值 swa () integer length   上一次我分享了使用繼承來實現泛型,今天講一下使用接口類型表示泛型。 只有在使用Object已有的那些方法能夠表示所執行的操作時,才能使用Object表示泛型,例如要比較一些圖形的

C# - box - unbox - Generic

com img 9.png bsp inf image 技術分享 ID .com 泛型: C# - box - unbox - 泛型(Generic)

詳解C#

安全 情況 重用 模板 信息 普通 cast 綁定 封閉式   一、C#中的泛型引入了類型參數的概念,類似於C++中的模板,類型參數可以使類型或方法中的一個或多個類型的指定推遲到實例化或調用時,使用泛型可以更大程度的重用代碼、保護類型安全性並提高性能;可以創建自定義的泛型類

TypeScript筆記 :

function identity<T>(arg: T): T { return arg; } function loggingIdentity<T>(arg: T[]): T[] { console.log(arg.length); // Array

詳解C# 詳解C# 詳解C#

  一、前面兩篇文章分別介紹了定義泛型型別、泛型委託、泛型介面以及宣告泛型方法:   詳解C#泛型(一)   詳解C#泛型(二)   首先回顧下如何構建泛型類: public class MyClass<T> { public void MyFunc() {

詳解C#

最大 泛型接口 oid 專用 技術分享 完全 html 回顧 連接   一、前面兩篇文章分別介紹了定義泛型類型、泛型委托、泛型接口以及聲明泛型方法:   詳解C#泛型(一)   詳解C#泛型(二)   首先回顧下如何構建泛型類: public class MyClass&

java必知---

參考Java必知必會之(二)—泛型(上) 所謂泛型就是允許定義類、介面、方法時指定型別形參(分別稱為泛型類、泛型介面、泛型方法)。這個型別形參將在宣告變數、建立物件、呼叫方法時動態指定(即傳入的實際的型別引數,也稱為型別實參)。 泛型類: 1public

Java必知---

參考:Java必知—泛型(下) 本文將會講到的是泛型的進階知識:萬用字元、PECS原則和型別擦除。 1.型別萬用字元 型別萬用字元:額...說白了就是一個?。 2.PECS原則 PECS全文為“Producer Extends, Consumer