Think in Java第四版 讀書筆記9第15章 泛型
泛型:適用於很多很多的類型
與其他語言相比 Java的泛型可能有許多侷限 但是它還是有很多優點的。
本章介紹java泛型的侷限和優勢以及java泛型如何發展成現在這個樣子的。
15.1 Java的泛型與C++比較
Java的語言設計靈感來自C++,雖然我們學習Java時不需要參考C++,但是有時與C++進行比較可以加深理解
泛型就是一個例子
之所以將Java的泛型與C++進行比較原因有二
1.比較後會理解泛型的基礎,同時你會了解Java泛型的侷限性以及爲什麼會有這些侷限性。(理解某個技術不能做什麼 才能更好地做到所能做到的,不必浪費時間在死衚衕亂轉–Think in JAVA作者講的很精闢!)
2.人們對C++模板有一種誤解,這種誤解可能導致我們在理解泛型的意圖產生偏差。
15.2 簡單泛型-指定容器存儲類型
泛型最常見的使用是在容器類上,讓我們從最簡單的開始吧
例子:存放單個指定類型對象的容器
class Automobile {}
public class Holder1 {
private Automobile a;
public Holder1(Automobile a) { this.a = a; }
Automobile get() { return a; }
} ///:~
例子很簡單,這個就是在類內部有個私有變量 存儲指定類型的對象,這個也被稱爲組合關係,用的還是很廣泛的,不過對於一個容器而言,他是不合格的,因爲它的可重用性很低。每出現一個新的類型就要新增一個類
例子:存放單個任意類型對象的容器
public class Holder2 {
private Object a;
public Holder2(Object a) {
this.a = a;
}
public void set(Object a) {
this.a = a;
}
public Object get() {
return a;
}
public static void main(String[] args) {
Holder2 h2 = new Holder2(new Automobile());
Automobile a = (Automobile) h2.get();
h2.set("Not an Automobile");
String s = (String) h2.get();
h2.set(1); // 自動裝箱是1.5之後纔有的 使用低版本jdk會編譯報錯
Integer x = (Integer) h2.get();
}
} // /:~
可以看到 Holder2對象的實例先後存儲了Automobile String Integer對象
通常 容器只會存儲一種類型的對象,並且我們想做的是暫時不指定其存儲對象的類型,等到使用時再指定。泛型能做到這一點,並且 泛型可以保證編譯期對象類型的正確性。
例子 使用泛型,在使用時才指定容器存儲類型
public class Holder3 {
private T a;
public Holder3(T a) {
this.a = a;
}
public void set(T a) {
this.a = a;
}
public T get() {
return a;
}
public static void main(String[] args) {
Holder3<Automobile> h3 = new Holder3<Automobile>(new Automobile());
Automobile a = h3.get(); // No cast needed
// h3.set("Not an Automobile"); // Error
// h3.set(1); // Error
Holder3<String> holder3 = new Holder3<String>("abc");
String string = holder3.get();
}
} ///:~
像這樣 在創建Holder3對象時必須指定存儲的類型,跟存儲Object相比,泛型在編譯期就可以確定存放和取出的對象類型,
泛型的一個核心作用是告訴編譯器使用的類型
15.2.1 一個一元組類庫
由於return只能返回一個對象,那麼要實現返回多個對象的需求 如何實現呢?這裏就可以使用元組的概念了。
我們可以創建一個對象A 該對象A持有需要返回的多個其他對象,返回那一個對象A,就可以實現返回多個對象的效果了,這也是元組的概念。另外,如果元組只允許讀取不允許重新賦值或新增對象(只讀)就可以叫做數據傳送對象/信使
元組可以是任意長度 任意類型的,我們舉一個2維元組的例子
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 + ")";
}
} // /:~
注意這裏沒有set get方法,原因是first second都是public的 可以直接訪問,但是無法修改,因爲他們都是final的,這就實現了只讀的效果,並且比較簡明
書中提到如果程序可以修改first second的內容,上面這種方式更安全,因爲如果需要存儲另外元素的元組 就需要創建另外的元組。 但是我看使用get set也能實現,不明白。。。 如果說按照後面講述的內容便於擴展倒是可以理解。。。
例子:利用繼承實現長度更長的元組
public class ThreeTuple<A,B,C> extends TwoTuple<A,B> {
public final C third;
public ThreeTuple(A a, B b, C c) {
super(a, b);
third = c;
}
public String toString() {
return "(" + first + ", " + second + ", " + third +")";
}
} ///:~
例子:使用元組(返回元組)
class Amphibian {
}
public class TupleTest {
static TwoTuple<String, Integer> f() {
// Autoboxing converts the int to Integer:
return new TwoTuple<String, Integer>("hi", 47);
}
static ThreeTuple<Amphibian, String, Integer> g() {
return new ThreeTuple<Amphibian, String, Integer>(new Amphibian(),
"hi", 47);
}
public static void main(String[] args) {
TwoTuple<String, Integer> ttsi = f();
System.out.println(ttsi);
// ttsi.first = "there"; // Compile error: final
System.out.println(g());
}
} /*
* Output: (80% match) (hi, 47) (Amphibian@1f6a7b9, hi, 47)
*/// :~
在上述例子中 返回時的new語句似乎有點煩,後面會將他優化
15.2.2一個堆棧類(使用泛型實現自定義Stack)
//不使用LinkedList 而使用自定義的內部鏈式存儲機制來實現stack
public class LinkedStack<T> {
private static class Node<U> {// 模擬鏈表節點
U item;// 當前節點內容
Node<U> next;// 鏈表的下一個節點
Node() {// 默認構造函數 當前內容與下一節點都爲null 用於初始化末端哨兵
item = null;
next = null;
}
Node(U item, Node<U> next) {// 構造函數 參數*2
this.item = item;
this.next = next;
}
boolean end() {// 鏈表的當前和下一個節點均爲空 則鏈表爲空
return item == null && next == null;
}
}
private Node<T> top = new Node<T>(); // End sentinel末端哨兵 初始化爲空節點
// 該節點一直在棧頂
public void push(T item) {// 入棧操作
top = new Node<T>(item, top);// 將棧頂指針從上次的棧頂指向現在的item所在Node
}
public T pop() {// 彈棧操作
T result = top.item;// 獲取棧頂元素
if (!top.end())// 鏈表不爲空
top = top.next;// 指針下移
return result;
}
public static void main(String[] args) {
LinkedStack<String> lss = new LinkedStack<String>();
for (String s : "Phasers on stun!".split(" ")){
lss.push(s);
}
lss.push(null);
String s;
while (!lss.top.end()){//個人覺得這樣寫更合適
s = lss.pop();
System.out.println(s);
}
// while ((s = lss.pop()) != null)//書中的寫法
// System.out.println(s);
}
} /*
* Output:
null
stun!
on
Phasers
*/// :~
15.2.3 RandomList(使用泛型創建隨機list)
public class RandomList<T> {
// 存儲特定類型對象的容器 內部包含一個ArrayList
private ArrayList<T> storage = new ArrayList<T>();
private Random rand = new Random(47);
public void add(T item) {// 新增item
storage.add(item);
}
public T select() {// 隨機取出一個元素
return storage.get(rand.nextInt(storage.size()));
}
public static void main(String[] args) {
RandomList<String> rs = new RandomList<String>();
for (String s : ("The quick brown fox jumped over the lazy brown dog")
.split(" ")) {
rs.add(s);
}
for (int i = 0; i < 11; i++) {
System.out.print(rs.select() + " ");
}
}
} /*
* Output:
* brown over fox quick quick dog brown The brown lazy brown
*/// :~
15.3 泛型接口
生成器(generator)負責創建對象 有點類似工程設計模式中的工廠方法。不過,一般工廠方法需要傳遞參數而生成器不需要。(生成器不需要額外信息就知道如何生成對象)
一般生成器只包含一個next方法 例如:
public class Coffee {
private static long counter = 0;
private final long id = counter++;
public String toString() {
return getClass().getSimpleName() + " " + id;
}
} // /:~
Coffee及其子類:
public class Coffee {
private static long counter = 0;
private final long id = counter++;
public String toString() {
return getClass().getSimpleName() + " " + id;
}
} // /:~
package generics.coffee;
public class Americano extends Coffee {} ///:~
package generics.coffee;
public class Breve extends Coffee {} ///:~
package generics.coffee;
public class Cappuccino extends Coffee {} ///:~
package generics.coffee;
public class Latte extends Coffee {} ///:~
package generics.coffee;
public class Mocha extends Coffee {} ///:~
實現泛型接口的類
public class CoffeeGenerator implements Generator<Coffee>, Iterable<Coffee> {
private Class[] types = { Latte.class, Mocha.class, Cappuccino.class,
Americano.class, Breve.class, };
private static Random rand = new Random(47);
public CoffeeGenerator() {//構造方法1
}
// For iteration:
private int size = 0;
public CoffeeGenerator(int sz) {//構造方法2
size = sz;
}
public Coffee next() {
try {
//隨機返回一種Coffee
return (Coffee) types[rand.nextInt(types.length)].newInstance();
// Report programmer errors at run time:
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//自定義迭代器
class CoffeeIterator implements Iterator<Coffee> {
int count = size;
public boolean hasNext() {
return count > 0;
}
public Coffee next() {
count--;
return CoffeeGenerator.this.next();
}
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
//實現Iterable的方法
public Iterator<Coffee> iterator() {
return new CoffeeIterator();
}
public static void main(String[] args) {
CoffeeGenerator gen = new CoffeeGenerator();
for (int i = 0; i < 5; i++)
System.out.println(gen.next());
for (Coffee c : new CoffeeGenerator(5))//實現了Iterable所以可以使用for循環
System.out.println(c);
}
} /*
* Output:
Americano 0
Latte 1
Americano 2
Mocha 3
Mocha 4
Breve 5
Americano 6
Latte 7
Cappuccino 8
Cappuccino 9
*/// :~
Generator接口的另一種實現的例子 該例子負責生成斐波那契數列
// Generate a Fibonacci sequence.
import net.mindview.util.*;
public class Fibonacci implements Generator<Integer> {
private int count = 0;
public Integer next() {
return fib(count++);
}
private int fib(int n) {//當n比較大時 遞歸效率很低
if (n < 2){//第0 和第1個數 返回1
return 1;
}
return fib(n - 2) + fib(n - 1);//遞歸調用
}
public static void main(String[] args) {
Fibonacci gen = new Fibonacci();
for (int i = 0; i < 18; i++){//生成18個斐波那契數列
System.out.println(gen.next() + " ");
}
}
} /*
* Output: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
*/// :~
上面這個例子我們看到我們實現的是Generator 但實際使用的卻是int基本類型。也就是說Java泛型有一個侷限性:基本類型無法使用泛型
但是Java SE 5 已經實現了自動裝箱和自動拆箱的功能,所以基本類型會與對應的對象類型自動轉換。
如果想要實現可以在for循環使用的Fibonacci 我們有兩種做法 一個是用Fibonacci直接實現Iterable,或者繼承Fibonacci並實現Iterable。
第一種實現是我們可以修改Fibonacci類的情況 第二章實現是我們不可以或者不想修改Fibonacci類的情況
第二種實現又叫適配器模式(實現某個接口以達到滿足某些方法的類型要求,詳見:https://blog.csdn.net/u011109881/article/details/82288922)
第二種實現的例子:
// Adapt the Fibonacci class to make it Iterable.
import java.util.*;
public class IterableFibonacci extends Fibonacci implements Iterable<Integer> {
private int n;
public IterableFibonacci(int count) {//參數用於判斷是否遍歷結束
n = count;
}
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
public boolean hasNext() {
return n > 0;
}
public Integer next() {
n--;
return IterableFibonacci.this.next();
}
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for (int i : new IterableFibonacci(18))
System.out.print(i + " ");
}
} /*
* Output: 1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
*/// :~
15.4 泛型方法
到目前位置 我們使用的泛型都是作用在類上的 泛型同樣可以作用於方法,即泛型方法。一個類是否有泛型方法與是否是泛型類沒有關係
如果使用泛型方法就可以達到目的 那麼使用泛型方法而不是使用泛型類(使整個類泛型化)。這樣的結構更清晰。
另外需要注意靜態方法需要使用泛型能力只能使其成爲泛型方法(泛型類的泛型無法使用在靜態方法上)
原因:
public class Atest<T> {
//static void testA(T t){}//編譯報錯:Cannot make a static reference to the non-static type T
static <K> void testA(K k){}//泛型方法
}
泛型方法:在返回值前加上泛型參數列表
例子:泛型方法的定義
public class GenericMethods {
public <T> void f(T x) {
System.out.println(x.getClass().getName());
}
public static void main(String[] args) {
GenericMethods gm = new GenericMethods();
gm.f("");
gm.f(1);
gm.f(1.0);
gm.f(1.0F);
gm.f('c');
gm.f(gm);
}
} /* Output:
java.lang.String
java.lang.Integer
java.lang.Double
java.lang.Float
java.lang.Character
GenericMethods
*///:~
GenericMethods類本身不是泛型類 但是其中的方法f()卻是泛型方法
當使用泛型類時 我們必須指定其參數化類型 但是使用泛型方法時 通常不需要指定類型,編譯器自動會找出指定的類型。這就是類型參數推斷(Type argument inference)
這樣在調用f方法時 我們可以傳入不同的類型參數,看起來就像該方法被重載了無數次,可以接受任意類型參數
傳入基本類型時 自動裝箱機制會起到作用,將基本類型轉換成指定的包裝類類型
15.4.1 類型推斷的限制
雖然編譯器可以做一些類型推斷 但是僅限於有限的情況,如果比較複雜,就不行了 比如
Map<Coffee, List<? extends Coffee>> coffeeMap = new HashMap<Coffee, List<? extends Coffee>>();
我們就需要重複寫2次冗長的參數類型
有沒有方法避免這個呢?
我們可以寫一個工具類來生成一些容器
import java.util.*;
public class New {
public static <K, V> Map<K, V> map() {
return new HashMap<K, V>();
}
public static <T> List<T> list() {
return new ArrayList<T>();
}
public static <T> LinkedList<T> lList() {
return new LinkedList<T>();
}
public static <T> Set<T> set() {
return new HashSet<T>();
}
public static <T> Queue<T> queue() {
return new LinkedList<T>();
}
// Examples:
public static void main(String[] args) {
Map<String, List<String>> sls = New.map();
List<String> ls = New.list();
LinkedList<String> lls = New.lList();
Set<String> ss = New.set();
Queue<String> qs = New.queue();
}
} // /:~
有了上述的工具類 我們的聲明就簡單了
Map<Coffee, List<? extends Coffee>> coffeeMap1 = New.map();
即可
看起來我們的工具類似乎起到一定的簡化作用 但是真的這樣嗎,我們的初始目的是簡化類型的聲明,但是其他人在閱讀代碼時 還需要閱讀New類的作用,這似乎與不使用new類時的效率不相上下。
從上面我們也可以看到 類型推斷只在賦值時起作用,其他時候並不起作用。
如果你將泛型方法的返回值作爲參數傳遞給另一個方法 類型推斷將會失效。
比如下面的例子
public class LimitsOfInference {
static void f(Map<Coffee, List<? extends Coffee>> coffees) {
}
public static void main(String[] args) {
//f(New.map()); // Does not compile
}
} // /:~
將New.map()的返回值傳遞給f方法 會編譯報錯 此時,編譯器認爲New.map()返回值被賦值給一個Object類型的變量
所以將f方法修改如下 會編譯通過
static void f(Object coffees) {
}
顯示的類型說明(很少使用)
即顯示地指明類型
具體做法:在點操作符後面插入類型聲明 比如
new1.<Coffee, List> map() (new1是New的實例 此時map方法不是靜態方法)
特別的
1)使用在定義該方法的類時要使用this關鍵字 即類似
this.<Coffee, List<Coffee>> map()
2)使用static的方法 必須在點操作符前加上類名即類似
New.<Coffee, List<Coffee>> map()
(此時map方法是靜態方法)
map方法是靜態方法的顯示的類型說明 案例
public class ExplicitTypeSpecification {
static void f(Map<Coffee, List<Coffee>> coffee) {
}
public static void main(String[] args) {
f(New.<Coffee, List<Coffee>> map());
}
} // /:~
要明確 顯示的類型說明僅使用在非賦值語句
15.4.2 可變參數與泛型方法
泛型方法與可變參數列表可以很好的共存
public class GenericVarargs {
public static <T> List<T> makeList(T... args) {//可變參數結合泛型的方法
List<T> result = new ArrayList<T>();
for(T item : args)
result.add(item);
return result;
}
public static void main(String[] args) {
List<String> ls = makeList("A");
System.out.println(ls);
ls = makeList("A", "B", "C");
System.out.println(ls);
ls = makeList("ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
System.out.println(ls);
}
} /* Output:
[A]
[A, B, C]
[, A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R, S, T, U, V, W, X, Y, Z]
*///:~
makeList方法的作用與java.util.Arrays.asList()方法一致 (可以把makeList替換成Arrays.asList)
15.4.3 用於Generator的泛型方法
泛型結合Collection的案例
import generics.coffee.*;
import java.util.*;
import net.mindview.util.*;
public class Generators {
//注意這裏使用了接口Generator 它的實現有CoffeeGenerator Fibonacci
public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen,
int n) {
for (int i = 0; i < n; i++)
coll.add(gen.next());
return coll;
}
public static void main(String[] args) {
Collection<Coffee> coffee = fill(new ArrayList<Coffee>(),
new CoffeeGenerator(), 4);
for (Coffee c : coffee)
System.out.println(c);
Collection<Integer> fnumbers = fill(new ArrayList<Integer>(),
new Fibonacci(), 12);
for (int i : fnumbers)
System.out.print(i + ", ");
}
} /*
* Output:
Americano 0
Latte 1
Americano 2
Mocha 3
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144,
*/// :~
15.4.4 一個通用的Genertor
public interface Generator<T> {//這就是泛型接口
T next();
} // /:~
//使用該生成器需要2個條件
//1.使用者是public類
//2.使用者擁有無參構造函數(默認構造方法)
public class BasicGenerator<T> implements Generator<T> {
private Class<T> type;
public BasicGenerator(Class<T> type){ this.type = type; }
public T next() {
try {
// 假設 type 是一個public類(否則報錯):
return type.newInstance();
} catch(Exception e) {
throw new RuntimeException(e);
}
}
// Produce a Default generator given a type token:
public static <T> Generator<T> create(Class<T> type) {
return new BasicGenerator<T>(type);
}
} ///:~
使用這個通用的Genertor的案例
public class CountedObject {
private static long counter = 0;
private final long id = counter++;
public long id() {
return id;
}
public String toString() {
return "CountedObject " + id;
}
} ///:~
public class BasicGeneratorDemo {
public static void main(String[] args) {
Generator<CountedObject> gen = BasicGenerator
.create(CountedObject.class);
for (int i = 0; i < 5; i++)
System.out.println(gen.next());
}
} /*
* Output: CountedObject 0 CountedObject 1 CountedObject 2 CountedObject 3
* CountedObject 4
*/// :~
15.4.5 簡化元組的使用(元組優化)
使用類型推斷+static方法 優化元組工具,使其更通用的工具類庫
結合15.2.1的各種元組類 用Tuple統一結合起來專門生成對象
public class Tuple {
public static <A, B> TwoTuple<A, B> tuple(A a, B b) {
return new TwoTuple<A, B>(a, b);
}
public static <A, B, C> ThreeTuple<A, B, C> tuple(A a, B b, C c) {
return new ThreeTuple<A, B, C>(a, b, c);
}
} // /:~
使用:
static TwoTuple f2() { return tuple("hi", 47); }
static ThreeTuple<Amphibian,String,Integer> g() {
return tuple(new Amphibian(), "hi", 47);
}
15.4.6 一個Set實用工具
//表示數學裏面的關係
public class Sets {
//a並b
public static <T> Set<T> union(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<T>(a);
result.addAll(b);
return result;
}
//a交b
public static <T> Set<T> intersection(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<T>(a);
result.retainAll(b);
return result;
}
//去掉superset中 superset與subset的交集
public static <T> Set<T> difference(Set<T> superset, Set<T> subset) {
Set<T> result = new HashSet<T>(superset);
result.removeAll(subset);
return result;
}
// A並B 去掉 A交B
public static <T> Set<T> complement(Set<T> a, Set<T> b) {
return difference(union(a, b), intersection(a, b));
}
} // /:~
public enum Watercolors {
A, B, C, D, E, F, G,
H, I, J, K, L, M, N,
O, P, Q, R, S, T,
U, V, W, X, Y, Z
} // /:~
public class WatercolorSets {
public static void main(String[] args) {
Set<Watercolors> set1 = EnumSet.range(A, N);
Set<Watercolors> set2 = EnumSet.range(H, T);
print("set1: " + set1);
print("set2: " + set2);
print("union(set1, set2): " + union(set1, set2));
Set<Watercolors> subset = intersection(set1, set2);
print("intersection(set1, set2): " + subset);
print("difference(set1, subset): " + difference(set1, subset));
print("difference(set2, subset): " + difference(set2, subset));
print("complement(set1, set2): " + complement(set1, set2));
}
} /*
* Output:
set1: [A, B, C, D, E, F, G, H, I, J, K, L, M, N]
set2: [H, I, J, K, L, M, N, O, P, Q, R, S, T]
union(set1, set2): [D, E, C, K, Q, M, S, G, P, N, B, I, O, T, A, J, L, H, F, R]
intersection(set1, set2): [K, M, N, I, J, L, H]
difference(set1, subset): [D, E, C, G, B, A, F]
difference(set2, subset): [Q, S, P, O, T, R]
complement(set1, set2): [D, E, C, Q, S, G, P, B, O, T, A, F, R]
*/// :~
例子:對比各種集合類的異同
public class ContainerMethodDifferences {
static Set<String> methodSet(Class<?> type) {//將集合類的方法存儲到TreeSet 存儲在set爲了去重
Set<String> result = new TreeSet<String>();
for (Method m : type.getMethods())
result.add(m.getName());
return result;
}
static void interfaces(Class<?> type) {//將接口方法存儲在ArrayList
System.out.print("Interfaces in " + type.getSimpleName() + ": ");
List<String> result = new ArrayList<String>();
for (Class<?> c : type.getInterfaces())
result.add(c.getSimpleName());
System.out.println(result);
}
static Set<String> object = methodSet(Object.class);//存儲Object所有方法
static {
object.add("clone");
}
static void difference(Class<?> superset, Class<?> subset) {
System.out.print(superset.getSimpleName() + " extends "
+ subset.getSimpleName() + ", adds: ");
//調用之前定義的difference方法
Set<String> comp = Sets.difference(methodSet(superset),
methodSet(subset));
comp.removeAll(object); //去掉Object的所有方法
System.out.println(comp);
interfaces(superset);//打印接口方法
}
public static void main(String[] args) {
System.out.println("Collection: " + methodSet(Collection.class));
interfaces(Collection.class);
difference(Set.class, Collection.class);
difference(HashSet.class, Set.class);
difference(LinkedHashSet.class, HashSet.class);
difference(TreeSet.class, Set.class);
difference(List.class, Collection.class);
difference(ArrayList.class, List.class);
difference(LinkedList.class, List.class);
difference(Queue.class, Collection.class);
difference(PriorityQueue.class, Queue.class);
System.out.println("Map: " + methodSet(Map.class));
difference(HashMap.class, Map.class);
difference(LinkedHashMap.class, HashMap.class);
difference(SortedMap.class, Map.class);
difference(TreeMap.class, Map.class);
}
} // /:~
/**
Collection: [add, addAll, clear, contains, containsAll, equals, forEach, hashCode, isEmpty, iterator, parallelStream, remove, removeAll, removeIf, retainAll, size, spliterator, stream, toArray]
Interfaces in Collection: [Iterable]
Set extends Collection, adds: []
Interfaces in Set: [Collection]
HashSet extends Set, adds: []
Interfaces in HashSet: [Set, Cloneable, Serializable]
LinkedHashSet extends HashSet, adds: []
Interfaces in LinkedHashSet: [Set, Cloneable, Serializable]
TreeSet extends Set, adds: [headSet, descendingIterator, descendingSet, pollLast, subSet, floor, tailSet, ceiling, last, lower, comparator, pollFirst, first, higher]
Interfaces in TreeSet: [NavigableSet, Cloneable, Serializable]
List extends Collection, adds: [replaceAll, get, indexOf, subList, set, sort, lastIndexOf, listIterator]
Interfaces in List: [Collection]
ArrayList extends List, adds: [trimToSize, ensureCapacity]
Interfaces in ArrayList: [List, RandomAccess, Cloneable, Serializable]
LinkedList extends List, adds: [offerFirst, poll, getLast, offer, getFirst, removeFirst, element, removeLastOccurrence, peekFirst, peekLast, push, pollFirst, removeFirstOccurrence, descendingIterator, pollLast, removeLast, pop, addLast, peek, offerLast, addFirst]
Interfaces in LinkedList: [List, Deque, Cloneable, Serializable]
Queue extends Collection, adds: [poll, peek, offer, element]
Interfaces in Queue: [Collection]
PriorityQueue extends Queue, adds: [comparator]
Interfaces in PriorityQueue: [Serializable]
Map: [clear, compute, computeIfAbsent, computeIfPresent, containsKey, containsValue, entrySet, equals, forEach, get, getOrDefault, hashCode, isEmpty, keySet, merge, put, putAll, putIfAbsent, remove, replace, replaceAll, size, values]
HashMap extends Map, adds: []
Interfaces in HashMap: [Map, Cloneable, Serializable]
LinkedHashMap extends HashMap, adds: []
Interfaces in LinkedHashMap: [Map]
SortedMap extends Map, adds: [lastKey, subMap, comparator, firstKey, headMap, tailMap]
Interfaces in SortedMap: [Map]
TreeMap extends Map, adds: [descendingKeySet, navigableKeySet, higherEntry, higherKey, floorKey, subMap, ceilingKey, pollLastEntry, firstKey, lowerKey, headMap, tailMap, lowerEntry, ceilingEntry, descendingMap, pollFirstEntry, lastKey, firstEntry, floorEntry, comparator, lastEntry]
Interfaces in TreeMap: [NavigableMap, Cloneable, Serializable]
**/
15.5 泛型運用在匿名內部類
public interface Generator<T> { T next(); } ///:~
public class Generators {
public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen,
int n) {
for (int i = 0; i < n; i++)
coll.add(gen.next());
return coll;
}
}
class Customer {
private static long counter = 1;
private final long id = counter++;
//私有化構造方法 只能通過Generator獲取實例
private Customer() {
}
public String toString() {
return "Customer " + id;
}
//匿名內部類1
//Customer對象生成器
//generator方法每次調用會創建一個Generator對象 但這是不必要的
public static Generator<Customer> generator() {
return new Generator<Customer>() {
public Customer next() {
return new Customer();
}
};
}
}
class Teller {
private static long counter = 1;
private final long id = counter++;
//私有化構造方法 只能通過Generator獲取實例
private Teller() {
}
public String toString() {
return "Teller " + id;
}
//匿名內部類2
// 單例Generator對象:
// 可以對比Customer的generator方法 這裏只會創建一個generator實例
public static Generator<Teller> generator = new Generator<Teller>() {
public Teller next() {
return new Teller();
}
};
}
public class BankTeller {
public static void serve(Teller t, Customer c) {
System.out.println(t + " serves " + c);
}
public static void main(String[] args) {
Random rand = new Random(47);
Queue<Customer> line = new LinkedList<Customer>();
//生成15個Customer對象 放入line中
Generators.fill(line, Customer.generator(), 15);
List<Teller> tellers = new ArrayList<Teller>();
//生成4個Teller對象 放入tellers中
Generators.fill(tellers, Teller.generator, 4);
for (Customer c : line){
//遍歷line中的Customer15個對象 從tellers取出隨機的Teller與Customer進行匹配輸出
serve(tellers.get(rand.nextInt(tellers.size())), c);
}
}
} /*
* Output:
Teller 3 serves Customer 1
Teller 2 serves Customer 2
Teller 3 serves Customer 3
Teller 1 serves Customer 4
Teller 1 serves Customer 5
Teller 3 serves Customer 6
Teller 1 serves Customer 7
Teller 2 serves Customer 8
Teller 3 serves Customer 9
Teller 3 serves Customer 10
Teller 2 serves Customer 11
Teller 4 serves Customer 12
Teller 2 serves Customer 13
Teller 1 serves Customer 14
Teller 1 serves Customer 15
*/// :~
15.6 構建複雜模型(組合與數組)
利用泛型可以輕鬆地將A B C D等不同數據結構組合起來構成一個新的數據結構,比如
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 + ")";
}
} ///:~
另外一個例子是將泛型和數組結合
public class Generators {
public static <T> Collection<T> fill(Collection<T> coll, Generator<T> gen,
int n) {
for (int i = 0; i < n; i++)
coll.add(gen.next());
return coll;
}
}
class Product {
private final int id;
private String description;
private double price;
public Product(int IDnumber, String descr, double price) {
id = IDnumber;
description = descr;
this.price = price;
System.out.println(toString());
}
public String toString() {
return id + ": " + description + ", price: $" + price;
}
public void priceChange(double change) {
price += change;
}
public static Generator<Product> generator = new Generator<Product>() {
private Random rand = new Random(47);
//隨機產生一個id<1000 描述爲Test 價格爲0-1000之間的 Product
public Product next() {
return new Product(rand.nextInt(1000), "Test", Math.round(rand
.nextDouble() * 1000.0) + 0.99);
}
};
}
class Shelf extends ArrayList<Product> {//Shelf是一個Product數組
public Shelf(int nProducts) {//產生nProducts個Product的ArrayList
Generators.fill(this, Product.generator, nProducts);
}
}
class Aisle extends ArrayList<Shelf> {//Aisle是一個Shelf數組
public Aisle(int nShelves, int nProducts) {//創建長度爲nShelves的ArrayList<Shelf> 每一個Shelf元素中填充了nProducts個Product
for (int i = 0; i < nShelves; i++)
add(new Shelf(nProducts));
}
}
//class CheckoutStand {
//}
//
//class Office {
//}
public class Store extends ArrayList<Aisle> {//Store是一個Aisle數組
// private ArrayList<CheckoutStand> checkouts = new ArrayList<CheckoutStand>();
// private Office office = new Office();
public Store(int nAisles, int nShelves, int nProducts) {
for (int i = 0; i < nAisles; i++)
add(new Aisle(nShelves, nProducts));
}
public String toString() {
StringBuilder result = new StringBuilder();
for (Aisle a : this)
for (Shelf s : a)
for (Product p : s) {
result.append(p);
result.append("\n");
}
return result.toString();
}
public static void main(String[] args) {
System.out.println(new Store(14, 5, 10));
}
} /*
258: Test, price: $400.99
861: Test, price: $160.99
868: Test, price: $417.99
207: Test, price: $268.99
551: Test, price: $114.99
278: Test, price: $804.99
520: Test, price: $554.99
...
*/// :~
這個例子像是List的嵌套
Store本身是一個list 包含A個Aisle
Aisle本身是一個list 包含B個Shelf
Shelf本身是一個list 包含C個Product
因此一個Store可以包含ABC個Product
15.7 擦除的神祕之處
//ArrayList<String>與ArrayList<Integer>是否是不同的類型呢?
//我們可以將String放入ArrayList<String> 卻不能放入ArrayList<Integer>
//所以他們是不同的類型? 但是輸出結果似乎出乎意料
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
System.out.println(Arrays.toString(c1.getTypeParameters()));
System.out.println(Arrays.toString(c2.getTypeParameters()));
}
} /*
* Output:
true
[E]
[E]
*/// :~
另一個補充案例
class Frob {}
class Fnorkle {}
class Quark<Q> {}
class Particle<POSITION,MOMENTUM> {}
public class LostInformation {
public static void main(String[] args) {
List<Frob> list = new ArrayList<Frob>();
Map<Frob,Fnorkle> map = new HashMap<Frob,Fnorkle>();
Quark<Fnorkle> quark = new Quark<Fnorkle>();
Particle<Long,Double> p = new Particle<Long,Double>();
System.out.println(Arrays.toString(
list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
quark.getClass().getTypeParameters()));
System.out.println(Arrays.toString(
p.getClass().getTypeParameters()));
}
} /* Output:
[E]
[K, V]
[Q]
[POSITION, MOMENTUM]
*///:~
/**
* 思考:
* 根據JDK文檔描述 我們可以通過調用Class.getTypeParameters方法來獲得一個類型變量數據,該數組表示有泛型聲明所生命的類型參數。
* 但是假如如文檔所說 我們看到的結果應該是
* [Frob]
* [Frob, Fnorkle]
* [Fnorkle]
* [Long, Double]
* 而事實卻非如此
* 因此結論是:在泛型代碼內部 無法獲取任何有關泛型參數類型的信息
*
*/
從上述例子,我們看出
我們無法知道創建某個實例的實際類型參數
Java的泛型使用擦除來實現,這意味着你在使用泛型時。任何類型信息都被擦除了,你只知道在使用一個對象。所以List List在運行時實際是相同的類型。
這兩種形式都被擦除成原生類型 即List。
本節將討論java的泛型的擦除 這也是Java泛型學習的一個最大障礙
15.7.1 C++的方式
C++泛型例子
#include <iostream>
using namespace std;
template<class T> class Manipulator {
T obj;//存儲了類型T
public:
Manipulator(T x) {
obj = x;
}
void manipulate() {
obj.f();//此處調用了f方法
}
};
class HasF {
public:
void f() {
cout << "HasF::f()" << endl;
}
};
int main() {
HasF hf;
Manipulator<HasF> manipulator(hf);//此處實例化Manipulator C++內部會查詢HasF是否有方法f 如果沒有則編譯報錯
//C++的泛型模板代碼知道模板參數的類型
manipulator.manipulate();
} /* Output:
HasF::f()
///:~
以上例子使用Java來寫:
public class HasF {
public void f() {
System.out.println("HasF.f()");
}
} // /:~
// 編譯報錯
class Manipulator<T> {
private T obj;
public Manipulator(T x) {
obj = x;
}
// Error: 編譯報錯 The method f() is undefined for the type T
public void manipulate() {
obj.f();//由於類型擦除 Java無法將obj能調用f方法的需求映射到實際類型HasF上
//爲了能調用f方法 我們需要協助泛型類 給定泛型邊界,告訴編譯器遵循邊界類型。
}
}
public class Manipulation {
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator<HasF> manipulator = new Manipulator<HasF>(hf);
manipulator.manipulate();
}
} // /:~
我們對上述例子稍加修改 就可以編譯成功了
//這裏有了邊界
class Manipulator2<T extends HasF> {//通過T extends HasF讓Java編譯器知道T也有HasF的方法f
private T obj;
public Manipulator2(T x) {
obj = x;
}
public void manipulate() {
obj.f();
}
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator2<HasF> manipulator = new Manipulator2<HasF>(hf);
System.out.println(Arrays.toString(manipulator.getClass().getTypeParameters()));
manipulator.manipulate();
}
}
/**
*輸出:
[T]
HasF.f()
**/
但是 上述例子中 泛型沒有多大作用,我們即使不使用泛型 仍可以寫出代碼
class Manipulator3 {
private HasF obj;
public Manipulator3(HasF x) {
obj = x;
}
public void manipulate() {
obj.f();
}
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator3 manipulator = new Manipulator3(hf);
manipulator.manipulate();
}
} // /:~
因此 泛型需要判斷是不是真的需要,泛型只有當你希望使用的類型參數比某個具體類型更加“泛化” 才需要使用。
比如下面這個例子泛型確實起到作用:
class ReturnGenericType<T extends HasF> {
private T obj;
public ReturnGenericType(T x) {
obj = x;
}
public T get() {//將返回確切的類型 如果不使用泛型 只能返回HasF類型
return obj;
}
} // /:~
15.7.2 遷移兼容性(Java泛型使用擦除實現的由來)
Java的泛型不是一開始就有的產物,而是在SE 5.0引入的。之所以使用擦除,是爲了兼容性。即使用了泛型的客戶端仍然可以使用非泛型的類庫,
並且使用了泛型的類庫也可以使用在非泛型的客戶端上。爲了實現這一需求,Java採用擦除這一特性來實現泛型,即泛型只在特殊時期起作用,
過了這一時期,泛型就好像不存在一樣,這樣 不管程序是泛型的還是非泛型的,通通都可以看成沒有使用泛型,也就不存在兼容性問題了。
爲什麼要兼容性:假設一個類庫開發了很長時間,但是該類庫不支持泛型,那麼對於需要使用泛型的客戶端,如果沒有兼容性,該類庫就廢了。
泛型類型只在靜態檢查期間纔出現,靜態檢查之後 所有泛型類型會被擦除,例如List會被擦除爲List,普通的泛型類型會被擦除爲Object。
15.7.3 擦除的問題(相比於其他語言 Java的泛型沒有那麼靈活)
Java實現泛型 需要從非泛化的代碼向泛化代碼轉變 同時不能破壞現有類庫。
擦除的代價是顯著的,泛型只存在於靜態檢查,,而不能使用在運行時,比如強制類型轉換 instanceof和new等操作符。在編寫代碼時,應該提醒自己
泛型只是看起來好像擁有參數類型信息,這只是暫時性的。
class GenericBase<T> {
private T element;
public void set(T arg) {
arg = element;
}
public T get() {
return element;
}
}
class Derived1<T> extends GenericBase<T> {//可以使用泛型
}
class Derived2 extends GenericBase {//也可以不使用泛型
} // 沒有報錯
//class Derived3 extends GenericBase<?> {}
// Strange error:
// unexpected type found : ?
// required: class or interface without bounds
// 沒明白。。。
public class ErasureAndInheritance {
@SuppressWarnings("unchecked")//SE 5之後出現的註解 壓制警告,不進行類型檢查
public static void main(String[] args) {
Derived2 d2 = new Derived2();
Object obj = d2.get();
d2.set(obj); // 在這裏出現警告,沒有使用泛型來規定參數類型!
}
} // /:~
15.7.4 邊界處的動作
由於擦除 泛型有一個令人困惑的地方:可以表示沒有任何意義的事物
例如:
public class ArrayMaker<T> {//泛型類
private Class<T> kind;
public ArrayMaker(Class<T> kind) {
this.kind = kind;
}
@SuppressWarnings("unchecked")
T[] create(int size) {
return (T[]) Array.newInstance(kind, size);
//由於擦除 Array.newInstance實際返回的是Object 所以必須強制轉換
}
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]
*/// :~
泛型使用在單個類型上
public class ListMaker<T> {
List<T> create() {
//雖然在調用new的時候 在運行時擦除了String的類型信息
//new ArrayList<T>()看起來寫成new ArrayList()也無所謂 但這樣編譯器會警告,沒有進行類型檢查
return new ArrayList<T>();
}
public static void main(String[] args) {
//沒有警告
ListMaker<String> stringMaker = new ListMaker<String>();
List<String> stringList = stringMaker.create();
}
} ///:~
泛型使用在List
public class FilledListMaker<T> {
List<T> create(T t, int n) {
List<T> result = new ArrayList<T>();//擦除了類型
for (int i = 0; i < n; i++){
result.add(t);//但是還可以確保對象是T類型 這一點由編譯器保證
}
return result;
}
public static void main(String[] args) {
FilledListMaker<String> stringMaker = new FilledListMaker<String>();
List<String> list = stringMaker.create("Hello", 4);
System.out.println(list);
}
} /*
* Output: [Hello, Hello, Hello, Hello]
*/// :~
我們再對比一下使用泛型和沒有泛型的編譯結果:
public class SimpleHolder {
private Object obj;
public void set(Object obj) {
this.obj = obj;
}
public Object get() {
return obj;
}
public static void main(String[] args) {
SimpleHolder holder = new SimpleHolder();
holder.set("Item");
String s = (String) holder.get();
}
}
/**
使用javac SimpleHolder.java編譯出class文件之後
再使用javap -c SimpleHolder反編譯
Compiled from "SimpleHolder.java"
public class SimpleHolder {
public SimpleHolder();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void set(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field obj:Ljava/lang/Object;
5: return
public java.lang.Object get();
Code:
0: aload_0
1: getfield #2 // Field obj:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #3 // class SimpleHolder
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5 // String Item
11: invokevirtual #6 // Method set:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7 // Method get:()Ljava/lang/Object;
18: checkcast #8 // class java/lang/String
21: astore_2
22: return
}
*/
public class GenericHolder<T> {
private T obj;
public void set(T obj) {
this.obj = obj;
}
public T get() {
return obj;
}
public static void main(String[] args) {
GenericHolder<String> holder = new GenericHolder<String>();
holder.set("Item");
String s = holder.get();
}
}
/**
C:\Users\hjcai\Desktop>javac GenericHolder.java
C:\Users\hjcai\Desktop>javap -c GenericHolder
Warning: Binary file GenericHolder contains generics.GenericHolder
Compiled from "GenericHolder.java"
public class generics.GenericHolder<T> {
public generics.GenericHolder();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public void set(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field obj:Ljava/lang/Object;
5: return
public T get();
Code:
0: aload_0
1: getfield #2 // Field obj:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #3 // class generics/GenericHolder
3: dup
4: invokespecial #4 // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5 // String Item
11: invokevirtual #6 // Method set:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7 // Method get:()Ljava/lang/Object;
18: checkcast #8 // class java/lang/String
21: astore_2
22: return
}
*/
可以看到他們的反編譯結果是一樣的,這也一定程度解釋了java的泛型是如何做到兼容性的。Java的泛型更多的由編譯器確保 而編譯結果看起來就像是沒有泛型一樣。(個人觀點,還是沒理解書裏說的邊界是什麼)
15.8 擦除的補償
由於泛型的擦除 所以在泛型代碼中某些操作能力會被丟失。
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]; // Unchecked warning
}
} ///:~
可以看到 instanceof 以及 new操作符 都不能直接作用在泛型上,那麼如何解決這一問題呢
既然運行時類型信息被擦除了,那麼我們可以在擦除前保存類型信息
class Building {
}
class House extends Building {
}
public class ClassTypeCapture<T> {
Class<T> kind;//用於保存類型信息
public ClassTypeCapture(Class<T> kind) {//創建對象時保存類型信息
this.kind = kind;
}
public boolean f(Object arg) {//判斷類型的方法
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<Building>(
Building.class);
System.out.println(ctt1.f(new Building()));//動態判斷類型
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 = new ClassTypeCapture<House>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
} /*
* Output: true true false true
*/// :~
15.8.1 創建泛型類型的實例
Java不能創建泛型對象 new T()的原因有二
1.由於泛型擦除了類型信息
2.不知道具體的T是否具有默認無參構造函數
在C++中是如何創建泛型類型的對象的呢?
// C++, not Java!
// C++可以直接創建泛型類型對象因爲它在編譯期就會被檢查
template<class T> class Foo {
T x; // 創建一個類型爲T的filed
T* y; // 指向T的指針
public:
// 初始化指針:
Foo() { y = new T(); }//創建了一個泛型
};
class Bar {};
int main() {
Foo<Bar> fb;
Foo<int> fi; //對基本類型同樣適用
} ///:~
如果我們想要像C++一樣創建泛型類型 需要做額外工作:可以使用工廠對象
import static net.mindview.util.Print.*;
//不完善的工廠
class ClassAsFactory<T> {
T x;//工廠保存了類型信息
public ClassAsFactory(Class<T> kind) {
try {
x = kind.newInstance();//使用newInstance 創建實例 不過使用該方法前提是存在默認構造函數
} catch (Exception e) {
throw new RuntimeException(e);
}
}
}
class Employee {
}
public class InstantiateGenericType {
public static void main(String[] args) {
ClassAsFactory<Employee> fe = new ClassAsFactory<Employee>(
Employee.class);
print("ClassAsFactory<Employee> succeeded");
try {
ClassAsFactory<Integer> fi = new ClassAsFactory<Integer>(
Integer.class);
} catch (Exception e) {
//創建Integer對象時失敗 因爲Integer沒有默認無參構造函數,在調用newInstance時失敗
print("ClassAsFactory<Integer> failed");
}
}
} /*
* Output: ClassAsFactory<Employee> succeeded ClassAsFactory<Integer> failed
*/// :~
對上述代碼進行優化 使用顯示的工廠
interface FactoryI<T> {
T create();
}
class Foo2<T> {
private T x;//保存類型信息
public <F extends FactoryI<T>> Foo2(F factory) {
x = factory.create();
}
// ...
}
class IntegerFactory implements FactoryI<Integer> {//專門創建Integer的工廠
public Integer create() {
return new Integer(0);//不使用newInstance創建對象 而使用new創建對象 以免異常
}
}
class Widget {
public static class Factory implements FactoryI<Widget> {//專門創建Widget的工廠
public Widget create() {
return new Widget();//不使用newInstance創建對象 而使用new創建對象 以免異常
}
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2<Integer>(new IntegerFactory());
new Foo2<Widget>(new Widget.Factory());
}
} // /:~
使用模板方法可以達到同樣的目的
abstract class GenericWithCreate<T> {
final T element;// 用於保存類型信息
GenericWithCreate() {
System.out.println("1");
element = create();//保存類型信息的實際地點
}
abstract T create();
}
class X {
}
class Creator extends GenericWithCreate<X> {
X create() {//創建X對象的方法
System.out.println("2");
return new X();
}
Creator(){
System.out.println("3");
}
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
//嘗試調用Creator默認函數,存在基類 先調用父類構造函數(point 1)
//父類構造函數調用了create方法(point 2)
//子類覆蓋了create方法 因此實際調用子類create方法
//調用子類構造函數(point 3)
Creator c = new Creator();
c.f();
}
} /*
* Output:
1
2
3
X
*/// :~
但是不管是哪一種方式,他們都會通過保存類型信息來創建泛型對象
15.8.2 泛型數組
本節的例子有點多 討論的內容有以下幾點
1.想要創建泛型數組可以使用ArrayList代替
2.非要使用數組的情況 在內部使用Object 在返回時轉型爲泛型類型
3.可以在創建泛型數組時傳遞一個類型標記 用於恢復被擦除的類型
4.Java的源碼中也有大量Object數組轉型爲泛型數組的代碼 這會產生大量警告。因此即使代碼是寫在源碼中的 也不代表這就是正確的寫法
如前Erased.java所述 不能創建泛型數組,可以使用ArrayList代替
這裏你可以獲得數組的行爲以及編譯期的類型安全
public class ListOfGenerics<T> {
private List<T> array = new ArrayList<T>();
public void add(T item) {
array.add(item);
}
public T get(int index) {
return array.get(index);
}
} // /:~
class Generic<T> {
}
public class ArrayOfGeneric {
static final int SIZE = 100;
// 編譯器接受這樣的聲明 但卻無法創建一個確切類型的數組
static Generic<Integer>[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
// 可以編譯 但是運行錯誤 [Ljava.lang.Object; cannot be cast to [Lgenerics.Generic;
// gia = (Generic<Integer>[])new Object[SIZE];
// 運行時類型是原始(擦除了)類型 即Object類型
gia = (Generic<Integer>[]) new Generic[SIZE];
System.out.println(gia.getClass().getSimpleName());
gia[0] = new Generic<Integer>();
// gia[1] = new Object(); // 編譯錯誤
// 編譯時發現類型不匹配
// gia[2] = new Generic<Double>();
}
} /*
public class GenericArray<T> {
private T[] array;// 存儲泛型類型
@SuppressWarnings("unchecked")//如果警告是符合預期的 可以通過該註解忽略警告
public GenericArray(int sz) {
//無法直接 創建 T[] array = new T[size]
//所以創建Object數組然後強轉
array = (T[]) new Object[sz];// 同樣出現類型擦除 需要強轉
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
// 暴露底層表示的方法 返回類型T的數組 但是調用它時
// Method that exposes the underlying representation:
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArray<Integer> gai = new GenericArray<Integer>(10);
// 運行時錯誤
// java.lang.ClassCastException: [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
// Integer[] ia = gai.rep();
// This is OK:
Object[] oa = gai.rep();//gai.rep按理來講是Integer數組 但是這隻在編譯時,運行時類型被擦除 只能看成Object數組
}
} // /:~
//內部使用時用Object類型的優勢在於 我們不太可能忘記運行時的類型 從而引入缺陷
public class GenericArray2<T> {
private Object[] array;//內部使用時用Object類型
public GenericArray2(int sz) {
array = new Object[sz];//內部使用時用Object類型
}
public void put(int index, T item) {
array[index] = item;
}
@SuppressWarnings("unchecked")
public T get(int index) {//返回時才轉型
return (T) array[index];
}
@SuppressWarnings("unchecked")
public T[] rep() {//返回時才轉型
return (T[]) array; // Warning: unchecked cast
}
public static void main(String[] args) {
GenericArray2<Integer> gai = new GenericArray2<Integer>(10);
for (int i = 0; i < 10; i++)
gai.put(i, i);
for (int i = 0; i < 10; i++)
System.out.print(gai.get(i) + " ");
System.out.println();
try {
Integer[] ia = gai.rep();
} catch (Exception e) {
System.out.println(e);
}
}
} /*
* Output: (Sample) 0 1 2 3 4 5 6 7 8 9 java.lang.ClassCastException:
* [Ljava.lang.Object; cannot be cast to [Ljava.lang.Integer;
*/// :~
import java.lang.reflect.*;
//使用一個類型標記
public class GenericArrayWithTypeToken<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArrayWithTypeToken(Class<T> type, int sz) {//傳遞了一個類型標記Class<T> type,以便從類型擦除中恢復
array = (T[]) Array.newInstance(type, sz);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
// Expose the underlying representation:
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<Integer>(
Integer.class, 10);
// This now works:
Integer[] ia = gai.rep();
}
} // /:~
15.9 邊界
extends使用在泛型邊界上和普通情況的例子
interface HasColor {
java.awt.Color getColor();
}
class Colored<T extends HasColor> {
T item;
Colored(T item) {
this.item = item;
}
T getItem() {
return item;
}
// 邊界允許你調用一個方法
java.awt.Color color() {
return item.getColor();
}
}
class Dimension {
public int x, y, z;
}
// 這不會起作用 -- 類必須在第一個 , 然後是接口:
// This won't work -- class must be first, then interfaces:
// class ColoredDimension<T extends HasColor & Dimension> {
// 多邊界:
// 可以看到這裏的extends和普通繼承關係的 extends 不同
// 這裏的extends被Java重寫了
class ColoredDimension<T extends Dimension & HasColor> {//ColoredDimension持有一個T 該類型繼承Dimension 實現HasColor
T item;
ColoredDimension(T item) {
this.item = item;
}
T getItem() {
return item;
}
java.awt.Color color() {// item實現了HasColor接口 因此可以調用getColor方法
return item.getColor();
}
int getX() {
return item.x;// item繼承了Dimension
}
int getY() {
return item.y;// item繼承了Dimension
}
int getZ() {
return item.z;// item繼承了Dimension
}
}
interface Weight {
int weight();
}
// As with inheritance, you can have only one
// concrete class but multiple interfaces:
// 因爲有繼承 你可以extends一個類以及多個接口
// 注意,類只可以放在第一個位置 否則報錯
// The type XXX is not an interface; it cannot be specified as a bounded
// parameter
class Solid<T extends Dimension & HasColor & Weight> {//Solid持有一個T 該類型繼承Dimension 實現HasColor和Weight
T item;
Solid(T item) {
this.item = item;
}
T getItem() {
return item;
}
java.awt.Color color() {
return item.getColor();
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
int weight() {
return item.weight();
}
}
//這裏是通常使用的extends
class Bounded extends Dimension implements HasColor, Weight {
public java.awt.Color getColor() {
return null;
}
public int weight() {
return 0;
}
}
public class BasicBounds {
public static void main(String[] args) {
//Bounded extends Dimension implements HasColor, Weight
//因此Bounded可以存儲在Solid
Solid<Bounded> solid = new Solid<Bounded>(new Bounded());
solid.color();
solid.getY();
solid.weight();
}
} // /:~
//例子 如何添加泛型邊界限制//每個層次都加入邊界限制
class HoldItem<T> {//HoldItem持有一個對象 item item類型沒有限制
T item;
HoldItem(T item) {
this.item = item;
}
T getItem() {
return item;
}
}
//前面的<T extends HasColor>是泛型邊界限制 後面的HoldItem<T>是繼承的意思
//Colored2繼承HoldItem<T> 它也持有一個對象item item限制爲<T extends HasColor>
class Colored2<T extends HasColor> extends HoldItem<T> {
Colored2(T item) {
super(item);
}
java.awt.Color color() {
return item.getColor();
}
}
//ColoredDimension2繼承Colored2<T> 它也持有一個對象item item限制爲<T extends Dimension & HasColor>
//當前類的限制<T extends Dimension & HasColor>其覆蓋範圍必須小於等於繼承類的限制<T extends HasColor>
class ColoredDimension2<T extends Dimension & HasColor> extends Colored2<T> {
ColoredDimension2(T item) {
super(item);
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
}
//進一步限制泛型類型
class Solid2<T extends Dimension & HasColor & Weight> extends
ColoredDimension2<T> {
Solid2(T item) {
super(item);
}
int weight() {
return item.weight();
}
}
public class InheritBounds {
public static void main(String[] args) {
Solid2<Bounded> solid2 = new Solid2<Bounded>(new Bounded());
solid2.color();
solid2.getY();
solid2.weight();
}
} // /:~
//更多層次添加泛型邊界限制示例
//Demonstrating bounds in Java generics.
import java.util.*;
interface SuperPower {// 超能力
}
interface XRayVision extends SuperPower {// 千里眼 透視
void seeThroughWalls();
}
interface SuperHearing extends SuperPower {// 順風耳
void hearSubtleNoises();
}
interface SuperSmell extends SuperPower {// 嗅覺靈敏
void trackBySmell();
}
class SuperHero<POWER extends SuperPower> {
POWER power;// 超級英雄具有能力
SuperHero(POWER power) {
this.power = power;
}
POWER getPower() {
return power;
}
}
class SuperSleuth<POWER extends XRayVision> extends SuperHero<POWER> {// 能力限制爲XRayVision
SuperSleuth(POWER power) {
super(power);
}
void see() {
power.seeThroughWalls();
}
}
class CanineHero<POWER extends SuperHearing & SuperSmell> extends
SuperHero<POWER> {// 能力限制爲SuperHearing和SuperSmell
CanineHero(POWER power) {
super(power);
}
void hear() {
power.hearSubtleNoises();
}
void smell() {
power.trackBySmell();
}
}
class SuperHearSmell implements SuperHearing, SuperSmell {// 普通類
public void hearSubtleNoises() {
}
public void trackBySmell() {
}
}
class DogBoy extends CanineHero<SuperHearSmell> {// SuperHearSmell滿足CanineHero泛型的限制
DogBoy() {
super(new SuperHearSmell());
}
}
public class EpicBattle {
// 邊界在泛型方法的使用
// Bounds in generic methods:
static <POWER extends SuperHearing> void useSuperHearing(
SuperHero<POWER> hero) {//返回類型限制爲SuperHearing
hero.getPower().hearSubtleNoises();
}
static <POWER extends SuperHearing & SuperSmell> void superFind(
SuperHero<POWER> hero) {//返回類型限制爲SuperHearing & SuperSmell
hero.getPower().hearSubtleNoises();
hero.getPower().trackBySmell();
}
public static void main(String[] args) {
DogBoy dogBoy = new DogBoy();
useSuperHearing(dogBoy);
superFind(dogBoy);
// You can do this:
List<? extends SuperHearing> audioBoys;
// But you can't do this: (因爲通配符"?"是被限制爲單一邊界)
// List<? extends SuperHearing & SuperSmell> dogBoys;
}
} // /:~
15.10 通配符
我們在前面的章節和本章前部分使用過通配符?
在本節 我們會更深入地討論該問題。
入手點是:可以向子類類型的數組賦予父類的數組引用。
例子:可以向子類類型的數組賦予父類的數組引用的例子(數組的協變)
//從本例子可以發現 數組的類型檢查 在編譯和運行時的類型檢查可能不同
class Fruit {
}
class Apple extends Fruit {
}
class Jonathan extends Apple {
}
class Orange extends Fruit {
}
public class CovariantArrays {
public static void main(String[] args) {
// 此處使用向上轉型 但是使用的時機不恰當
Fruit[] fruit = new Apple[10];// 創建父類類型Fruit數組的引用,指向子類類型Apple數組
fruit[0] = new Apple(); // OK
fruit[1] = new Jonathan(); // OK
// 運行時類型是 Apple[], not Fruit[] or Orange[]:
try {
// 編譯時允許添加Fruit:
// 編譯時 由於本身是一個Fruit數組,所以允許添加任意fruit及其子類
// 但是運行時發現是Apple數組,只能添加Apple及其子類
fruit[0] = new Fruit(); // ArrayStoreException
} catch (Exception e) {
System.out.println(e);
}
try {
// 編譯時允許添加Oranges:
fruit[0] = new Orange(); // ArrayStoreException
} catch (Exception e) {
System.out.println(e);
}
}
} /*
* Output: java.lang.ArrayStoreException: Fruit java.lang.ArrayStoreException:
* Orange
*/// :~
例子:泛型不支持協變
// {CompileTimeError} (Won't compile)
import java.util.*;
//將上一個例子中的類型錯誤檢查移到編譯時
public class NonCovariantGenerics {
// Compile Error: 類型不匹配:
// 不能將一個涉及Apple的容器 賦值給一個涉及Fruit的容器
//因爲像上一個例子那樣
//Apple的容器存放Apple及其子類
//Fruit容器存放Fruit及其子類 所以Fruit的List既可以放Apple 也可以放不是Apple的Fruit
//因此Fruit的List 和 Apple的List不等價
//這裏討論的是容器本身的類型 而不是容器持有的內容類型之間的關係
List<Fruit> flist = new ArrayList<Apple>();
} // /:~
//使用通配符可以在兩個類型建立向上轉型的關係(通配符支持協變)
public class GenericsAndCovariance {
public static void main(String[] args) {
// 通配符允許協變:
List<? extends Fruit> flist = new ArrayList<Apple>();
// List<? extends Fruit> 可以理解爲flist是一個List,該list的所有持有對象都是Fruit或者其子類
// List<? extends Fruit> flist期望的引用是任意fruit或者其子類 但是它不關心具體是什麼類型
// 只要是fruit的子類即可
// 由於通配符的向上轉型功能 new ArrayList<Apple>();實際轉型爲new ArrayList<Fruit>()
// ?又代表了不確定的類型 那麼編譯器就不知道實際存儲的類型了 因此添加任何類型的對象都會報錯
// Compile Error: can't add any type of object:
// flist.add(new Apple());
// flist.add(new Fruit());
// flist.add(new Object());
flist.add(null); // Legal but uninteresting
// We know that it returns at least Fruit:
Fruit f = flist.get(0);
}
} // /:~
解釋可能不是很清楚 但是隻要記住 使用了通配符聲明的引用,無法調用任何參數類型爲泛型的方法,因爲它不知道當前的類型
15.10.1 編譯器有多聰明
注意使用通配符之後 不是所有的方法都無法調用 而是方法參數爲泛型類型的方法,無法調用 比如下面的例子
public class CompilerIntelligence {
public static void main(String[] args) {
Apple apple = new Apple();
List<? extends Fruit> flist =
Arrays.asList(apple);
//flist.add(new Apple());//compile error
//E get(int index);
Apple a = (Apple)flist.get(0); // No warning
//boolean contains(Object o);
System.out.println(flist.contains(apple));// Argument is 'Object'
//int indexOf(Object o);
System.out.println(flist.indexOf(apple));// Argument is 'Object'
}
} ///:~
可以看到 如果參數是Object類型或者返回值是泛型類型,仍然可以調用
另一個例子
public class Holder<T> {
private T value;
public Holder() {
}
public Holder(T val) {
value = val;
}
public void set(T val) {
value = val;
}
public T get() {
return value;
}
public boolean equals(Object obj) {
return value.equals(obj);
}
public static void main(String[] args) {
Holder<Apple> apple = new Holder<Apple>();
Apple d = apple.get();
apple.set(d);
// Holder<Fruit> Fruit = apple; // 泛型不支持協變 通配符才支持
Holder<? extends Fruit> fruit = apple; // OK
Fruit p = fruit.get();//fruit繼承自Fruit 可以向上轉型
d = (Apple) fruit.get(); // Returns 'Object' //開發者確保安全性
try {
Orange c = (Orange) fruit.get(); // No warning//開發者確保安全性
} catch (Exception e) {
System.out.println(e);
}
// fruit.set(new Apple()); // 使用了通配符,無法再使用泛型類型 Cannot call set()
// fruit.set(new Fruit()); // 使用了通配符,無法再使用泛型類型 Cannot call set()
System.out.println(fruit.equals(d)); // equals方法沒有使用泛型 所以沒有問題
}
} /*
* Output: (Sample) java.lang.ClassCastException: Apple cannot be cast to Orange
* true
*/// :~
15.10.2 逆變(超類型通配符) (下界通配符)
之前我們使用的是<? extends MyClass> (extends後面的爲上界)
現在我們可以使用<? super MyClass>甚至是<? super T>
可以讀作任意是類型T的父類類型(super後面跟着的爲下界)
(extends可以理解爲“繼承自” super可以理解爲“的子類之一是”)
注意不可以聲明爲(T super MyClass)
超類型通配符使用案例
//class Fruit {
//}
//
//class Apple extends Fruit {
//}
//
//class Jonathan extends Apple {
//}
//
//class Orange extends Fruit {
//}
public class SuperTypeWildcards {
static void writeTo(List<? super Apple> apples) {
apples.add(new Apple());
apples.add(new Jonathan());
// apples.add(new Fruit()); // Error
}
} // /:~
解釋:writeTo方法的參數apples的類型不確定 但是知道是Apple的直接或間接父類,即Apple是類型下界(畫個繼承關係圖更好理解)
既然它是apple的父類型,那麼我們可以向該列表添加Apple或者其子類
超類型通配符(super)就是下界通配符
子類型通配符(extends)就是上界通配符
//使用下界通配符寫入對象
public class GenericWriting {
static List<Apple> apples = new ArrayList<Apple>();
static List<Fruit> fruit = new ArrayList<Fruit>();
static <T> void writeExact(List<T> list, T item) {
list.add(item);
}
static void f1() {
writeExact(apples, new Apple());//不使用通配符 向AppleList添加Apple
// writeExact(fruit, new Apple()); // Error:
// Incompatible types: found Fruit, required Apple
//不使用通配符 無法向FruitList添加Apple 即使知道可以
}
static <T> void writeWithWildcard(List<? super T> list, T item) {//使用通配符
//?是T的父類
list.add(item);
}
static void f2() {
writeWithWildcard(apples, new Apple());
writeWithWildcard(fruit, new Apple());//使用通配符纔可以向fruitList添加Apple
//調用時fruit是下界 因此可以向該list添加fruit或者其子類的對象
}
public static void main(String[] args) {
f1();
f2();
}
} // /:~
//使用上界通配符讀取對象
public class GenericReading {
static <T> T readExact(List<T> list) {
return list.get(0);
}
static List<Apple> apples = Arrays.asList(new Apple());
static List<Fruit> fruit = Arrays.asList(new Fruit());
// A static method adapts to each call:
static void f1() {
Apple a = readExact(apples);//返回一個Apple
Fruit f = readExact(fruit);//返回一個Fruit
f = readExact(apples);//返回一個Apple賦值給Fruit
}
// If, however, you have a class, then its type is
// established when the class is instantiated:
static class Reader<T> {
T readExact(List<T> list) {
return list.get(0);
}
}
static void f2() {
Reader<Fruit> fruitReader = new Reader<Fruit>();//泛型確定爲Fruit
Fruit f = fruitReader.readExact(fruit);
// Fruit a = fruitReader.readExact(apples); // Error:
// readExact(List<Fruit>) cannot be
// applied to (List<Apple>).
// 如15.10所述 泛型不支持協變
}
static class CovariantReader<T> {
T readCovariant(List<? extends T> list) {//使用上界通配符來讀取
return list.get(0);
}
}
static void f3() {
CovariantReader<Fruit> fruitReader = new CovariantReader<Fruit>();
//可以從fruit列表讀取fruit或者apples列表讀取apple賦值給Fruit
Fruit f = fruitReader.readCovariant(fruit);
Fruit a = fruitReader.readCovariant(apples);
}
public static void main(String[] args) {
f1();
f2();
f3();
}
} // /:~
上面兩個例子分別顯示了上界通配符和下界通配符的使用場景
15.10.3 無界通配符
<?> 看起來意味着任何事物 那麼它和Object有什麼區別呢 其實有區別的
//本示例綜合使用了上界 下界 無界通配符
public class Wildcards {
// Raw argument:
static void rawArgs(Holder holder, Object arg) {
holder.set(arg); // Warning:
// Unchecked call to set(T) as a
// member of the raw type Holder
// holder.set(new Wildcards()); // Same warning
// Can't do this; don't have any 'T':
// T t = holder.get();
// OK, but type information has been lost:
Object obj = holder.get();
}
// Holder<?> 和 Holder<Object>的區別
// Holder<Object>是持有任何類型的數組
// Holder<?> 是持有某種類型的同種類型的集合
static void unboundedArg(Holder<?> holder, Object arg) {
// holder.set(arg); // Holder<?> 是持有某種類型的同種類型的集合 不能只向其中放入Object
// set(capture of ?) in Holder<capture of ?>
// cannot be applied to (Object)
// holder.set(new Wildcards()); // Same error
// Can't do this; don't have any 'T':
// T t = holder.get();
// OK, but type information has been lost:
Object obj = holder.get();
}
static <T> T exact1(Holder<T> holder) {
T t = holder.get();
return t;
}
static <T> T exact2(Holder<T> holder, T arg) {
holder.set(arg);
T t = holder.get();
return t;
}
//子類(上界)通配符 適用於get
static <T> T wildSubtype(Holder<? extends T> holder, T arg) {
// holder.set(arg); // Error:
// set(capture of ? extends T) in
// Holder<capture of ? extends T>
// cannot be applied to (T)
T t = holder.get();
return t;
}
//父類(下界)通配符 適用於set
static <T> void wildSupertype(Holder<? super T> holder, T arg) {
holder.set(arg);
// T t = holder.get(); // Error:
// Incompatible types: found Object, required T
// OK, but type information has been lost:
Object obj = holder.get();
}
public static void main(String[] args) {
//再次比較<?>和原生類型<Object>的區別
ArrayList<?> arrays = new ArrayList<>();
//arrays.add(new Object());//報錯
ArrayList<Object> arrays1 = new ArrayList<>();
arrays1.add(new Object());
Holder raw = new Holder<Long>();
// Or:
raw = new Holder();
Holder<Long> qualified = new Holder<Long>();
Holder<?> unbounded = new Holder<Long>();
Holder<? extends Long> bounded = new Holder<Long>();
Long lng = 1L;
rawArgs(raw, lng);
rawArgs(qualified, lng);
rawArgs(unbounded, lng);
rawArgs(bounded, lng);
unboundedArg(raw, lng);
unboundedArg(qualified, lng);
unboundedArg(unbounded, lng);
unboundedArg(bounded, lng);
// Object r1 = exact1(raw); // Warnings:
// Unchecked conversion from Holder to Holder<T>
// Unchecked method invocation: exact1(Holder<T>)
// is applied to (Holder)
Long r2 = exact1(qualified);
Object r3 = exact1(unbounded); // Must return Object
Long r4 = exact1(bounded);
// Long r5 = exact2(raw, lng); // Warnings:
// Unchecked conversion from Holder to Holder<Long>
// Unchecked method invocation: exact2(Holder<T>,T)
// is applied to (Holder,Long)
Long r6 = exact2(qualified, lng);
// Long r7 = exact2(unbounded, lng); // Error:
// exact2(Holder<T>,T) cannot be applied to
// (Holder<capture of ?>,Long)
// Long r8 = exact2(bounded, lng); // Error:
// exact2(Holder<T>,T) cannot be applied
// to (Holder<capture of ? extends Long>,Long)
// Long r9 = wildSubtype(raw, lng); // Warnings:
// Unchecked conversion from Holder
// to Holder<? extends Long>
// Unchecked method invocation:
// wildSubtype(Holder<? extends T>,T) is
// applied to (Holder,Long)
Long r10 = wildSubtype(qualified, lng);
// OK, but can only return Object:
Object r11 = wildSubtype(unbounded, lng);
Long r12 = wildSubtype(bounded, lng);
// wildSupertype(raw, lng); // Warnings:
// Unchecked conversion from Holder
// to Holder<? super Long>
// Unchecked method invocation:
// wildSupertype(Holder<? super T>,T)
// is applied to (Holder,Long)
wildSupertype(qualified, lng);
// wildSupertype(unbounded, lng); // Error:
// wildSupertype(Holder<? super T>,T) cannot be
// applied to (Holder<capture of ?>,Long)
// wildSupertype(bounded, lng); // Error:
// wildSupertype(Holder<? super T>,T) cannot be
// applied to (Holder<capture of ? extends Long>,Long)
}
} // /:~
15.10.4 捕獲轉換
//f2使用了無界通配符 但它調用f1時 f1仍然可以知道具體類型
public class CaptureConversion {
static <T> void f1(Holder<T> holder) {//該方法沒有使用通配符 因此沒有邊界
T t = holder.get();
System.out.println(t.getClass().getSimpleName());
}
static void f2(Holder<?> holder) {//f2使用了無界通配符 並且調用了沒有使用通配符的方法f1
f1(holder); // Call with captured type
}
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Holder raw = new Holder<Integer>(1);
f1(raw); // Produces warnings
f2(raw); // No warnings
Holder rawBasic = new Holder();
rawBasic.set(new Object()); // Warning
f2(rawBasic); // No warnings
// Upcast to Holder<?>, still figures it out:
Holder<?> wildcarded = new Holder<Double>(1.0);
f2(wildcarded);
}
} /*
* Output: Integer Object Double
*/// :~
15.11 問題
泛型的各種問題
15.11.1 任何基本類型都不能作爲類型參數
由於基本類型都有包裝類 因此 大部分問題都可以解決 比如像下面這個
//使用包裝類創建泛型集合類
public class ByteSet {
Byte[] possibles = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Set<Byte> mySet = new HashSet<Byte>(Arrays.asList(possibles));
// But you can't do this:
// Set<Byte> mySet2 = new HashSet<Byte>(
// Arrays.<Byte>asList(1,2,3,4,5,6,7,8,9));
} // /:~
//包裝類無法解決基本類型無法作爲泛型類型的所有問題
// Fill an array using a generator:
class FArray {
public static <T> T[] fill(T[] a, Generator<T> gen) {//Generator是一個接口 僅有一個next方法
//方法作用爲填充數組並返回
for (int i = 0; i < a.length; i++){
a[i] = gen.next();
}
return a;
}
}
public class PrimitiveGenericTest {
public static void main(String[] args) {
//RandomGenerator的作用是生成各種包裝類的生成器 第十六章會講
String[] strings = FArray.fill(new String[7],
new RandomGenerator.String(10));
for (String s : strings)
System.out.println(s);
Integer[] integers = FArray.fill(new Integer[7],
new RandomGenerator.Integer());
for (int i : integers)
System.out.println(i);
// Autoboxing won't save you here. This won't compile:
// int[] b =
// FArray.fill(new int[7], new RandIntGenerator());
// 看上去還是泛型數組無法直接賦值給基本類型數組
}
} /*
* Output: YNzbrnyGcF OWZnTcQrGs eGZMmJMRoE suEcUOneOE dLsmwHLGEa hKcxrEqUCB
* bkInaMesbt 7052 6665 2654 3909 5202 2209 5458
*/// :~
15.11.2 實現參數化接口 一個類不能實現泛型接口的兩種變體
一個類不能實現泛型接口的兩種變體
比如下面這個例子 看Hourly類 由於父類實現了Payable ,因此Hourly類實現了Payable和Payable兩個接口
interface Payable<T> {}
class Employee implements Payable<Employee> {}
class Hourly extends Employee
implements Payable<Hourly> {} ///:~
//報錯
//The interface Payable cannot be implemented more than once with different arguments: Payable<Employee> and Payable
//也就是說編譯器認爲Payable<Employee>和Payable<Hourly>是相同接口
//原因是類型擦除
15.11.3 泛型轉型和警告
我們必須在一些情況加上@SuppressWarnings(“unchecked”) 但是按道理講是沒有必要的 (泛型強制轉換看起來無效)
//加了泛型 仍然需要轉型
public class NeedCasting {
@SuppressWarnings("unchecked")
public void f(String[] args) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
args[0]));
//泛型轉型似乎沒有效果 仍然提示需要轉型
List<Widget> shapes = (List<Widget>) in.readObject();
//不使用泛型 則不會發出警告
//List shapes = (ArrayList) in.readObject();
}
} // /:~
/**
如果去掉@SuppressWarnings("unchecked")
則出現下面的情況
C:\Users\hjcai\Desktop>javac NeedCasting.java
Note: NeedCasting.java uses unchecked or unsafe operations.
Note: Recompile with -Xlint:unchecked for details.
C:\Users\hjcai\Desktop>javac -Xlint:unchecked NeedCasting.java
NeedCasting.java:13: warning: [unchecked] unchecked cast
List<Widget> shapes = (List<Widget>) in.readObject();
^
required: List<Widget>
found: Object
1 warning
看起來(List<Widget>)不是一個強制轉換
**/
看另一個例子 這裏我們使用Java SE5新的轉型方式 – 泛型類轉型
public class ClassCasting {
@SuppressWarnings("unchecked")
public void f(String[] args) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
args[0]));
// Won't Compile:
// List<Widget> lw1 =
// List<Widget>.class.cast(in.readObject());
List<Widget> lw2 = List.class.cast(in.readObject());
List<Widget> lw3 = (List<Widget>) List.class.cast(in.readObject());//不加@SuppressWarnings和lw2一樣的警告
}
} // /:~
/**
* 當我們去掉@SuppressWarnings("unchecked")時 仍然有警告
C:\Users\hjcai\Desktop>javac -Xlint:unchecked ClassCasting.java
ClassCasting.java:13: warning: [unchecked] unchecked conversion
List<Widget> lw2 = List.class.cast(in.readObject());
^
required: List<Widget>
found: List
ClassCasting.java:15: warning: [unchecked] unchecked cast
(List<Widget>)List.class.cast(in.readObject());
^
required: List<Widget>
found: List
2 warnings
**/
15.11.4 存在泛型參數方法的重載
// {CompileTimeError} (Won't compile)
import java.util.*;
public class UseList<W, T> {
//由於類型擦除 這兩個方法在編譯器看來是同一個方法 因此無法編譯
void f(List<T> v) {
}
void f(List<W> v) {
}
} // /:~
public class UseList2<W,T> {
//必須使用不同的方法名以示區別纔可以編譯
void f1(List<T> v) {}
void f2(List<W> v) {}
} ///:~
15.11.5 基類劫持接口 (基類實現接口時確定了泛型類型 子類無法修改)
//ComparablePet實現Comparable接口 期望可以進行比較
public class ComparablePet implements Comparable<ComparablePet> {
public int compareTo(ComparablePet arg) {
return 0;
}
} // /:~
// {CompileTimeError} (Won't compile)
//Cat繼承了ComparablePet並實現Comparable接口 期望對Comparable進行窄化處理
//Cat只能與Cat比較
//但是無法編譯 報錯如下
//The interface Comparable cannot be implemented more than once with different arguments: Comparable<ComparablePet> and Comparable<Cat>
//由於擦除 父類子類的實現接口相同 這裏如果不使用泛型 可以編譯,使用泛型導致報錯
//因爲Comparable的參數類型在父類已經確定 子類無法修改類型
class Cat extends ComparablePet implements Comparable<Cat>{
// Error: Comparable cannot be inherited with
// different arguments: <Cat> and <Pet>
public int compareTo(Cat arg) { return 0; }
} ///:~
//要想覆蓋基類的Comparable接口
//只能確保類型完全相同
//方式1
class Hamster extends ComparablePet implements Comparable<ComparablePet> {
public int compareTo(ComparablePet arg) {
return 0;
}
}
//方式二
// Or just:
class Gecko extends ComparablePet {
public int compareTo(ComparablePet arg) {
return 0;
}
} // /:~
15.12 自限定的類型
我們也許會看到類似
class MyTest<T extends MyTest>{
}
之類的聲明,這就是循環泛型,這看起來很難理解,讓我們從簡單的入手
15.12.1 古怪的循環泛型
先不使用自限定邊界(不使用extends泛型邊界)
public class BasicHolder<T> {
T element;
void set(T arg) {
element = arg;
}
T get() {
return element;
}
void f() {
System.out.println(element.getClass().getSimpleName());
}
} // /:~
//Subtype類繼承一個泛型類型 該類型接受Subtype作爲參數
class Subtype extends BasicHolder<Subtype> {
}
public class CRGWithBasicHolder {
public static void main(String[] args) {
Subtype st1 = new Subtype(), st2 = new Subtype();
st1.set(st2);
Subtype st3 = st1.get();
st1.f();
}
} /*
* Output: Subtype
*/// :~
/**
子類Subtype接受的參數以及返回值均是Subtype類型 而不是父類類型
CRG(循環泛型)的本質:基類中的泛型類型用子類類型代替
也就是說 泛型基類變成所有子類公共功能的模板 這些方法對於所有參數和返回值將使用子類類型
比如該例子 set的參數和get的返回值均是子類類型
*/
這裏的BasicHolder 看起來變成了所有子類的一個公共模板
15.12.2 自限定
上面的BasicHolder可以使用仍以類型作爲其泛型參數 比如:
class Other {
}
class BasicOther extends BasicHolder<Other> {
}
public class Unconstrained {
public static void main(String[] args) {
BasicOther b = new BasicOther(), b2 = new BasicOther();
b.set(new Other());
Other other = b.get();
b.f();
}
} /*
* Output: Other
*/// :~
上面這個例子和Subtype幾乎一樣
下面我們更進一步 使用泛型邊界限定(extends) 觀察具體的使用方法 以及哪些不可以使用
class SelfBounded<T extends SelfBounded<T>> {//基類使用自限定泛型類型
//可以對比BasicHolder 容易理解
T element;
SelfBounded<T> set(T arg) {//注意返回值 返回的是泛型類型T
element = arg;
return this;
}
T get() {
return element;
}
}
class A extends SelfBounded<A> {//強制要求A類傳遞給基類 使用A類當作泛型類型
}
class B extends SelfBounded<A> {//雖然可以這麼寫 但是很少這麼用
//由於A extends SelfBounded<A> 因此
//A類滿足<T extends SelfBounded<T>>
} // Also OK
class C extends SelfBounded<C> {
C setAndGet(C arg) {//新增方法 參數和返回值都是確切類型(C)
set(arg);
return get();
}
}
class D {
}
// Can't do this:
// class E extends SelfBounded<D> {}
// Compile error: Type parameter D is not within its bound
//編譯錯誤 參數類型D不在邊界內
// Alas, you can do this, so you can't force the idiom:
//但是你卻可以這麼做
class F extends SelfBounded {
}
public class SelfBounding {
public static void main(String[] args) {
A a = new A();
a.set(new A());
a = a.set(new A()).get();
a = a.get();
C c = new C();
c = c.setAndGet(new C());
}
} // /:~
//自限定的參數意義在於 確保類型參數與正在被定義的類相同
------
再對比一下沒有使用自限定限制
//不使用自限定泛型
public class NotSelfBounded<T> {//該類和BasicHolder基本一致
T element;
NotSelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() {
return element;
}
}
class A2 extends NotSelfBounded<A2> {
}
class B2 extends NotSelfBounded<A2> {
}
class C2 extends NotSelfBounded<C2> {
C2 setAndGet(C2 arg) {
set(arg);
return get();
}
}
class D2 {
}
// Now this is OK:
class E2 extends NotSelfBounded<D2> {//自限定限制只能強制作用於繼承關係
} // /:~
15.12.3 參數協變(考慮什麼情況可以進行基於參數類型的重載)
不使用自限定泛型例子
class Base {
}
class Derived extends Base {
}
interface OrdinaryGetter {
Base get();
}
interface DerivedGetter extends OrdinaryGetter {
// Return type of overridden method is allowed to vary:
//返回類型允許修改(修改爲範圍更小的類型)
@Override
Derived get();
}
public class CovariantReturnTypes {
void test(DerivedGetter d) {
Derived d2 = d.get();
}
} // /:~
使用自限定類型
//作用和CovariantReturnTypes一樣 關注返回類型
interface GenericGetter<T extends GenericGetter<T>> {
T get();
}
interface Getter extends GenericGetter<Getter> {
}
public class GenericsAndReturnTypes {
void test(Getter g) {
Getter result = g.get();
GenericGetter gg = g.get(); // Also the base type
}
} // /:~
不使用自限定泛型 關注點轉移到參數類型
class OrdinarySetter {
void set(Base base) {
System.out.println("OrdinarySetter.set(Base)");
}
}
class DerivedSetter extends OrdinarySetter {//DerivedSetter存在兩個set方法
//對比DerivedGetter 返回值可以修改 但是參數不可以修改 這裏不是覆蓋
void set(Derived derived) {
System.out.println("DerivedSetter.set(Derived)");
}
}
public class OrdinaryArguments {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
DerivedSetter ds = new DerivedSetter();
ds.set(derived);
//可以編譯 但是是重載 不是覆蓋
ds.set(base); // Compiles: overloaded, not overridden!
}
} /*
* Output: DerivedSetter.set(Derived) OrdinarySetter.set(Base)
*/// :~
//使用自限定泛型 關注參數類型
interface SelfBoundSetter<T extends SelfBoundSetter<T>> {
void set(T arg);
}
interface Setter extends SelfBoundSetter<Setter> {//子類只有一個set方法 並且參數類型是子類類型
}
public class SelfBoundingAndCovariantArguments {
void testA(Setter s1, Setter s2, SelfBoundSetter sbs) {
s1.set(s2);
// s1.set(sbs); // Error:不能使用父類類型
// set(Setter) in SelfBoundSetter<Setter>
// cannot be applied to (SelfBoundSetter)
}
} // /:~
不使用自限定泛型
//對比OrdinaryArguments 關注參數類型
class GenericSetter<T> { // Not self-bounded
void set(T arg) {
System.out.println("GenericSetter.set(Base)");
}
}
class DerivedGS extends GenericSetter<Base> {
//沒有使用自限定類型 將可以進行重載
void set(Derived derived) {
System.out.println("DerivedGS.set(Derived)");
}
}
public class PlainGenericInheritance {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
DerivedGS dgs = new DerivedGS();
dgs.set(derived);
dgs.set(base); // Compiles: overloaded, not overridden!
}
} /*
* Output: DerivedGS.set(Derived) GenericSetter.set(Base)
*/// :~
結論:使用自限定類型 子類將使用確切的子類類型,如果不使用自限定類型,方法可以被重載(參數類型方式重載)
15.13 動態類型安全
由於JavaSE5之前不支持泛型 那麼舊代碼可能破壞你的泛型容器 嘗試向其中添加類型不正確的對象 java.util.Collections中存在一系列check方法可以解決這類類型錯誤問題
如果不使用這類方法 泛型類型的容器將會在取出對象時報錯
// Using Collection.checkedList().
import java.util.*;
class Pet {
}
class Dog extends Pet {
}
class Cat extends Pet {
}
public class CheckedList {
@SuppressWarnings("unchecked")
static void oldStyleMethod(List probablyDogs) {
probablyDogs.add(new Cat());// 悄悄地在DogList插入一個Cat
}
public static void main(String[] args) {
//創建Dog list
List<Dog> dogs1 = new ArrayList<Dog>();
oldStyleMethod(dogs1); // 悄悄地插入一個Cat
//參數:檢查的列表 列表中的類型
List<Dog> dogs2 = Collections.checkedList(new ArrayList<Dog>(),
Dog.class);
try {
oldStyleMethod(dogs2); // Throws an exception
} catch (Exception e) {
System.out.println(e);
}
// Derived types work fine:
// 基類類型列表正常工作
List<Pet> pets = Collections.checkedList(new ArrayList<Pet>(),
Pet.class);
pets.add(new Dog());
pets.add(new Cat());
}
} /*
* Output: java.lang.ClassCastException: Attempt to insert class
* typeinfo.pets.Cat element into collection with element type class
* typeinfo.pets.Dog
*/// :~
15.14 異常
將泛型應用於異常是受限的,因爲
1.由於擦除,在運行時catch語句不能知曉異常的確切類型
2.泛型類不能繼承Throwable(The generic class XXX may not subclass java.lang.Throwable)
但是可以以另外一種形式引入異常
interface Processor<T, E extends Exception> {//以第二個參數的形式將Exception引入
void process(List<T> resultCollector) throws E;
}
class ProcessRunner<T, E extends Exception> extends ArrayList<Processor<T, E>> {
List<T> processAll() throws E {
List<T> resultCollector = new ArrayList<T>();
for (Processor<T, E> processor : this)
processor.process(resultCollector);
return resultCollector;
}
}
class Failure1 extends Exception {
}
class Processor1 implements Processor<String, Failure1> {
static int count = 3;//注意是static的 所有對象公用
public void process(List<String> resultCollector) throws Failure1 {
//初始值爲3
//第一次3 > 1add("Hep!")
//第二次2 > 1add("Hep!")
//第三次1不大於1add("Ho!")
//且沒有拋出異常
//如果把count初始值改爲小於等於1的值 將會拋出異常
if (count-- > 1)
resultCollector.add("Hep!");
else
resultCollector.add("Ho!");
if (count < 0)
throw new Failure1();
}
}
class Failure2 extends Exception {
}
class Processor2 implements Processor<Integer, Failure2> {
static int count = 2;
public void process(List<Integer> resultCollector) throws Failure2 {
//
if (count-- == 0)
resultCollector.add(47);
else {
resultCollector.add(11);
}
if (count < 0)
throw new Failure2();
}
}
public class ThrowGenericException {
public static void main(String[] args) {
ProcessRunner<String, Failure1> runner = new ProcessRunner<String, Failure1>();
for (int i = 0; i < 3; i++)
runner.add(new Processor1());//執行3次
try {
System.out.println(runner.processAll());
} catch (Failure1 e) {
System.out.println(e);
}
ProcessRunner<Integer, Failure2> runner2 = new ProcessRunner<Integer, Failure2>();
for (int i = 0; i < 3; i++)
runner2.add(new Processor2());//執行3次
try {
System.out.println(runner2.processAll());
} catch (Failure2 e) {
System.out.println(e);
}
}
} // /:~
/**
[Hep!, Hep!, Ho!]
generics.Failure2
*/
15.15 混型
這裏的混型是指混合多個類的能力到一個類上
15.15.1& 15.15.2
由於JAVA不支持多重繼承 只能用接口來模擬這種情況
C++使用混型比Java容易得多,原因在於它支持多重繼承 Java要實現混型 其代碼量遠大於C++
15.15.3
書中提到Java的混型與裝飾器模式很類似,這裏不是很理解。裝飾器模式在於裝飾者和被裝飾者都繼承了同一個基類
而混型的關鍵在於多重繼承。也許是理解還不夠到位,看不出來相似性。
雖然說書中將混型的例子用裝飾器模式實現了,但是其實混型和裝飾器還是有很大不同的。關於裝飾器可以參考我之前的鏈接
https://blog.csdn.net/u011109881/article/details/81051385
書中接下來將例子修改爲使用裝飾器模式實現 但是感覺和真正的混型差別很大。
15.15.4 與動態代理混合
有些看不明白。。。
15.16 潛在類型機制
所謂潛在類型機制是指一種類型 只要該類型滿足具有某些特定方法 就屬於一種特殊類型(和實現了某個接口的類很類似的概念)
在C++與Python中 這種實現很靈活,而在Java中 最終還是迴歸到實現特定接口上了
public interface Performs {
void speak();
void sit();
} ///:~
class PerformingDog extends Dog implements Performs {
public void speak() {
print("Woof!");
}
public void sit() {
print("Sitting");
}
public void reproduce() {
}
}
class Robot implements Performs {
public void speak() {
print("Click!");
}
public void sit() {
print("Clank!");
}
public void oilChange() {
}
}
//不明白這麼寫的目的 感覺多此一舉 因爲下面那種實現更清晰,可能是爲了模仿C++和python?
//class Communicate {
// public static <T extends Performs> void perform(T performer) {
// performer.speak();
// performer.sit();
// }
//}
class Communicate {
public static void perform(Performs performer) {
performer.speak();
performer.sit();
}
}
public class DogsAndRobots {
public static void main(String[] args) {
PerformingDog d = new PerformingDog();
Robot r = new Robot();
Communicate.perform(d);
Communicate.perform(r);
}
} /*
* Output: Woof! Sitting Click! Clank!
*/// :~
15.17 15.18 都在強調如何在Java中實現潛在類型機制以及如何優化。 但實際中 潛在類型機制似乎並沒有廣泛的使用,這部分我本身也看得雲裏霧裏 就先跳過了。
15.19 總結
即使沒有泛型 Java的強制轉換其實不是很遭,泛型的最根本是解決將Dog類型的對象插入到Cat列表,但其實這類問題很容易發現,但是,因爲泛型是java誕生很久之後添加的新功能,其兼容性使得程序的升級變得相當複雜,而且,本章也討論了泛型的各種缺陷,因此引入泛型究竟是好是壞,這個問題值得深思。