Java中關於泛型的淺析

前言

java 泛型是java SE 1.5的新特性,泛型的本質是參數化類型,也就是說所操作的數據類型被指定爲一個參數。這種參數類型可以用在類、接口和方法的創建中,分別稱爲泛型類、泛型接口、泛型方法。

泛型(Generic type 或者 generics)是對 Java 語言的類型系統的一種擴展,以支持創建可以按類型進行參數化的類。可以把類型參數看作是使用參數化類型時指定的類型的一個佔位符,就像方法的形式參數是運行時傳遞的值的佔位符一樣。

爲什麼使用泛型

問題引入

泛型是在java SE 1.5中引入的,在這之前,如果要使用集合的話是怎麼樣子的呢?對於集合List而言,往裏面添加數據的方法add的參數是一個Object類型,這也就意味着什麼數據類型都可以往裏面添加。

List list = new ArrayList();
	list.add("abc");
	list.add("cdf");

然而,添加容易取出來就有點麻煩了。因爲我們取的時候需要進行強制轉換:

String s = (String) list.get(0);

有的時候,我們忘了添加進去是什麼類型了,或者我們寫的方法,類給別人使用的時候,這個時候就會很容易出錯,因爲我們不知道是什麼類型,因此在運行的時候就會出現異常。

List list = new ArrayList();
		list.add("abc");
		list.add("cdf");
		list.add(12);
		list.add(23.9);

		for (int i = 0; i < list.size(); i++) {
			String tempString = (String) list.get(i);
			System.out.println(tempString);
		}

在這裏插入圖片描述

解決方法

對於上述引發的問題,我們可以通過限定傳入數據的類型來解決,就是在add數據的時候我們就將它限定爲某一類型,其他類型的數據add不進去,這樣的話,我們就很簡單的解決了該問題。這種方法就是下面需要說的泛型。

List<String> list = new ArrayList<>();
		list.add("abc");
		list.add("cdf");

通過限定add時傳入的類型是String,這樣在編譯時就能檢測出來,這樣就能避免了上述問題。

泛型分類

泛型有三種使用方式,分別爲:泛型類、泛型接口、泛型方法。

泛型類

泛型類說起來比較抽象,我們通過一個示例來了解一下:

/**
 * 此處T可以隨便寫爲任意標識,常見的如T、E、K、V等形式的參數常用於表示泛型
 * 
 * @author admin
 *
 * @param <T>
 */
public class Generic<T> {

	/**
	 * 這個成員變量的類型爲T,T的類型由外部指定
	 */
	private T t;

	/**
	 * 泛型構造方法形參t的類型也爲T,T的類型由外部指定
	 * 
	 * @param t
	 */
	public Generic(T t) {
		super();
		this.t = t;
	}

	/**
	 * 泛型方法getT的返回值類型爲T,T的類型由外部指定
	 * 
	 * @return
	 */
	public T getT() {
		return t;
	}

	/**
	 * 泛型方法setT的參數類型爲T,T的類型由外部指定
	 * 
	 * @param t
	 */
	public void setT(T t) {
		this.t = t;
	}
}

泛型類比較簡單,需要指定一個參數類型T,然後由外部傳入,我們看一下這個泛型類它的使用方式:

//泛型參數T這裏指定爲String類型
Generic<String> generic = new Generic<String>("Hello");
System.out.println(generic.getT());

打印結果如下:

Hello

這裏需要注意的是:泛型的類型參數只能是類類型(包括自定義類),不能是簡單類型。

泛型接口

泛型接口與泛型類的定義及使用基本相同。我們也通過示例來了解一下:

/**
 * 定義一個泛型接口
 * 
 * @author admin
 *
 * @param <T>
 */
public interface IGeneric<T> {

	T result();

}

定義一個泛型接口後,使用這個泛型接口有兩種情況:

未傳入泛型參數
/**
 * 未傳入泛型實參時,與泛型類的定義相同,在聲明類的時候,需將泛型的聲明也一起加到類中
 * 
 * @author admin
 *
 * @param <T>
 */
public class Generic1Test<T> implements IGeneric<T> {

	@Override
	public T result() {
		return null;
	}

}
傳入參數類型
/**
 * 在實現類實現泛型接口時,如已將泛型類型傳入實參類型,則所有使用泛型的地方都要替換成傳入的實參類型
 * 
 * @author admin
 *
 */
public class Generic2Test implements IGeneric<String> {

	@Override
	public String result() {

		return "";
	}
}

泛型方法

泛型方法,是在調用方法的時候指明泛型的具體類型 ,但是相對於泛型類而言,泛型方法就比較複雜了,我們來看一個具體示例:

