Java筆記 – 泛型 泛型方法 泛型介面 擦除 邊界 萬用字元
Java中的泛型參考了C++的模板,Java的界限是Java泛型的侷限。
2、簡單泛型促成泛型出現最引人注目的一個原因就是為了創造容器類。
首先看一個只能持有單個物件的類,這個類可以明確指定其持有的物件的型別
class Holder1 {
private Circle a;
public Holder1(Circle a) { this.a = a; }
Circle get() { return a; }
}
上面的類的可重用性不怎麼樣,無法持有其他型別的任何物件,下面通過持有Object型別的物件實現
class Holder2 { private Object a; publicHolder2(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 Circle()); Circle a = (Circle)h2.get(); h2.set("Not an Automobile"); String s = (String)h2.get(); h2.set(1); // Autoboxes to Integer Integer x = (Integer)h2.get(); } }
通常而言,我們只會用容器來儲存一種型別的隊形,泛型的主要目的之一就是用來指定容器要持有什麼型別的物件,由編譯器來保證型別的正確性:
class Holder3<T> { 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物件時,必須指明想持有什麼型別的物件,然後只能存入該型別(或其子型別,因為多臺與泛型不衝突)的物件了// 取出物件的時候,會自動轉型為正確的型別 Holder3<Circle> h3 = new Holder3<Circle>(new Circle()); Circle a = h3.get(); // No cast needed // h3.set("Not an Automobile"); // Error // h3.set(1); // Error } }
也就是告訴編譯器想使用什麼型別,然後編譯器幫你處理一切細節。
2.1、一個一元組類庫
為了在一次方法呼叫返回多個物件,可以使用元組的概念:將一組物件直接打包儲存於其中的一個單一物件,這個類容器允許讀取其中元素,但是不允許向其中存放新的物件(也稱為資料傳送物件,或信使)。
元組可以具有任意長度,元組中的物件可以使任意不同型別的,下面的程式是一個二維元組,能夠持有兩個物件:
class TwoTuple<A,B> {
public final A first; // 宣告為final,同樣確保了public的安全性,不可改寫,如果想要使用具有不同元素的元組,就強制要求另外建立一個新的TwoTuple物件
public final B second;
// 元組隱含的保持了其中元素的次序
public TwoTuple(A a, B b) { first = a; second = b; }
public String toString() {
return "(" + first + ", " + second + ")";
}
}
/**
* 使用繼承機制實現長度更長的元組
*/
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 FourTuple<A,B,C,D> extends ThreeTuple<A,B,C> {
public final D fourth;
public FourTuple(A a, B b, C c, D d) {
super(a, b, c);
fourth = d;
}
public String toString() {
return "(" + first + ", " + second + ", " +
third + ", " + fourth + ")";
}
}
為了使用元組,只需定義長度合適的元組,作為方法的返回值就可以了
class TupleTest {
static TwoTuple<String,Integer> f() {
// Autoboxing converts the int to Integer:
return new TwoTuple<String,Integer>("hi", 47); // 這裡的new語句有點囉嗦,後面有方法簡化
}
static ThreeTuple<Circle,String,Integer> g() {
return new ThreeTuple<Circle, String, Integer>(
new Circle(), "hi", 47);
}
static FourTuple<Circle,Square,String,Integer> h() {
return
new FourTuple<Circle,Square,String,Integer>(
new Circle(), new Square(), "hi", 47);
}
static FiveTuple<Circle,Square,String,Integer,Double> k() {
return new
FiveTuple<Circle,Square,String,Integer,Double>(
new Circle(), new Square(), "hi", 47, 11.1);
}
public static void test() {
TwoTuple<String,Integer> ttsi = f();
FourTuple<Circle,Square,String,Integer> ts = h();
System.out.println(ttsi);
// ttsi.first = "there"; // Compile error: final
}
}
class FiveTuple<A,B,C,D,E> extends FourTuple<A,B,C,D> {
public final E fifth;
public FiveTuple(A a, B b, C c, D d, E e) {
super(a, b, c, d);
fifth = e;
}
public String toString() {
return "(" + first + ", " + second + ", " +
third + ", " + fourth + ", " + fifth + ")";
}
}
public class Chapter15_2_1 {
public static void main(String args){
TupleTest.test();
}
}
2.2、一個堆疊類
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>(); // End sentinel
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;
}
public static void test() {
LinkedStack<String> lss = new LinkedStack<String>();
for(String s : "Phasers on stun!".split(" "))
lss.push(s);
String s;
while((s = lss.pop()) != null)
System.out.println(s);
}
} /* Output:
stun!
on
Phasers
*///:~
2.3、RandomList
持有特定型別物件的列表,每次呼叫其上的select()方法,可以隨機地取一個元素:
class RandomList<T> { private ArrayList<T> storage = new ArrayList<T>(); private Random rand = new Random(47); public void add(T 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 *///:~3、泛型介面
泛型介面也可以應用於介面,例如生成器
是工廠模式的一種應用,不過使用生成器建立新的物件的時候,不需要任何的引數,而工廠方法一般需要引數。
下面我就來建立一個生成器來展示泛型在介面中的使用場景
interface Generator<T> { T next(); } ///:~
// 現在我們編寫一個類,實現Generator<Shape>介面,能夠隨機生成不同型別的Coffee物件
// 實現了Iterable介面,所以可以再迴圈語句中使用
class ShapeGenerator implements Generator<Shape>, Iterable<Shape> {
private Class[] types = { Circle.class, Square.class,
Triangle.class};
private static Random rand = new Random(47);
public ShapeGenerator() {}
// For iteration:
private int size = 0;
public ShapeGenerator(int sz) { size = sz; }
public Shape next() {
try {
return (Shape)
types[rand.nextInt(types.length)].newInstance();
// Report programmer errors at run time:
} catch(Exception e) {
throw new RuntimeException(e);
}
}
class ShapeIterator implements Iterator<Shape> {
int count = size;
public boolean hasNext() { return count > 0; }
public Shape next() {
count--;
return ShapeGenerator.this.next();
}
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
// 迭代方法
@Override
public Iterator<Shape> iterator() {
return new ShapeIterator();
}
public static void test() {
ShapeGenerator gen = new ShapeGenerator();
for(int i = 0; i < 5; i++)
System.out.println(gen.next());
for(Shape c : new ShapeGenerator(5))
System.out.println(c);
}
}
下面的類是Generator介面的另一個實現,負責生成Fibonacci數列:
class Fibonacci implements Generator<Integer> {
private int count = 0;
public Integer next() { return fib(count++); }
private int fib(int n) {
if(n < 2) return 1;
return fib(n-2) + fib(n-1);
}
public static void test(String[] args) {
Fibonacci gen = new Fibonacci();
for(int i = 0; i < 18; i++)
System.out.print(gen.next() + " ");
}
}
/* Output:
1 1 2 3 5 8 13 21 34 55 89 144 233 377 610 987 1597 2584
*///:~
下面編寫一個實現了Iterable的Fibonacci生成器,通過繼承來建立介面卡類:
class IterableFibonacci extends Fibonacci implements Iterable<Integer> {
private int n;
public IterableFibonacci(int count) { n = count; }
@Override
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 test(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
*///:~
4、泛型方法
4.1、槓桿利用型別引數推斷
首先是一個靜態方法:
class New1 { public static <K, V> Map<K, V> map(){ return new HashMap<K, V>(); } // 然後可以這樣建立一個Map: public static void test(String[] args){ Map<String, List<Cat>> catMap = New.map(); } }
可以發現,右邊的不用再按照以前的這種寫法了:
Map<String, List> catMap = new HashMap<String, List>();
左邊宣告部分的型別為右邊提供了一種推斷,使得編譯器可以直接創造出來具體的類了。不過,這種場景沒有宣告,直接使用New.map()是編譯不通過的,因為沒有像這裡左邊的可以推斷的依據了, 如下面的,加入f()是一個方法,需要傳入一個Map,如下的寫法是編譯不通過的:
f(New.map());如果確實還是想按照上面那樣使用,則可以考慮使用顯示型別說明了,在操作符與方法名直接插入尖括號顯示的指明型別,程式碼如下:
F(New.<Person, List>map());
不過這種方式很少使用。也就是說,在編寫非賦值語句時,才要這樣的說明,而使用不了槓桿利用型別推斷。
我們為了方便,可以使用同樣的方式建立其他的容器了,可惜JDK本身沒有提供這樣的類:
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 test(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(); } }
4.2、可變引數與泛型方法
可變引數也是可以使用泛型宣告型別的:
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 test(String[] args){ List<String> ls = makeList("Jay", "Mike"); } }
4.3、用於Generator的泛型方法
通過使用泛型方法,封裝更加抽象的方法,比如下面的fill(),然後在使用的時候才傳入需要使用的的具體物件:
class GenericGenerator{
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 class Chapter15_4_3 {
public static void main(String[] args){
Collection<Shape> shapes = GenericGenerator.fill(new ArrayList<Shape>(), new ShapeGenerator(), 2);
for(Shape a : shapes){
System.out.println(a);
}
}
}
4.4、一個通用的Generator
通過使用泛型類,我們更建立一個更加通用的生成器Generator。
class BasicGenerator<T> implements Generator<T> {
private Class<T> type;
public BasicGenerator(Class<T> type){
this.type = type;
}
@Override
public T next() {
try {
return type.newInstance();
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public static <T> Generator<T> create(Class<T> type){
return new BasicGenerator<T>(type);
}
}
由於使用了newInstance()方法,所以這裡生產的類必須要提供一個預設的無參建構函式。
下面試驗一下,建立一個物件,為了標示是新建立的物件,在類裡面儲存一個static的計數器,每建立一個物件就加1:
class CountObject {
private static long counter = 0;
private final long id = counter++;
public long id(){
return id;
}
public String toString(){
return "countObject" + id;
}
public static void test(String[] args){
Generator<CountObject> gen = BasicGenerator.create(CountObject.class);
for(int i=0; i<5; i++){
System.out.println(gen.next());
}
}
}
/*
test 輸入結果如下:
countObject0
countObject1
countObject2
countObject3
countObject4
*/
4.5、簡化元組的使用
我們可以發現之前建立的元組,在使用的時候都傳入了一長串具體的型別,通過槓桿利用型別推斷引數,我們其實可以直接省略掉那一長串具體的型別了,新增一個static方法,可以使該方法成為更通用的類庫的方法了:
class TupleTest2 {
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);
}
}
public class Chapter15_4_5 {
public static void main(String[] args){
// 根據左邊的型別自動判斷右邊的型別,無需手動建立時指明型別了
ThreeTuple<Cat, Integer, String> tt = TupleTest2.tuple(new Cat(), 1, "Jason");
System.out.println(tt);
}
}
4.6、一個Set實用工具
enum Watercolors { ZINC, LEMON_YELLOW, MEDIUM_YELLOW, DEEP_YELLOW, ORANGE, BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, BURNT_UMBER, PAYNES_GRAY, IVORY_BLACK } class WatercolorSets { public static void main(String[] args) { Set<Watercolors> set1 = EnumSet.range(Watercolors.BRILLIANT_RED, Watercolors.VIRIDIAN_HUE); Set<Watercolors> set2 = EnumSet.range(Watercolors.CERULEAN_BLUE_HUE, Watercolors.BURNT_UMBER); System.out.println("set1: " + set1); System.out.println("set2: " + set2); System.out.println("union(set1, set2): " + union(set1, set2)); Set<Watercolors> subset = intersection(set1, set2); System.out.println("intersection(set1, set2): " + subset); System.out.println("difference(set1, subset): " + difference(set1, subset)); System.out.println("difference(set2, subset): " + difference(set2, subset)); System.out.println("complement(set1, set2): " + complement(set1, set2)); } } /* Output: (Sample) set1: [BRILLIANT_RED, CRIMSON, MAGENTA, ROSE_MADDER, VIOLET, CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE] set2: [CERULEAN_BLUE_HUE, PHTHALO_BLUE, ULTRAMARINE, COBALT_BLUE_HUE, PERMANENT_GREEN, VIRIDIAN_HUE, SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, RAW_UMBER, BURNT_UMBER] union(set1, set2): [SAP_GREEN, ROSE_MADDER, YELLOW_OCHRE, PERMANENT_GREEN, BURNT_UMBER, COBALT_BLUE_HUE, VIOLET, BRILLIANT_RED, RAW_UMBER, ULTRAMARINE, BURNT_SIENNA, CRIMSON, CERULEAN_BLUE_HUE, PHTHALO_BLUE, MAGENTA, VIRIDIAN_HUE] intersection(set1, set2): [ULTRAMARINE, PERMANENT_GREEN, COBALT_BLUE_HUE, PHTHALO_BLUE, CERULEAN_BLUE_HUE, VIRIDIAN_HUE] difference(set1, subset): [ROSE_MADDER, CRIMSON, VIOLET, MAGENTA, BRILLIANT_RED] difference(set2, subset): [RAW_UMBER, SAP_GREEN, YELLOW_OCHRE, BURNT_SIENNA, BURNT_UMBER] complement(set1, set2): [SAP_GREEN, ROSE_MADDER, YELLOW_OCHRE, BURNT_UMBER, VIOLET, BRILLIANT_RED, RAW_UMBER, BURNT_SIENNA, CRIMSON, MAGENTA] *///:~
下面的示例使用Sets.difference() 打印出 java.util包中各種Collection類與Map類之間的方法差異:
class ContainerMethodDifferences { static Set<String> methodSet(Class<?> type) { Set<String> result = new TreeSet<String>(); for(Method m : type.getMethods()) result.add(m.getName()); return result; } static void interfaces(Class<?> type) { 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); static { object.add("clone"); } static void difference(Class<?> superset, Class<?> subset) { System.out.print(superset.getSimpleName() + " extends " + subset.getSimpleName() + ", adds: "); Set<String> comp = Sets.difference( methodSet(superset), methodSet(subset)); comp.removeAll(object); // Don't show 'Object' methods System.out.println(comp); interfaces(superset); } public static void test(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); } }5、匿名內部類
泛型方法還可以應用於內部類和匿名內部類,下面是使用匿名內部類實現Generator介面的例子:
class Customer {
private static long counter = 1;
private final long id = counter++;
// private 構造器,強制你使用Generator類的generator方法生成物件
private Customer() {}
public String toString() { return "Customer " + id; }
// A method to produce Generator objects:
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++;
private Teller() {}
public String toString() { return "Teller " + id; }
// A single Generator object:
public static Generator<Teller> generator =
new Generator<Teller>() {
public Teller next() { return new Teller(); }
};
}
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>();
Generators.fill(line, Customer.generator(), 15);
List<Teller> tellers = new ArrayList<Teller>();
Generators.fill(tellers, Teller.generator, 4);
for(Customer c : line)
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
*///:~
6、構建複雜模型
泛型的一個重要好處是能夠簡單而安全地建立複雜的模型,例如很容易的建立List元組:
class TupleList<A,B,C,D>
extends ArrayList<FourTuple<A,B,C,D>> {
public static void main(String[] args) {
TupleList<Circle, Square, String, Integer> tl =
new TupleList<Circle, Square, String, Integer>();
tl.add(TupleTest.h());
tl.add(TupleTest.h());
for(FourTuple<Circle,Square,String,Integer> i: tl)
System.out.println(i);
}
} /* Output: (75% match)
(Circle, [email protected], hi, 47)
(Circle, [email protected], hi, 47)
*///:~
下面一個例項展示使用泛型型別來構建複雜模型是多麼簡單的事情
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);
public Product next() {
return new Product(rand.nextInt(1000), "Test",
Math.round(rand.nextDouble() * 1000.0) + 0.99);
}
};
}
class Shelf extends ArrayList<Product> {
public Shelf(int nProducts) {
Generators.fill(this, Product.generator, nProducts);
}
}
class Aisle extends ArrayList<Shelf> {
public Aisle(int nShelves, int nProducts) {
for(int i = 0; i < nShelves; i++)
add(new Shelf(nProducts));
}
}
class CheckoutStand {}
class Office {}
public class Store extends ArrayList<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));
}
} /* Output:
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
140: Test, price: $530.99
...
*///:~
public class Chapter15_6 {
public static void main(String[] args){
TupleList.main(args);
}
}
7、擦除的神祕之處
看個奇怪的問題,考慮下面輸出的結果:
Class c1 = new ArrayList<String>().getClass(); Class c2 = new ArrayList<Integer>().getClass(); System.out.println(c1 == c2);
輸出的結果竟然是true。
下面我們用Class.getTypeParameters()方法返回TypeVariable物件陣列看個究竟:
System.out.println(Arrays.toString(c1.getTypeParameters())); System.out.println(Arrays.toString(c2.getTypeParameters()));
我們發現輸出結果為:
[E] [E]
這裡只是引數的佔位符,所以,在泛型程式碼內部,無法獲得任何有關泛型引數型別的資訊。你可以知道諸如型別引數標示符和泛型型別邊界這類資訊,但卻無法知道用來建立某個特定例項的實際的型別引數。Java中的泛型是使用擦除來實現的,所以在使用泛型的時候,任何具體的型別資訊都被擦除了,只知道當前使用的是一個物件。所以上面才會出現相等的情況。
7.1、C++的方式檢視下面的一段C++的泛型程式碼:
#include <iostream>
using namespace std;
template<class T> class Manipulator {
T obj;
public:
Manipulator(T x) { obj = x; }
void manipulate() { obj.f(); }
};
class HasF {
public:
void f() { cout << "HasF::f()" << endl; }
};
int main() {
HasF hf;
Manipulator<HasF> manipulator(hf);
manipulator.manipulate();
} /* Output:
HasF::f()
C++編寫的泛型,當模板被例項化時,模板程式碼知道其模板引數的型別,C++編譯器將進行檢查,如果泛型物件呼叫了一個當前例項化物件不存在的方法,則報一個編譯期錯誤。例如上面的manipulate裡面呼叫了obj.f(),因為例項化的HasF存在這個方法,所以不會報錯。
而Java是使用擦除實現泛型的,在沒有指定邊界的情況下,是不能在泛型類裡面直接呼叫泛型物件的方法的,如下面的例子:
public class Manipulator<T> {
private T obj;
public Manipulator(T x){
obj = x;
}
public void doSomething(){
obj.f(); // 編譯錯誤
}
}
通過沒有邊界的obj呼叫f(),編譯出錯了,下面指定邊界,讓其通過編譯:
public class Manipulator<T extends HasF> {
private T obj;
public Manipulator(T x){
obj = x;
}
public void doSomething(){
obj.f(); // 編譯錯誤
}
}
class HasF{
public void f(){
System.out.println("HasF.f();");
}
}
上面的例子,把泛型型別引數擦除到了HasF,就好像在類的宣告中用HasF替換了T一樣。
7.2、遷移相容性泛型是JDK1.5才出現的,所以為了相容,採用了擦除的方式實現。泛型型別只有在靜態型別檢查期間才出現,在此之後,程式中所有泛型型別都被擦除,替換為他們的非泛型上界。例如List將被擦除為List,而普通的型別變數在未指定邊界的情況下將被擦除為Object。
7.3、擦除的問題擦除使得現有的非泛型客戶端程式碼能夠在不改變的情況下繼續使用,直至客戶端準備好用泛型重寫這些程式碼。
但是擦除的代價也是顯著的,泛型不能用於顯式的引用執行時型別的操作中,如轉型,instanceof和new操作符,因為所有關於引數的型別資訊都丟失了。無論何時當你在編寫泛型程式碼時,必須時刻提醒自己,你只是看起來好像擁有有關引數的型別資訊而已,實際上,它只是一個Object。
當要使用@SuppressWarnings("unchecked") 關閉警告時,最好儘量地“聚焦”,這樣就不會因為過於寬泛地關閉警告,而導致意外的遮蔽掉真正的問題。
下面的Derived3的錯誤意味著編譯器期望得到一個原生基類,當你希望將引數型別不要僅僅當做Object處理時,需要付出額外努力來管理邊界。
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 {} // No warning
// class Derived3 extends GenericBase<?> {}
// Strange error:
// unexpected type found : ?
// required: class or interface without bounds
class ErasureAndInheritance {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Derived2 d2 = new Derived2();
Object obj = d2.get();
d2.set(obj); // Warning here!
}
} ///:~
7.4、邊界處的動作
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(); } }
上面的程式碼的set()方法會在編譯期接受檢查,而get()的時候直接取出了String型別,其實此處還是會進行轉型的,只不過是由編譯器自動插入的相當於插入了這樣的程式碼:(String)holder.get(),詳細的轉型處理可以編譯成位元組碼檢視。
8、擦除的補償正因為型別資訊被擦除了,所以和型別相關的程式碼都無法工作了,如下的:
class Erased<T> {
private static 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方法也沒法使用了額,為了在泛型類中能夠判斷型別,可以引入型別標籤:
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<String> ctc = new ClassTypeCapture<String>(String.class); System.out.println(ctc.f("art")); // true System.out.println(ctc.f(1)); // false } }
8.1、建立型別例項
我們怎麼在一個泛型類中建立泛型的物件呢,上面直接建立的方法也是編譯不通過的?我們可以使用泛型工廠的方式。可以儲存一個型別標籤,使用Class.newInstance()的方式,建立泛型的物件, 但是這種情況,傳入的型別標籤對應的類必須要有建構函式,所以不推薦這樣幹,下面說說顯示的工廠這種方法(限制其型別,使得只能接受實現了這個工廠的類):
首先來建立一個工廠介面:
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>{
@Override
public Integer create() {
return new Integer(0);
}
}
class Widget {
public static class WFactory implements Factory<Widget>{
@Override
public Widget create() {
return new Widget();
}
}
}
這樣子我們就可以建立泛型類中的泛型物件了,通過傳入上面的顯示工廠:
public class Chapter15_8_1 {
public static void main(String[] args){
new Foo<Integer>(new IntegerFactory());
new Foo<Widget>(new Widget.WFactory());
// TODO 模板方法設計模式
}
}
8.2、泛型陣列
從上面Erased的例子中可以看出,不能直接建立泛型陣列,一般使用ArrayList替代。
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> {}
但是可以按照編譯器喜歡的方式來定義一個引用,卻永遠都不能建立這個確切型別的陣列。
class ArrayOfGenericReference {
static Generic<Integer>[] gia;
}
不能建立這個確切型別的陣列
class ArrayOfGeneric { static final int SIZE = 100; static Generic<Integer>[] gia; @SuppressWarnings("unchecked") public static void main(String[] args) { gia = (Generic<Integer>[])new Object[SIZE]; // 編譯通過,執行報ClassCastException錯誤,因為陣列將跟蹤它們的實際型別,而這個型別是在陣列被建立時確定的。 // Runtime type is the raw (erased) type: gia = new Generic<Integer>[SIZE]; // 不能這樣建立,Cannot create a generic array of Generic<Integer> gia = (Generic<Integer>[])new Generic[SIZE]; // 成功建立泛型陣列的唯一方法就是建立一個被擦除型別的新陣列,然後對其轉型。 System.out.println(gia.getClass().getSimpleName()); // Generic[] gia[0] = new Generic<Integer>(); gia[1] = new Object(); // 錯誤:cannot convert from Object to Generic<Integer> gia[2] = new Generic<Double>(); // 錯誤:cannot convert from Generic<Double> to Generic<Integer> } }
下面是一個泛型陣列包裝器
class GenericArray<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
array = (T[])new Object[sz];
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) { return array[index]; }
/