1. 程式人生 > 其它 >簡述Java的泛型機制

簡述Java的泛型機制

  • 泛型類

什麼時候定義泛型類?在類中要操作的資料型別不明確的時候,早期通過定義Object來完成擴充套件,而現在定義泛型來完成擴充套件,集合類就是使用了泛型的一個經典例子。

class Person{}
class Utils<QQ>{
	private QQ q;
	public void setObject(QQ q){
		this.q=q;
	}
	public QQ getObject(){
		return q;
	}
}
public class GenericDemo{
	public static void main(String[] args){
		Utils<Person> u=new Utils<Person>();
		u.setObject(new Person());
		Person p=u.getObject();
		System.out.println(p); //Person@512ddf17
	}
}
  • 泛型方法

class Person<T>{
	private T t;
	public Person(T t){
		this.t=t;
	}
	public void set(T t){  //這不是泛型方法
		this.t=t;
	}
	public T get(){  //這不是泛型方法
		return t;
	}
	public <Q> void print1(Q q){ //泛型方法的格式:<型別>寫在返回值前面、訪問修飾符後面
		System.out.println(q);
	}
	public <T> void print2(T t){
		System.out.println(t);
		//泛型方法中定義的T和泛型類上定義的T沒有關係,它們獨立存在
		//只有非泛型方法中使用的T才是泛型類限定的T,如上面的get()和set()
	}
}

注意:類上限定的<T>不能用於靜態方法
如:public static void method(T t){ }這種寫法是錯誤的。

正確寫法為:public static <T> void method(T t){ }這是泛型方法,此處的<T>與類限定的<T>無關。

  • 泛型方法的實際應用

import java.util.Arrays;

public class Demo{
	public static void main(String[] args){
		String[] arr1 ={"a","b","c"};
		swap(arr1,0,2);
		System.out.println(Arrays.toString(arr1)); //[c, b, a]
		Integer[] arr2 = {1,2,3};
		//泛型要求包容的是引用型別,而基本資料型別在Java中不屬於引用型別,但是基本資料型別有其包裝類可以使用
		swap(arr2,0,2);
		System.out.println(Array.toString(arr2)); //[3, 2, 1]
	}
	public static <T> void swap(T[] arr,int index1,int index2){
		T temp = arr[index1];
		arr[index1] = arr[index2];
		arr[index2] = temp;
	}
}

從上面的例子可以看出使用泛型的方便之處。如果按照早期的思想,這裡要定義Object[]來接收引數。

  • 泛型介面

interface Inter<T>{ //泛型介面
	void method(T t);
}
class Student{}

class Person implements Inter<Student>{ //實現介面時傳入實際型別
	private Student s;
	public void method(Student s){
	//由於實現介面時傳入了具體型別,所以實現了介面的這個方法也必須是那個具體的型別,否則編譯報錯。
		System.out.println(s); //Student@2c13da15
	}
}

class A<T> implements Inter<T>{//如果定義實現類時也不明確傳入的具體型別,可以這樣寫
//然後當類建立物件時使用泛型傳入具體的實際型別,則介面的泛型也會跟著確定。
	private T t;
	public void method(T t){
		System.out.println(t); //由泛型傳入的型別決定。
	}
}
public class GenericDemo{
	public static void main(String[] args){
		Person p = new Person();
		p.method(new Student());
		A<String> a = new A<String>();
		//a.methood(1);編譯報錯
		//a.method("hello");完全OJBK
	}
}
  • 泛型萬用字元

先來看一段原始程式碼:

import java.util.*;

public class GenericDemo{
	public static void main(String[] args){
		ArrayList<String> al1 = new ArrayList<String>();
		al1.add("java1");
		al1.add("java2");
		al1.add("java3");

		ArrayList<Integer> al2 = new ArrayList<Integer>();
		al2.add(1);
		al2.add(2);
		al2.add(3);

		printList(al1);
		printList(al2);
	}
	public static void printList(ArrayList<String> al){
		for(Iterator<String> it = al.iterator();it.hasNext();){
			System.out.println(it.next());
		}
	}
}

