[CoreJava]Java泛型詳解

/**
 * @version 1.0
 * @author 子月初七
 */
複製代碼

1.泛型簡介

泛型是從Java1.5開始引進的,所謂的泛型能夠理解成參數化類型,即類型是以參數的方式傳入泛型類或者泛型方法。 泛型這個術語的意思是:「適用於許多許多的類型」。java

1.1 泛型的好處

泛型可使編寫的代碼被不少不一樣的類型對象所重用。編程

使用泛型機制編寫的程序代碼要比那些雜亂地使用Object變量,而後再進行強制類型轉換的代碼具備更好的安全性和可讀性。泛型對於集合類尤爲有用,例如,ArrayList就是一個無處不在的集合。api

泛型類能夠當作普通類的工廠。數組

  • 使用泛型以前
package com.corejava.genericprogramming;

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

}
複製代碼

這種方法有兩個問題。安全

  1. 獲取值的時候必須強制轉換。容易出現類型轉換錯誤。
ArrayList files = new ArrayList();
 String filename = (String) files.get(0);
複製代碼
  1. 能夠向數組列表添加任意類型對象。
  • 使用泛型以後
//ArrayList類有一個類型參數用來指示元素的種類
ArrayList<String> files = new ArrayList<String>();
//JavaSE 7後,構造函數能夠省略泛型類型。
ArrayList<String> files = new ArrayList<>();
複製代碼

當調用get的時候,不須要進行強制類型轉換。 編譯器就知道返回值類型爲String,而不是Object。bash

String filename = files.get(0);
複製代碼

1.2 泛型類 Generic Class

具備一個或多個類型變量的類稱之爲泛型類。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等表示任意類型。

1.3 泛型方法 Generic Method

泛型方法能夠定義在普通類中,也能夠定義在泛型類中。

注意,類型變量放在修飾符後面,返回類型前面。

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"));
    }
}

複製代碼

1.4 泛型接口 Generic Interface

以下定義一個泛型接口。

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;
	}

}
複製代碼

1.5 類型變量的限定

有的時候,類或方法須要對類型變量加以約束。 下面是一個計算最小元素的例子。

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
複製代碼

1.6 泛型代碼和虛擬機

請注意,虛擬機沒有泛型類型對象——全部對象屬於普通類。

1.6.1 類型擦除

泛型類型的原始類型是刪除類型參數後的泛型類型名。

擦除類型變量,而且替換成限定類型。

若是沒有限定類型,使用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;
		}
	}
}

複製代碼

2. 泛型的約束與侷限性

2.1 不能使用基本類型實例化類型參數

以以前的Pair類爲例,只有Pair<Double>,沒有Pair<double>。

緣由:

擦除以後,Pair類含有Object類的域,而Object不能存儲double值。

筆者在這裏其實有個疑惑,就算擦除以後,爲何Object不能存放double?

double是能夠自動裝箱成Double,而Double做爲一個對象類型是能夠被Object存儲的。

2.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;
	}
}

複製代碼
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方法則會返回一個原始類型。

2.3 不能建立參數化類型的數組

不能實例化參數化類型的數組

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。

2.4 不能實例化類型變量

不能使用new T(...)或者new T[...]或T.class這樣的表達式中的類型變量。 下面的構造器是非法的。

public Generic(){
        first = new T();
        second = new T();
    }
複製代碼

2.5 不能構造泛型數組

public static <T extends Comparable> T[] minmax(T[] a) {
        //error:
		T[] mm = new T[2];
	}
複製代碼

2.6 泛型類的靜態上下文中類型變量無效

不能在靜態域或方法中引用類型變量。 請看代碼:

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");
	}
}
複製代碼

2.7 不能拋出或捕獲泛型類的實例

既不能拋出也不能捕獲泛型類對象。

實際上,甚至泛型類拓展Throwable都是不合法的。

//The generic class Generic<T> may not subclass java.lang.Throwable
public class Generic<T> extends Exception{...}
複製代碼

3. 泛型類型的繼承規則

不管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;
	}

}

複製代碼

4. 通配符類型

4.1 通配符概念

通配符類型中,容許類型參數變化。

例如,通配符類型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只包含一個類型參數,因此後者不能經過。

5. 結語

本文參考了大量文章,將來會加入《Java編程思想》和《Effective Java》的內容和觀點。目前主要內容仍是來自《Java核心技術》,經過這篇博文的撰寫,原先比較陌生的Java泛型如今也比較熟悉了。可是仍然未涉及其複雜之處。若是須要轉載,請註明出處!若是有什麼疑問或者看法,請分享你的觀點!謝謝你們!

參考連接

  1. java 泛型詳解-絕對是對泛型方法講解最詳細的,沒有之一
  2. 《Java核心技術卷I》
  3. Java泛型
  4. Java Generics FAQs - Frequently Asked Questions
  5. 《Java編程思想》
  6. 《Effective Java》
相關文章
相關標籤/搜索