/**
* @version 1.0
* @author 子月初七
*/
複製代碼
泛型是從Java1.5開始引進的,所謂的泛型能夠理解成參數化類型,即類型是以參數的方式傳入泛型類或者泛型方法。 泛型這個術語的意思是:「適用於許多許多的類型」。java
泛型可使編寫的代碼被不少不一樣的類型對象所重用。編程
使用泛型機制編寫的程序代碼要比那些雜亂地使用Object變量,而後再進行強制類型轉換的代碼具備更好的安全性和可讀性。泛型對於集合類尤爲有用,例如,ArrayList就是一個無處不在的集合。api
泛型類能夠當作普通類的工廠。數組
package com.corejava.genericprogramming;
public class ArrayList{
private Object[] elementData;
...
public Object get(int i ){...}
public void add(Object o){...}
}
複製代碼
這種方法有兩個問題。安全
ArrayList files = new ArrayList();
String filename = (String) files.get(0);
複製代碼
//ArrayList類有一個類型參數用來指示元素的種類
ArrayList<String> files = new ArrayList<String>();
//JavaSE 7後,構造函數能夠省略泛型類型。
ArrayList<String> files = new ArrayList<>();
複製代碼
當調用get的時候,不須要進行強制類型轉換。 編譯器就知道返回值類型爲String,而不是Object。bash
String filename = files.get(0);
複製代碼
具備一個或多個類型變量的類稱之爲泛型類。app
下面是一個泛型類。ide
package com.corejava.genericprogramming;
public class Pair<T> {
private T first;
private T second;
public Pair(T first, T second){
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
複製代碼
由上段代碼可見,類型變量位於類名以後的尖括號<>中。函數
能夠引進多個類型變量。ui
public class Pair<T, U> {...}
複製代碼
在Java庫中,E表示集合的元素類型,K和V表示關鍵字和值,T或者U或者S等表示任意類型。
泛型方法能夠定義在普通類中,也能夠定義在泛型類中。
注意,類型變量放在修飾符後面,返回類型前面。
package com.corejava.genericprogramming;
public class ArrayAlg {
//若是沒有<T>聲明,不能稱之爲泛型方法
public static <T> T getMiddle(T... a) {
return a[a.length / 2];
}
public static void main(String[] args) {
//System.out.println(ArrayAlg.<String>getMiddle("Matthew","Xu","CoreJava"));
//通常狀況下能夠省略<String>類型參數。
//編譯器有足夠的信息推斷出所調用的方法。
System.out.println(ArrayAlg.getMiddle("Matthew","Xu","CoreJava"));
}
}
複製代碼
以下定義一個泛型接口。
package com.corejava.genericprogramming;
public interface IGeneric<T> {
public T test();
}
複製代碼
泛型接口未傳入泛型實參時
package com.corejava.genericprogramming;
public class TestGeneric<T> implements IGeneric<T>{
@Override
public T test() {
return null;
}
}
複製代碼
泛型接口傳入泛型實參時
package com.corejava.genericprogramming;
public class TestGeneric implements IGeneric<String>{
@Override
public String test() {
return null;
}
}
複製代碼
有的時候,類或方法須要對類型變量加以約束。 下面是一個計算最小元素的例子。
package com.corejava.genericprogramming;
public class ArrayAlg {
public static <T> T getMin(T[] a) {
if( a == null || a.length == 0)
return null;
T min = a[0];
for (T t : a) {
if(min.compareTo(t) > 0)
min = t;
}
return min;
}
}
複製代碼
這裏有個問題,T是任意類型,可是不能保證T含有compareTo方法,在編寫代碼過程當中會直接報錯。
解決方案即是給T加上限定,使其實現Comparable接口。
public static <T extends Comparable> T getMin(T[] a){...}
複製代碼
一個類型變量或者通配符能夠有多個限定。
不管限定是接口仍是類,只用extends來鏈接。
選擇關鍵字extends的緣由是更接近子類的概念。
限定類型用&分隔,而逗號用來分隔類型變量。
類型變量的限定類型中能夠有多個接口,但最多一個類。
若是須要用一個類做爲限定,它必須是限定列表中的第一個。
T extends Comparable & Serializable
複製代碼
請注意,虛擬機沒有泛型類型對象——全部對象屬於普通類。
泛型類型的原始類型是刪除類型參數後的泛型類型名。
擦除類型變量,而且替換成限定類型。
若是沒有限定類型,使用Object。
例如,Pair的原始類型以下所示。
package com.corejava.genericprogramming;
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 void setFirst(Object first) {
this.first = first;
}
public Object getSecond() {
return second;
}
public void setSecond(Object second) {
this.second = second;
}
}
複製代碼
若是泛型類型含有限定的類型變量,那麼原始類型可使用第一個限定的類型變量替換。若是沒有限定就用Object替換。
代碼示例:
package com.corejava.genericprogramming;
import java.io.Serializable;
public class Interval <T extends Comparable & Serializable> implements Serializable{
private T lower;
private T upper;
public Interval(T first, T second) {
if(first.compareTo(second) <= 0) {
lower = first;
upper = second;
}else {
lower = second;
upper = first;
}
}
}
複製代碼
原始類型:
package com.corejava.genericprogramming;
import java.io.Serializable;
public class Interval implements Serializable{
private Comparable lower;
private Comparable upper;
public Interval(Comparable first, Comparable second) {
if(first.compareTo(second) <= 0) {
lower = first;
upper = second;
}else {
lower = second;
upper = first;
}
}
}
複製代碼
以以前的Pair類爲例,只有Pair<Double>,沒有Pair<double>。
緣由:
擦除以後,Pair類含有Object類的域,而Object不能存儲double值。
筆者在這裏其實有個疑惑,就算擦除以後,爲何Object不能存放double?
double是能夠自動裝箱成Double,而Double做爲一個對象類型是能夠被Object存儲的。
請看下列兩段代碼。
package com.corejava.genericprogramming;
public class Generic<T> {
private T first;
private T second;
public Generic(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
}
複製代碼
package com.corejava.genericprogramming;
import org.junit.jupiter.api.Test;
class GenericTest {
@Test
void test() {
Generic<String> genericStr = new Generic<>("first", "second");
//error:Cannot perform instanceof check against parameterized type Generic<String>.
//Use the form Generic<?> instead since further generic type information will be erased at runtime
//System.out.println(genericStr instanceof Generic<String>);
//error:Cannot perform instanceof check against parameterized type Generic<String>.
//Use the form Generic<?> instead since further generic type information will be erased at runtime
//System.out.println(genericStr instanceof Generic<T>);
//輸出:true
System.out.println(genericStr instanceof Generic);
//輸出:class com.corejava.genericprogramming.Generic
System.out.println(genericStr.getClass());
}
}
複製代碼
因而可知,若是想查詢一個對象是否屬於某個泛型類型時,使用instanceof會獲得一個編譯器錯誤。
若是使用getClass方法則會返回一個原始類型。
不能實例化參數化類型的數組
package com.corejava.genericprogramming;
import org.junit.jupiter.api.Test;
class GenericTest {
@Test
void test() {
//實際這行代碼會報錯,報錯信息:Cannot create a generic array of Generic<String>
Generic<String>[] array = new Generic<String>[10];
//類型擦除後,array的類型即爲Generic[]。
//能夠把它轉換成Object[]類型。
Object[] objarray = array;
//數組會記住它的元素類型,當傳入其餘類型元素,本應會報錯。
//可是對於泛型來講,擦除使檢查機制無效。
//出於這個緣由,參數化類型的數組不容許被建立。
objarray[0] = "hello";
}
}
複製代碼
須要說明的是,聲明類型爲Generic[]的變量還是合法的,可是不能用new Generic[10]進行初始化。
若是須要收集參數類型化對象,只能使用ArrayList。
不能使用new T(...)或者new T[...]或T.class這樣的表達式中的類型變量。 下面的構造器是非法的。
public Generic(){
first = new T();
second = new T();
}
複製代碼
public static <T extends Comparable> T[] minmax(T[] a) {
//error:
T[] mm = new T[2];
}
複製代碼
不能在靜態域或方法中引用類型變量。 請看代碼:
package com.corejava.genericprogramming;
public class Generic<T> {
private T first;
private T second;
public Generic(T first, T second) {
super();
this.first = first;
this.second = second;
}
public T getFirst() {
return first;
}
public void setFirst(T first) {
this.first = first;
}
public T getSecond() {
return second;
}
public void setSecond(T second) {
this.second = second;
}
public void test(T t) {
System.out.println("hello");
}
//若是不聲明T,會給出下列報錯信息。
//error: Cannot make a static reference to the non-static type T
public static <T> void name(T t) {
System.out.println("hello");
}
}
複製代碼
既不能拋出也不能捕獲泛型類對象。
實際上,甚至泛型類拓展Throwable都是不合法的。
//The generic class Generic<T> may not subclass java.lang.Throwable
public class Generic<T> extends Exception{...}
複製代碼
不管S與T有什麼聯繫,一般,Pair<E>與Pair<T>沒有什麼聯繫。 請看下列代碼。
package com.corejava.genericprogramming;
import org.junit.jupiter.api.Test;
class GenericTest {
@Test
void test() {
Manager ceo = new Manager();
Manager cfo = new Manager();
Employee employee = new Employee();
Generic<Manager> managerGroup = new Generic<>(ceo, cfo);
//error:Type mismatch: cannot convert from Generic<Manager> to Generic<Employee>
Generic<Employee> employeeGroup = managerGroup;
employeeGroup.setFirst(cfo);
}
}
複製代碼
注意泛型和數組之間的區別。
package com.corejava.genericprogramming;
import org.junit.jupiter.api.Test;
class GenericTest {
@Test
void test() {
Manager ceo = new Manager();
Manager cfo = new Manager();
Employee lowerEmployee = new Employee();
Manager[] managerGroup = new Manager[] {ceo, cfo};
//操做合法
Employee[] employeeGroup = managerGroup;
//error: Type mismatch: cannot convert from Employee to Manager
managerGroup[0] = lowerEmployee;
}
}
複製代碼
通配符類型中,容許類型參數變化。
例如,通配符類型Pair< ? extends Employee>表示任何泛型Pair類型,它的類型參數是Employee的子類。
假設要編寫一個方法。
package com.corejava.genericprogramming;
public class GenericTest {
public static void printGroup(Generic<Employee> g) {
System.out.println(g.getFirst().getName() + g.getSecond().getName());
}
}
複製代碼
可是這個方法不能將Generic傳入。
public static void main(String[] args) {
Manager m1 = new Manager();
Manager m2 = new Manager();
m1.setName("mat");
m2.setName("xu");
Generic<Manager> gm = new Generic<Manager>(m1, m2);
//error:The method printGroup(Generic<Employee>) in the type GenericTest is not applicable for the arguments (Generic<Manager>)
printGroup(gm);
}
複製代碼
解決的辦法即是使用通配符類型。
public static void printGroup(Generic<? extends Employee> g) {...}
複製代碼
但筆者在這裏有個疑惑,爲何不能這麼寫。
public static void printGroup(Generic<T extends Employee> g) {...}
複製代碼
Eclipse給出的報錯信息是:
Incorrect number of arguments for type Generic<T>; it cannot be parameterized with arguments <T, Employee>
複製代碼
也就是說其實<? extends Employee>算一個參數,而算兩個參數。 對應的泛型類Generic只包含一個類型參數,因此後者不能經過。
本文參考了大量文章,將來會加入《Java編程思想》和《Effective Java》的內容和觀點。目前主要內容仍是來自《Java核心技術》,經過這篇博文的撰寫,原先比較陌生的Java泛型如今也比較熟悉了。可是仍然未涉及其複雜之處。若是須要轉載,請註明出處!若是有什麼疑問或者看法,請分享你的觀點!謝謝你們!
參考連接