/**
	 * 聲明一個泛型方法,該泛型方法中帶一個T類型形參,
	 * 
	 * @param <T>
	 * @param a
	 * @param c
	 */
	static <T> void fromArrayToCollection(T[] a, List<T> c) {
		for (T o : a) {
			c.add(o);
		}
	}

申明一個泛型方法時,首先在public與返回值之間的必不可少,這表明這是一個泛型方法,並且聲明瞭一個泛型T,這個T可以出現在這個泛型方法的任意位置,泛型的數量也可以爲任意多個。

對於泛型參數的申明比較複雜的多,後面需要專門的講解。

泛型通配符

我們在定義泛型類,泛型方法,泛型接口的時候經常會碰見很多不同的通配符,比如 T,E,K,V ,?等等,這些通配符又都是什麼意思呢?

其實,這些不同的通配符沒什麼本質上的區別,只不過在Java開發過程中我們約定了這些不同的通配符所表達的意思不一樣:

  1. ?表示不確定的 java 類型
  2. T (type) 表示具體的一個java類型
  3. K V (key value) 分別代表java鍵值中的Key, Value
  4. E (element) 代表Element

無界通配符 <?>

對於通配符<?>我們可以理解爲無界,就是<?>, 比如List<?>,通配符<?>的主要作用就是讓泛型能夠接受未知類型的數據。如果泛型的類型只在方法聲明中出現一次,就可以用通配符<?>取代它。

我們說明一點:List<?> list和List list的區別:

  1. List<?> list是表示持有某種特定類型對象的List,但是不知道是哪種類型;List
    list是表示持有Object類型對象的List。
  2. List<?> list因爲不知道持有的實際類型,所以不能add任何類型的對象,但是List
    list因爲持有的是Object類型對象,所以可以add任何類型的對象。

注意:List<?> list可以add(null),因爲null是任何引用數據類型都具有的元素。

上界通配符 < ? extends E>

用 extends 關鍵字聲明,表示參數化的類型可能是所指定的類型,或者是此類型的子類。

下界通配符 < ? super E>

用 super 進行聲明,表示參數化的類型可能是所指定的類型,或者是此類型的父類型,直至 Object。

泛型擦除

Java的泛型是僞泛型,這是因爲Java在編譯期間,所有的泛型信息都會被擦掉,正確理解泛型概念的首要前提是理解類型擦除。Java的泛型基本上都是在編譯器這個層次上實現的,在生成的字節碼中是不包含泛型中的類型信息的,使用泛型的時候加上類型參數,在編譯器編譯的時候會去掉,這個過程成爲類型擦除。

譬如:
對於List類型,在編譯後會變成List,JVM看到的只是List,而由泛型附加的類型信息對JVM是看不到的。Java編譯器會在編譯時儘可能的發現可能出錯的地方。

我們舉個例子來說明泛型擦除:

public static void main(String[] args) {
		List<String> strList = new ArrayList<String>();
		strList.add("AAAAA");

		List<Integer> intList = new ArrayList<>();
		intList.add(123);

		System.out.println("編譯時期類型是否相同: " + (strList.getClass() == intList.getClass()));
	}

打印結果如下:
在這裏插入圖片描述

從上面的例子看出,我們定義了兩個ArrayList數組,不過一個是ArrayList泛型類型的,只能存儲字符串;一個是ArrayList泛型類型的,只能存儲整數,最後,我們通過list1對象和list2對象的getClass()方法獲取他們的類的信息,最後發現結果爲true。說明泛型類型String和Integer都被擦除掉了,只剩下原始類型。

泛型好處

Java 語言中引入泛型是一個較大的功能增強。不僅語言、類型系統和編譯器有了較大的變化,以支持泛型,而且類庫也進行了大翻修,所以許多重要的類,比如集合框架,都已經成爲泛型化的了。

  1. 類型安全。 泛型的主要目標是提高 Java 程序的類型安全。
  2. 消除強制類型轉換。 泛型的一個附帶好處是,消除源代碼中的許多強制類型轉換。這使得代碼更加可讀,並且減少了出錯機會。

總結

在上面所舉的例子都是一些簡單的示例並不具有實際的應用,只是爲了簡單說明泛型的概念。本篇文章只是作爲泛型的簡單闡述,對於開發過程中的泛型還需要我們深入的探索,後期會出一遍深入理解泛型的文章,敬請期待。

關於作者

專注於 Android 開發多年,喜歡寫 blog 記錄總結學習經驗,blog 同步更新於本人的公衆號,歡迎大家關注,一起交流學習~

在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章