這段程式碼是會報錯的,因為ArrayList泛型是String型別,所以無法傳入泛型為Integer的ArrayList實參,那怎麼辦呢?可以用萬用字元來解決:

import java.util.*;

public class GenericDemo{
	public static void main(String[] args){
		ArrayList<String> al1 = new ArrayList<String>();
		al1.add("java1");
		al1.add("java2");
		al1.add("java3");

		ArrayList<Integer> al2 = new ArrayList<Integer>();
		al2.add(1);
		al2.add(2);
		al2.add(3);

		printList(al1);
		printList(al2);
	}
	public static void printList(ArrayList<?> al){
		for(Iterator<?> it = al.iterator();it.hasNext();){
			System.out.println(it.next());
		}
	}
}

萬用字元“?”,代表未知型別,有點類似於Object型別的形參接收任意型別的實參一樣。萬用字元並不是一定要用“?”,任意一個字母比如T,V,K都可以,但是二者有區別:使用字母做為萬用字元的話,要使用泛型方法的格式

比如:public static <T> void printList(ArrayList<T> al){}前面的不可省略,而使用<?>如例圖所示不用。

還有一個區別是:字母萬用字元代表的是具體型別,可以用來定義變數,而“?”不行:

public static <T> void printList(ArrayList<T> al){
	T t;
	for(Iterator<T> it = al.iterator();it.hasNext();){
		t=it.next();
		System.out.println(t);
	}
}
  • 泛型限定

學了泛型萬用字元,接下來就以萬用字元為基礎,進一步瞭解一下泛型限定的應用:
先來看個程式碼:

import java.util.*;

class Person{ }
class Student extends Person{ }

public class GenericDemoo{
	public static void main(String[] args){
		ArrayList<Person> al1 = new ArrayList<Person>();
		al1.add(new Person());
		ArrayList<Student> al2 = new ArrayList<Student>();
		al2.add(new Student());

		printList(al1);
		printList(al2);
	}
	public static void printList(ArrayList<> al){
		for(Iterator<> it = al.iterator();it.hasNext();){
			System.out.println(it.next());
		}
	}
}

程式碼中的泛型沒有寫出來,那麼應該怎麼寫比較合適?
寫“?”,還是"Person"?。 都不對,寫“?”會導致泛型範圍太大,根據題意,該泛型只需要Person和Student,而寫Person又會導致Student無法傳入導致編譯失敗。

正確寫法是<? extends Person>代表未知型別,但是這個未知型別有一定的範圍,是Person類或者Person類的子類,叫做泛型的上限限定

同理,還有 <? super Student> 代表Student類或者Student類的父類,叫做泛型的下限限定。

但是新的問題又出現了:用<? extends Person>的話下面的Iterator<>中無法使用"?",那麼怎麼辦呢?正確應該寫為:

public static <T extends Person> void printList(ArrayList<T> al){
	for(Iterator<T> it = al.iterator();it.hasNext();){
		System.out.println(it.next());
	}
}

用字母萬用字元來代替"?",字母萬用字元與"?"不同,必須在返回值之前宣告它

在泛型方法中新增上下限定時,必須在許可權宣告與返回值之間的字母萬用字元新增上下邊界,即在泛型宣告的時候新增

public <T> void printList(ArrayList<T extends Person> al){ }
編譯器會報錯:"Unexpected bound"

public <T extends Person> void printList(ArrayList<T> al){ }
正確

  • 泛型擦除

泛型是提供給javac編譯器使用的,它用於限定輸入型別,讓編譯器在原始碼級別上擋住非法型別資料,但編譯器編譯完帶有泛型的java程式後,生成的class檔案中將不再帶有泛型資訊,以使程式的執行效率不受到影響
這個過程稱之為泛型擦除