簡單泛型
1.泛型的主要目的之一就是用來指定容器要持有什么類型的對象蜀备,而且由編譯器來保證類型的正確性。
2.Java泛型的核心概念:告訴編譯器想使用什么類型惜浅,然后編譯器幫你處理一切細(xì)節(jié)。
一.一個(gè)元組類庫
1.元組(tuple):它是將一組對象直接打包儲存于其中的一個(gè)單一對象。
2.下面程序是一個(gè)二維元組赞枕,它能夠持有兩個(gè)對象:
public class TwoTuple<A, B> {
public final A first;
public final B second;
public TwoTuple(A a, B b) {
first = a;
second = b;
}
public String toString() {
return "(" + first + ", " + second + ")";
}
}
客戶端程序可以讀取first或second所引用的對象,然后可以隨心所欲地使用這兩個(gè)對象坪创。但是炕婶,它們卻無法將其他值賦予first或second。因?yàn)閒inal聲明為你買了安全保險(xiǎn)莱预,實(shí)現(xiàn)了Java編程的安全性原則柠掂,而且這種格式更加簡潔明了。
3.我們可以利用繼承機(jī)制實(shí)現(xiàn)更長的元組依沮。
一個(gè)堆棧類
1.不用LinkedList涯贞,自己實(shí)現(xiàn)的內(nèi)部鏈?zhǔn)酱鎯C(jī)制:
public class LinkedStack<T> {
private static class Node<U> {
U item;
Node<U> next;
Node() {
item = null;
next = null;
}
Node(U item, Node<U> next) {
this.item = item;
this.next = next;
}
boolean end() {
return item == null && next == null;
}
}
private Node<T> top = new Node<T>();
public void push(T item) {
top = new Node<T>(item, top);
}
public T pop() {
T result = top.item;
if (!top.end()) {
top = top.next;
}
return result;
}
}
2.另一個(gè)例子RandomList:假設(shè)我們需要一個(gè)持有特定類型對象的列表,每次調(diào)用其上的select()方法時(shí)危喉,它可以隨機(jī)地選取一個(gè)元素:
class RandomList<T> {
private ArrayList<T> storage = new ArrayList<T>();
private Random rand = new Random(47);
public void add(T t) {
storage.add(t);
}
public T select() {
return storage.get(rand.nextInt()storage.size());
}
}
泛型方法
1.可以在類中包含參數(shù)化方法宋渔,而這個(gè)方法所在的類可以是泛型類,也可以不是泛型類辜限。也就是說皇拣,是否擁有泛型方法,與其所在的類是否是泛型沒有關(guān)系薄嫡。
2.如果使用泛型方法可以取代將整個(gè)類泛型化氧急,那么就應(yīng)該只使用泛型化,另外對于一個(gè)static方法而言岂座,無法訪問泛型類的類型參數(shù)态蒂,所以如果static方法需要使用泛型能力,就必須使其成為泛型方法费什。
3.要定義泛型方法钾恢,只需將泛型參數(shù)列表置于返回值之前:
public <T> void f(T x) {
System.out.println(x.getClass().getName());
}
如果調(diào)用f()時(shí)傳入的是基本類型手素,自動打包機(jī)制就會介入其中,將基本類型的值包裝為對應(yīng)的對象瘩蚪。
一.可變參數(shù)與泛型方法
1.泛型方法與可變參數(shù)列表能夠很好的共存:
//和java.util.Arrays.asList()方法功能相同
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<T>();
for (T items : args) {
result.add(item);
}
return result;
}
二.簡化元組的使用
1.我們現(xiàn)在可以重新編寫之前的元組工具泉懦,使其成為更通用的工具類庫:
public class Tuple {
public static <A, B> TwoTuple<A, B> tuple(A a, B b) {
return new TwoTuple<A, B>(a, b);
}
......
}
下面是一個(gè)測試類:
public class TupleTest {
static TwoTuple<String, Integer> f() {
return Tuple.tuple("hi", 47);
}
static TwoTuple f2() {
return Tuple.tuple("hi", 47);
}
public static void main(String... args) {
TwoTuple<String, Integer> ttsi = f();
System.out.println(f());
System.out.println(f2());
}
}
/*
Output:
(hi, 47)
(hi, 47)
*/
在這里,方法f()返回一個(gè)參數(shù)化的TwoTuple對象疹瘦,而f2()返回的是非參數(shù)化的TwoTuple對象崩哩。編譯器并沒有關(guān)于f2()的警告信息,因?yàn)槲覀儾]有將其返回值作為參數(shù)化對象使用言沐。在某種意義上邓嘹,它被“向上轉(zhuǎn)型”為一個(gè)非參數(shù)化的TwoTuple。然而险胰,如果試圖將f2()的返回值轉(zhuǎn)型為參數(shù)化的TwoTuple汹押,編譯器就會發(fā)出警告。
擦除的神秘之處
1.ArrayList<String>和ArrayList<Integer>很容易被誤認(rèn)為是兩種不同的類型起便,但它們是相同的:
public class Test {
public static void main() {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
if (c1 == c2)
System.out.println("true");
System.out.println(Arrays.toString(c1.getTypeParameters()));
System.out.println(Arrays.toString(c2.getTypeParameters()));
}
}
/*
Output:
true
[E]
[E]
*/
首先我們可以看到棚贾,上面的程序會認(rèn)為它們是相同的類型。另外榆综,程序中使用的Class.getTypeParameters()可以返回一個(gè)TypeVariable對象數(shù)組妙痹,表示有泛型聲明所聲明的類型參數(shù),但是我們能夠發(fā)現(xiàn)的只有用作參數(shù)占位符的標(biāo)識符鼻疮。因此我們得出結(jié)論:在泛型代碼內(nèi)部怯伊,無法獲得任何有關(guān)泛型參數(shù)類型的信息。
2.Java泛型是使用擦除來實(shí)現(xiàn)的陋守,這意味著當(dāng)你在使用泛型時(shí)震贵,任何具體的類型信息都被擦除了,你唯一知道的就是你在使用一個(gè)對象水评。因此List<String>和List<Integer>在運(yùn)行時(shí)都被擦除成它們的“原生”類型List猩系。
3.由于擦除,在使用泛型時(shí)中燥,這些類型參數(shù)都會被當(dāng)作Object進(jìn)行處理寇甸。
一.擦除的問題
1.泛型不能用于顯式地引用運(yùn)行時(shí)類型的操作之中,例如轉(zhuǎn)型疗涉、instanceof操作和new表達(dá)式拿霉。
2.為了關(guān)閉由于沒有使用泛型的警告,Java提供了一個(gè)注解:
@SuppressWarnings("unchecked")
這個(gè)注解被放置在可以產(chǎn)生這類警告的方法之上咱扣,而不是整個(gè)類上绽淘。
二.邊界處的動作
public class ArrayMaker<T> {
private Class<T> kind;
private ArrayMaker(Class<T> kind) {
this.kind = kind;
}
@SuppressWarnings("unchecked")
T[] create(int size) {
return (T[]) Array.newInstance(kind, size);
}
public static void main(String... args) {
ArrayMaker<String> stringMaker = new ArrayMaker<String>(String.class);
String[] stringArray = stringMaker.create(9);
System.out.println(Arrays.toString(stringArray))
}
}
/*
Output:
[null, null, null, null, null, null, null, null, null]
*/
上面這段代碼中,由于擦除闹伪,kind實(shí)際被儲存為Class沪铭,沒有任何參數(shù)壮池。因此Array,newInstance()中并沒有收到任何具體的類型信息,因此必須轉(zhuǎn)型杀怠。
注意椰憋,對于在泛型中創(chuàng)建數(shù)組,使用Array.newInstance()是推薦的方式赔退。
2.因?yàn)椴脸诜椒w中移除了類型信息橙依,所以在運(yùn)行時(shí)的問題就是邊界:即對象進(jìn)入和離開方法的地點(diǎn)。這些正是編譯器執(zhí)行類型檢查并插入轉(zhuǎn)型代碼的地點(diǎn)硕旗。
3.在泛型中所有的動作都發(fā)生在邊界處——對傳遞進(jìn)來的值進(jìn)行額外的編譯期檢查窗骑,并插入對傳遞出去的值的轉(zhuǎn)型。典型的情況就是在get()和set()方法中使用泛型漆枚。
擦除的補(bǔ)償
1.擦除丟失了在泛型代碼中執(zhí)行某些操作的能力慧域。任何在運(yùn)行時(shí)需要知道的確切類型信息的操作都將無法工作:
public class Erased<T> {
private final int SIZE = 100;
public static void f(Object arg) {
if (arg instanceof T) {) //ERROR
T var = new T(); //ERROR
T[] array = new T[SIZE]; //ERROR
T[] array = (T) new Object[SIZE]; //ERROR
}
}
一.創(chuàng)建類型實(shí)例
1.上面的程序中對創(chuàng)建一個(gè)new T()的嘗試無法實(shí)現(xiàn),部分原因是因?yàn)椴脸硕粒硪徊糠衷蚴且驗(yàn)榫幾g器不能驗(yàn)證T具有默認(rèn)(無參)構(gòu)造器。
2.對于上面這個(gè)問題Java的解決方法是使用工廠對象來創(chuàng)建新的實(shí)例辛藻,建議使用顯式工廠碘橘,并限制其類型,使得只能接受實(shí)現(xiàn)了這個(gè)工廠的類:
interface Factory<T> {
T create();
}
class Foo<T> {
private T x;
public <F extends Factory<T>> Foo(F Factory) {
x = factory.create();
}
}
class IntegerFactory implements Factory<Integer> {
public Integer create() {
return new Integer(0);
}
}
class Widget {
public static class WidgetFactory implements Factory<Widget> {
public WidgetFactory create() {
return new Widget();
}
}
}
public class FactoryConstraint {
public static void main(String... args) {
new Foo<Integer>(new IntegerFactory());
new Foo<Widget>(new Widget.WidgetFactory());
}
}
二.泛型數(shù)組
1.不能直接創(chuàng)建泛型數(shù)組吱肌,一般想要達(dá)到相同效果的方法是在任何想要?jiǎng)?chuàng)建泛型數(shù)組的地方都使用ArrayList痘拆。
2.如果一定要?jiǎng)?chuàng)建一個(gè)泛型數(shù)組,唯一的方式就是創(chuàng)建一個(gè)被擦除類型的新數(shù)組氮墨,然后對其轉(zhuǎn)型:
class Generic<T> {}
class ArrayOfGeneric {
static Generic<Interger> gia;
public static void main(String... args) {
gia = (Generic<Interger>[]) new Generic[10];
}
}
3.因?yàn)橛辛瞬脸龜?shù)組的運(yùn)行時(shí)類型就只能是Object[]纺蛆。如果我們立即將其轉(zhuǎn)型為T(),那么在編譯期該數(shù)組的實(shí)際類型就將丟失规揪。因此桥氏,最好時(shí)機(jī)在集合內(nèi)部使用Object[],然后當(dāng)你使用數(shù)組元素時(shí)猛铅,添加一個(gè)對T[]對轉(zhuǎn)型:
class GenericArray<T> {
private Object[] array;
public GenericArray(int size) {
array = new Object[size];
}
public void put(int index, T item) {
array[index] = item;
}
@SupressWarnings("unchecked")
public T get(int index) {
return (T) array[index];
}
}
但如果試著將Object[]轉(zhuǎn)型為T[]是不行的字支,因?yàn)椋瑳]有任何方式可以推翻底層數(shù)組類型奸忽,它只能是Object[]堕伪。
4.下面是對上面代碼對一種改進(jìn),ArrayList中就使用了這種形式對轉(zhuǎn)型:
public class GenericArrayWithTypeToken<T> {
private T[] array;
@SupressWarnings("unchecked")
public GenericArrayWithTypeToken(Class<T> type, int size) {
array = (T[]) Array.newInstance(size, type);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return (T) array[index];
}
}
邊界
1.邊界使得我們可以在用于泛型的類型參數(shù)類型上設(shè)置限制條件栗菜。因?yàn)椴脸祟愋托畔⑶反疲钥梢杂脽o界泛型參數(shù)調(diào)用的方法只是那些可以用 Object調(diào)用的方法。但是疙筹,如果能夠?qū)⑦@個(gè)參數(shù)限制為某個(gè)類型子集富俄,那就可以用類型子集來調(diào)用方法禁炒。
2.為了執(zhí)行對泛型參數(shù)的限制,Java重用了extends關(guān)鍵字蛙酪。
3.對泛型進(jìn)行參數(shù)限制也有多繼承齐苛,并且也可以通過繼承消除冗余:
interface A{}
interface B {}
class C{}
class D extends C implements A, B {}
class E<T extends C & A & B> {}
class F {
public F() {
E<D> e = new E<D>();
}
}
4.下面的程序展示了如何在繼承的每個(gè)層次上添加邊界限制:
class A {
void set();
}
class B<T> {
T item;
public B(T item) {
this.item = item;
}
}
class C<T extends A> extends B<T> {
T item;
public C(T item) {
this,item = item;
item.set();
}
}
上面的程序中B直接持有一個(gè)對象,因此這種行為被繼承到了C中桂塞,它也要求其參數(shù)與A一致凹蜂。