學習總結
本篇是對學習【Java編程思想 第 15 章 泛型】的學習總結。
一般的類和方法,只能使用具體的類型,要麼是基本類型,要麼是自定義的類。如果要編寫可以應用於多種類型的代碼,這種刻板的限制的束縛就會很大。
在面向對象編程語言中,多態算是一種泛化機制。例如,你可以將方法的參數設爲一個基類,那麼該方法就可以接受從這個基類導出的任意類型作爲參數。
自Java SE5以來,提出一個重大的變化:泛型的概念。泛型實現了參數化類型的概念。
泛型初識
泛型類
就是在類名後加上泛型參數,語法是<>
裏放上我們不確定的類型,通常用大寫字母表示,常用的有A T E R
,,,
public class Holder<T> {
private T a;
public Holder(T a) {
this.a = a;
}
public T getA() {
return a;
}
public void setA(T a) {
this.a = a;
}
}
泛型接口
泛型可以應用於接口。例如生成器(generator),這是一種專門負責創建對象的類。實際上,這是工廠模式的一種應用。
// 泛型接口
public interface Generator<T> {
T next();
}
//咖啡工廠
public class CoffeeGenerator implements Generator<Coffee> {
private Random rand = new Random(47);
//咖啡的子類
private Class[] types = {
Amerciano.class,
Breve.class,
Cappuccino.class,
Latte.class,
Mocha.class};
private int size = 0 ;
public CoffeeGenerator() {
}
public CoffeeGenerator(int size) {
this.size = size;
}
@Override
public Coffee next() {
try {
return (Coffee) types[rand.nextInt(types.length)].newInstance();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
當然我們也可以用適配器模式
來創建泛型接口。這裏就不貼代碼了。
泛型方法
要定義泛型方法,只需要將泛型參數置於返回值之前即可。另外,是否擁有泛型方法,與其是否是泛型類沒有關係。
// 泛型方法
public <T> void f(T t) {
System.out.println(t.getClass().getName());
}
元組
<>
裏不止放一個或者兩個“大寫字母”,可以放很多的。這被稱爲元組,可以存放不同類型的對象。
public class ThreeTuple<A, B, C> extends TwoTuple<A, B> {
public C c;
public ThreeTuple(A a, B b, C c) {
super(a, b);
this.c = c;
}
@Override
public String toString() {
return "ThreeTuple{" +
"a=" + first +
", c=" + c +
", second=" + second +
'}';
}
}
可變參數與泛型方法
泛型方法與可變參數列表能夠很好地共存。
public static <T> List<T> makeList(T...args) {
List<T> list = new ArrayList<>();
for ( T t: args ) {
list.add(t);
}
return list;
}
內部類與泛型
泛型還可應用於內部類和匿名內部類。
class Customer {
private static long counter = 0;
private long id = counter++;
private Customer(){}
@Override
public String toString() {
return "Customer{ id=" + id + "}";
}
public static Generator<Customer> generator() {
//lambda表達式
return () -> new Customer();
}
}
擦除
在泛型代碼內部,無法獲得任何有關泛型參數類型的信息。
ArrayList<String>
與ArrayList<Integer>
被看作是相同的類型。
Class.getTypeParameters()將返回一個TypeVariable對象數組,表示有泛型聲明中所聲明的參數類型。
所以說,ArrayList<String>
與ArrayList<Integer>
在運行時實際上是相同的類型。這兩種類型都被擦除爲它們的原生類型,即ArrayList
。
class Forb {}
class Fnork {}
class Quark<Q extends Forb> {}
class Particle<POSITION, MOMENTUM> {}
源代碼 | 擦除後的Type對象 |
---|---|
Forb |
[] |
List |
[E] |
List<Forb> |
[E] |
Map<Forb, Fnork> |
[K,V] |
Quark<? extends Forb> |
[Q] |
Particle<Long, Double> |
[POSITION, MOMENTUM] |
泛型類型只有在靜態類型檢查期間纔出現,在此之後,程序中的所有泛型類型都將被擦除,替換爲它們的非泛型上界。
List<T> ==> List
Person==>Object//普通的類型變量未指定泛型邊界時
泛型擦除意味着在運行時失去了參數的類型信息。
有趣的是,非泛型代碼和泛型代碼的字節碼文件是一樣的。
由於擦除丟失了參數的類型信息,任何在運行時需要知道確切類型信息的操作都將無法工作。
if( o instanceof T) {}
T var = new T();
T[] array = new T[size];
T[] arr = (T[])new Object[size];
爲此,引入了類型標籤,就可以轉而使用動態的isInstance。
public boolean f(Object obj) {
return c.isInstance(obj);
}
創建泛型實例和泛型數組
泛型實例
Java
中的解決方案是傳遞一個工廠對象,並使用它來創建新的實例。最便利的工廠對象就是Class對象。
try {
//使用該語法的類必須有一個無參的構造函數
return c.newInstance();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
對上述這個不好的地方的解決辦法就是使用顯示的工廠模式。
interface Factory<T>{
T create();
}
class Foo<T> {
private T t;
//F代表不確定的類型,<F extends Factory<T>>表示Factory<T>或者是其未知的子類型
public <F extends Factory<T>> Foo(F factory) {
t = factory.create();
}
}
class IntegerFactory implements Factory<Integer> {
@Override
public Integer create() {
return new Integer(0);
}
}
Foo<Integer> foo = new Foo<>(new IntegerFactory());
泛型數組
- 使用
ArrayList
創建擦除後的數組,然後對其轉型
?????攜帶類型標記創建數組(推薦)
public <T> GenericArrayWithTypeToken(Class<T> type, int size) {
array = (T[]) Array.newInstance(type, size);
}
邊界
邊界使得你可以用於泛型的參數上設置限制條件。
class ColoredDimension<T extends Dimension & HasColor> { }
泛型重用了extends
關鍵字,這裏它區別於繼承,表示某個類以及該類的子類。
類在前,接口在後。
通配符,extends,super
List<? extends Fruit> fruits = new ArrayList<>();
//編譯錯誤
//fruits.add(new Fruit());
//fruits.add(new Apple());
//fruits.add(new Orange());
//fruits.add(new Object());
fruits.add(null);
再看一個例子
public class Holder<T> {
private T t;
public Holder(){}
public Holder(T t) {
this.t = t;
}
public T getT() {
return t;
}
public void setT(T t) {
this.t = t;
}
public static void main(String[] args) {
Holder<Apple> apples = new Holder<>(new Apple());
Apple a = apples.getT();
apples.setT(a);
//不能裝換
//Holder<Fruit> fruits = apples;
//? extends Fruit意味着它可以是任意從Fruit繼承的類,編譯器無法驗證
//故添加的操作都將失敗
Holder<? extends Fruit> fruits = apples;
Fruit fruit = fruits.getT();
Apple aa = (Apple) fruits.getT();
try {
//會報錯
Orange orange = (Orange) fruits.getT();
} catch (Exception e) {
e.printStackTrace();
}
// 編譯錯誤
// fruits.setT(new Fruit());
// fruits.setT(new Apple());
// fruits.setT(new Orange());
// fruits.setT(new Object());
}
}
//能讀不能寫
List
List<? super Fruit> flist = new ArrayList<Apple>();
List
無界通配符
無界通配符<?>
看起來意味着“任何事物”,因此使用無界通配符好像等價使用原生類型。
問題
任何基本類型都不能作爲類型參數
一個類不能實現同一泛型接口的多種變體
由於擦除,重載方法將產生相同的類型簽名
自限定的類型
古怪的循環泛型
class GenericType<T>{ }
public class CuriouslyRecurringGeneric
extends GenericType<CuriouslyRecurringGeneric> {
}
我創建了一個新類,它繼承自一個泛型類型,這個泛型類型接受一個我的類的名字作爲參數
自限定
自限定將採用額外的步驟,強制泛型當做其自己的邊界參數來使用。
自限定限制只能強制作用於繼承關係。
class SelfBounded<T extends SelfBounded<T>>{
T element;
SelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() {
return element;
}
}
class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {}
class C extends SelfBounded<C> {
C setAndGet(C arg) {
set(arg);
return get();
}
}
class D {}
//class E extends SelfBounded<D> {}
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());
}
}
SelfBounded<T extends SelfBounded<T>>
要求只接受從該泛型繼承的類型作爲泛型參數。
參數協變
自限定類型的價值在於它們可以產生協辦類型參數——方法參數類型會隨子類而變化。
class Base{}
class Derived extends Base{}
interface GenericsGetter<T extends GenericsGetter<T>> {
T get() ;
}
interface Getter extends GenericsGetter<Getter> {}
public class GenericAndReturnTypes {
void test(Getter g) {
Getter result = g.get();
GenericsGetter gg = g.get();
}
}
我們看到繼承自自限定類型的類,返回類型是導出類的類型。同樣的,方法中參數也是接受導出類的類型。
interface GenericsGetter<T extends GenericsGetter<T>> {
T get() ;
}
interface Getter extends GenericsGetter<Getter> {}
public class GenericAndReturnTypes {
void test(Getter g) {
Getter result = g.get();
GenericsGetter gg = g.get();
}
}
動態類型安全
checkedCollection
checkedList
checkedMap
checkedSet
checkedSortedMap
checkedSortedSet()
這些方法每一個都會將你希望動態檢查的容器當做第一個參數接受,並希望你強制要求的類型作爲第二個參數接受。
public class CheckedList {
static void oldStyleMethod(List pro) {
pro.add(new Cat());
}
public static void main(String[] args) {
List<Dog> dogs1 = new ArrayList<>();
//這句不合法的語句居然逃脫了編譯器的檢查
oldStyleMethod(dogs1);
List<Dog> dogs2 = Collections.checkedList(new ArrayList<>(), Dog.class);
try{
oldStyleMethod(dogs2);
} catch (ClassCastException cce) {
cce.printStackTrace();
}
List<Pet> pets = Collections.checkedList(new ArrayList<>(), Pet.class);
pets.add(new Dog());
pets.add(new Dog());
}
}
混型
最基本的概念就是混合多個類的能力。(根本不理解)
潛在類型機制
相信看代碼就懂了。
C++版本
class Dog{
public:
void speak(){}
void sit(){}
void reproduce(){}
};
class Robot{
public:
void speak(){}
void sit(){}
void reproduce(){}
}
template<class T> void perform(T anything) {
anything.speak();
anything.sit();
}
int main() {
Dog d = new Dog();
Robot r = new Robot();
perform(d);
perform(r);
return 0;
}
Python版本
# coding:UTF-8
class Dog:
def speak(self):
print "Arf"
def sit(self):
print "Sitting"
def reproduce(self):
pass
class Robot:
def speak(self):
print "Click"
def sit(self):
print "Clank"
def reproduce(self):
pass
def perform(anything):
anything.speak();
anything.sit();
dog = Dog();
robot = Robot();
perform(dog);
perform(robot);
可惜的是Java的語法不允許這樣寫。你可能想到用多態或者簡單的泛型來寫,可以到達相同的效果,是的。
Java替代方案-多態
interface Performs {
void speak();
void sit();
}
static void perform(Performs p) {
p.speak();
p.sit();
}
Java替代方案-泛型
static <P extends Performs>void perform(P p) {
p.speak();
p.sit();
}
Java替代方案-反射
public static void perform(Object speaker) {
Class<?> cl = speaker.getClass();
try {
//指定公共成員方法
Method method1 = cl.getMethod("speak");
method1.invoke(speaker);
} catch (Exception e) {
e.printStackTrace();
}
try {
Method method2 = cl.getMethod("sit");
method2.invoke(speaker);
} catch (Exception e) {
e.printStackTrace();
}
}