Java/Scala 泛型快速入門(mén)教程
泛型(Generics)是強(qiáng)類(lèi)型編程語(yǔ)言中經(jīng)常使用的一種技術(shù)。很多框架的代碼中都會(huì)大量使用到泛型,比如在Java中我們經(jīng)常看到的:
- List<String> strList = new ArrayList<String>();List<Double> doubleList = new LinkedList<Double>();
在這段代碼中,ArrayList就是一個(gè)泛型類(lèi),List就是一個(gè)泛型接口類(lèi),他們提供給開(kāi)發(fā)者一個(gè)放置不同類(lèi)型的集合容器,我們可以向這個(gè)集合容器中添加String、Double以及其他各類(lèi)數(shù)據(jù)類(lèi)型。無(wú)論內(nèi)部存儲(chǔ)的是什么類(lèi)型,集合容器提供給開(kāi)發(fā)者的功能都是相同的,比如添加add,get等。有了泛型,我們就沒(méi)必要?jiǎng)?chuàng)建StringArrayList、DoubleArrayList等集合了,否則代碼量太大,維護(hù)起來(lái)成本極高。
在Java中,泛型一般有三種使用方式:泛型類(lèi),泛型方法和泛型接口類(lèi)。一般使用尖括號(hào)<>來(lái)接收泛型參數(shù)。
Java泛型類(lèi)
假如我們自己定義一個(gè)支持泛型的MyArrayList,這個(gè)列表類(lèi)可以簡(jiǎn)單支持初始化和數(shù)據(jù)寫(xiě)入。只要在類(lèi)名后面加上
- public class MyArrayList<T> { private int size; T[] elements; public MyArrayList(int capacity) { this.size = capacity; this.elements = (T[]) new Object[capacity]; } public void set(T element, int position) { elements[position] = element; } @Override public String toString() { String result = ""; for (int i = 0; i < size; i++) { result += elements[i].toString(); } return result; } public static void main(String[] args){ MyArrayList<String> strList = new MyArrayList<String>(2); strList.set("first", 0); strList.set("second", 1); System.out.println(strList.toString()); }}
我們也可以從父類(lèi)中繼承并擴(kuò)展泛型,比如Flink源碼中有這樣一個(gè)類(lèi)定義,子類(lèi)繼承了父類(lèi)的T,同時(shí)自己增加了泛型KEY:
- public class KeyedStream<T, KEY> extends DataStream<T> { ...}
Java泛型接口類(lèi)
Java泛型接口類(lèi)的定義和Java泛型類(lèi)基本相同。下面的代碼展示了List接口中定義subList方法,該方法截取原來(lái)列表的一部分。
- public interface List<E> { ... public List<E> subList(int fromIndex, int toIndex);}
繼承并實(shí)現(xiàn)這個(gè)接口類(lèi)的代碼如下:
- public class ArrayList<E> implements List<E> { ... public List<E> subList(int fromIndex, int toIndex) { subListRangeCheck(fromIndex, toIndex, size); return new SubList(this, 0, fromIndex, toIndex); }}
Java泛型方法
泛型方法可以存在于泛型類(lèi)(包括接口類(lèi))中,也可以存在于普通的類(lèi)中。
- public class MyArrayList<T> { ... // public關(guān)鍵字和返回值E之間的<E>表明這是一個(gè)泛型方法 // 泛型方法中的類(lèi)型E和泛型類(lèi)中的類(lèi)型T可以不一樣 public <E> E processElement(E element) { ... return E; }}
從上面的代碼示例可以看出,public或private關(guān)鍵字和方法返回值之間的尖括號(hào)
通配符
除了用
泛型小結(jié)
對(duì)Java的泛型總結(jié)下來(lái)發(fā)現(xiàn),雖然它的語(yǔ)法有時(shí)候讓人有些眼花繚亂,其本質(zhì)是為了接受不同的數(shù)據(jù)類(lèi)型,增強(qiáng)代碼的復(fù)用性。
我們可以在一個(gè)類(lèi)里使用多個(gè)泛型,每個(gè)泛型一般使用大寫(xiě)字母表示。Java為此提供了一些大寫(xiě)字母使用規(guī)范:
- T 代表一般的任何類(lèi)。
- E 代表元素(Element)或異常(Exception)。
- K 代表鍵(Key)。
- V 代表值(Value),通常與K一起配合使用,比如
Java的泛型給開(kāi)發(fā)者提供了不少便利,尤其是保證了底層代碼簡(jiǎn)潔性,因?yàn)檫@些底層代碼通常被封裝為一個(gè)框架,會(huì)有各種各樣的上層應(yīng)用調(diào)用這些底層代碼進(jìn)行特定的業(yè)務(wù)處理,每次調(diào)用都可能涉及泛型問(wèn)題。比如,大數(shù)據(jù)框架Spark和Flink中都需要開(kāi)發(fā)者基于泛型進(jìn)行數(shù)據(jù)處理。
以上只對(duì)泛型做了一個(gè)簡(jiǎn)單的介紹,實(shí)際上在具體使用時(shí)還有一些細(xì)節(jié)需要注意。
類(lèi)型擦除
Java的泛型有一個(gè)遺留問(wèn)題,那就是類(lèi)型擦除(Type Erasure)。我們先看一下下面的代碼:
- Class<?> strListClass = new ArrayList<String>().getClass();Class<?> intListClass = new ArrayList<Integer>().getClass();// 輸出:class java.util.ArrayListSystem.out.println(strListClass);// 輸出:class java.util.ArrayListSystem.out.println(intListClass);// 輸出:trueSystem.out.println(strListClass.equals(intListClass));
雖然聲明時(shí)我們分別使用了String和Integer,但運(yùn)行時(shí)關(guān)于泛型的信息被擦除了,我們無(wú)法區(qū)別strListClass和intListClass這兩個(gè)類(lèi)型。這是因?yàn)椋盒托畔⒅淮嬖谟诖a編譯階段,當(dāng)程序運(yùn)行到JVM上時(shí),與泛型相關(guān)的信息會(huì)被擦除掉。類(lèi)型擦除對(duì)于絕大多數(shù)應(yīng)用系統(tǒng)開(kāi)發(fā)者來(lái)說(shuō)關(guān)系不太大,但是對(duì)于一些框架開(kāi)發(fā)者來(lái)說(shuō),必須要注意。比如,Spark和Flink的開(kāi)發(fā)者都使用了一些辦法來(lái)解決類(lèi)型擦除問(wèn)題,對(duì)于API調(diào)用者來(lái)說(shuō),受到的影響不大。
Scala中的泛型
對(duì)Java的泛型有了基本了解后,我們接著來(lái)了解一下Scala中的泛型。相比而言,Scala的類(lèi)型系統(tǒng)更復(fù)雜,本文只介紹一些簡(jiǎn)單語(yǔ)法,幫助讀者能夠讀懂一些源碼。
Scala中,泛型放在了中括號(hào)[]中。或者我們可以簡(jiǎn)單地理解為,原來(lái)Java的泛型類(lèi)
我們創(chuàng)建一個(gè)Stack[T]的泛型類(lèi),并實(shí)現(xiàn)了兩個(gè)簡(jiǎn)單的方法,類(lèi)中各成員和方法都可以使用泛型T。我們也定義了泛型方法,形如isStackPeekEquals[T],方法中可以使用泛型T。
- object MyStackDemo { // Stack泛型類(lèi) class Stack[T] { private var elements: List[T] = Nil def push(x: T) { elements = x :: elements } def peek: T = elements.head } // 泛型方法,檢查兩個(gè)Stack頂部是否相同 def isStackPeekEquals[T](p: Stack[T], q: Stack[T]): Boolean = { p.peek == q.peek } def main(args: Array[String]): Unit = { val stack = new Stack[Int] stack.push(1) stack.push(2) println(stack.peek) val stack2 = new Stack[Int] stack2.push(2) val stack3 = new Stack[Int] stack3.push(3) println(isStackPeekEquals(stack, stack2)) println(isStackPeekEquals(stack, stack3)) }}
總結(jié)
本文簡(jiǎn)單介紹了Java/Scala的泛型,它允許數(shù)據(jù)類(lèi)型是可變,提升了代碼的復(fù)用性,是很多框架都會(huì)采用的技術(shù),開(kāi)發(fā)者非常有必要了解泛型的基本用法。





















