泛型的引入的原因:主要解決容器類存放數(shù)據(jù)的靈活性.
泛型的主要目的之一:用來制定容器類可以存放什么類型對象,而且有編譯器來保證類型的正確性.[說明在編譯時就能確定]核心概念:告訴編譯器想使用什么類型,然后編譯器幫你處理一切細(xì)節(jié).
15.2.1 用泛型返回多個對象(一個元組類庫)
package tinking_in_java.generics;
/**
* Created by leon on 17-12-18.
*/
public class Holder<T> {
private T t;
public Holder(T t) {
this.t = t;
}
public void setT(T t) {
this.t = t;
}
public T getT() {
return t;
}
package tinking_in_java.generics;
/**
* Created by leon on 17-12-18.
*/
public class Tuple<A, B> {
public final A a;
public final B b;
public Tuple(A a, B b) {
this.a = a;
this.b = b;
}
@Override
public String toString() {
return "" + a + b;
}
}
/**
* 三元元組
*
* @param <A>
* @param <B>
* @param <C>
*/
class ThreeTuple<A, B, C> extends Tuple<A, B> {
public final C c;
public ThreeTuple(A a, B b, C c) {
super(a, b);
this.c = c;
}
@Override
public String toString() {
return "A= " + a + " B= " + b + " C= " + c;
}
}
class FourTuple<A, B, C, D> extends ThreeTuple<A, B, C> {
public final D d;
public FourTuple(A a, B b, C c, D d) {
super(a, b, c);
this.d = d;
}
}
15.2.2泛型作為 LinkedStack<T>
package tinking_in_java.generics;
/**
* Created by leon on 17-12-18.
*/
public class LinkedStack<T> {
class Node<U> {
private U value;
private Node<U> next;
public Node() {
value = null;
next = null;
}
public Node(U u, Node<U> nextNode) {
value = u;
next = nextNode;
}
public boolean isEnd() {
return value == null && next == null;
}
}
private Node<T> top = new Node<T>();
public void push(T t) {
top = new Node<>(t, top);
}
public T pop() {
if (top.isEnd()) return null;
T result = top.value;
top = top.next;
return result;
}
public static void main(String[] args) {
LinkedStack<String> myStack = new LinkedStack<>();
myStack.push("abc");
myStack.push("skjhklh");
myStack.push("asdadf");
String myStr = null;
while ((myStr = myStack.pop()) != null) {
System.out.println(myStr);
}
}
}
15.4泛型方法
泛型方法和是否是泛型類沒有關(guān)系.以下是一條基本原則: 無論何時,只要你能做到,盡量使用泛型方法,也就是說如果泛型方法能代替泛型類,就應(yīng)該采用泛型方法,因為他可以把事情更清楚明白.static方法無法使用泛型類的類型參數(shù),如果需要使static 方法使用泛型參數(shù)必須申明為泛型方法.
泛型方法定義:在返回值前加<T> .
杠桿利用,類型參數(shù)推斷.
可以做一個泛型推斷生成器工具類.(但是這只對賦值操作有效,如果把他直接傳入函數(shù)參數(shù)時是無效的,傳入?yún)?shù)時候,編譯器認(rèn)為執(zhí)行泛型方法后,返回值賦給一個Object類型變量)
package tinking_in_java.generics;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
/**
* Created by leon on 17-12-18.
*/
public class NewUtils {
public static <V, K> HashMap<V, K> map() {
return new HashMap<>();
}
public static <T> List<T> list() {
return new ArrayList<T>();
}
public static <T> LinkedList<T> linkedList() {
return new LinkedList<T>();
}
public static <T> Queue<T> quene() {
return new LinkedList<>();
}
public static void main(String[] args) {
HashMap<String, List<String>> myHash = NewUtils.map();
}
}
可變參數(shù)與泛型方法
泛型方法可以與可變參數(shù)很好的共存
public static <T> List<T> makeList(T... args) {
ArrayList<T> list = new ArrayList<T>();
for (T arg : args) {
list.add(arg);
}
return list;
}
…
public static void main(String[] args) {
List<String> makeList = makeList("a", "abc", "adalkj");
System.out.println(makeList.toString());
}
15.5泛型構(gòu)建匿名內(nèi)部類
15.6 構(gòu)建復(fù)雜模型
15.7擦除的神秘.
在泛型代碼內(nèi)部,無法獲取獲取任何泛型參數(shù)類型信息.
因此List<String> ,List<Integer>在運行時事實上是相同類型的,這兩種形式都被摖除成”原生”類型,即List.所以為了確定泛型的參數(shù)類型,所以必須使用限定邊界.
邊界 <T extends AAA>,這個邊界說明 T 必須具有AAA,或者T是從AAA導(dǎo)出的類型(AAA是T的基類).泛型類型將摖除到他的第一個邊界(他可能會有多個邊界).
class Manipupolor<T extends Frob>
private T obj;
public Manipupolor(T t) {
obj = t;
}
public void manipuplor() {
obj.f();
}
}
這摖除之后相當(dāng)于
class Manipupolor {
private Frob obj;
public Manipupolor(Frob t) {
obj = t;
}
public void manipuplor() {
obj.f();
}
}
只有需要希望使用這個類型比某個具體類型(以及他所有子類)更加”泛化”的時候,也就是他能跨多個類工作時,泛型才有幫助.
泛型類型只有在靜態(tài)類型檢測期間才出現(xiàn),在此之后,程序中的所有泛型類型都會被摖除,替換成他們的非泛型上界.諸如List<T> ==>摖除成 List,而普通類型變量在未指定邊界情況下將摖除成Object. java采用摖除的原因是,要使得泛型能向后兼容.必須采取折中方案.
因為泛型擦除在方法體中移除了類型信息,所有在運行時的問題就是邊界:對象進入和離開的方法地點.
通過泛型創(chuàng)建類型實例.java中要創(chuàng)建通用實例,是做不到的,只能通過工廠方法預(yù)先針對不同類型創(chuàng)建.
15.8.2泛型數(shù)組
不能直接創(chuàng)建泛型數(shù)組(例如 new T[]),一般的解決方法是用arrayList<T> 創(chuàng)建,或者創(chuàng)建(T[])new Object[],(創(chuàng)建object數(shù)組,然后轉(zhuǎn)型T[]).因為有了擦除,數(shù)組在運行時只能是Object[],那么在編譯的時候如果強行轉(zhuǎn)成T[], 編譯期該數(shù)組的實際類型將會丟失,那么編譯器會錯過錯誤檢查,所以最好在集合內(nèi)部使用Object[],在使用時候再進行轉(zhuǎn)型T.其實ArrayList 就是內(nèi)部的存儲就是這么做得.
15.9邊界
extends關(guān)鍵字在泛型上下文環(huán)境中和普通情況下的意義完全不同.
1.通過在<T extends xxx> 來確定邊界
2.還可以在 class< > extends 基類來確定邊界.(好處是可以省去重復(fù)代碼)
例如:
第一種 :
BasicBound.java
package tinking_in_java.generics;
import java.awt.Color;
/**
* Created by leon on 17-12-19.
*/
interface HashColor {
Color getColor();
}
class Colored<T extends HashColor> {
T item;
public Colored(T t) {
this.item = t;
}
T getItem() {
return item;
}
public Color color() {
return item.getColor();
}
}
class Dimension {
public int x, y, z;
}
//extends 多重繼承關(guān)系 ,必須class 在前,interface在后
class DimensionColor<T extends Dimension & HashColor> {
T item;
public DimensionColor(T t) {
item = t;
}
public Color color() {
return item.getColor();
}
public int getX() {
return item.x;
}
public int getY() {
return item.y;
}
public int getZ() {
return item.z;
}
}
interface Weight {
int getWeight();
}
class Solid<T extends Dimension & HashColor & Weight> {
T item;
public Solid(T t) {
item = t;
}
T getItem() {
return item;
}
Color color() {
return item.getColor();
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
int weight() {
return item.getWeight();
}
}
class Bound extends Dimension implements HashColor, Weight {
@Override
public Color getColor() {
return Color.RED;
}
@Override
public int getWeight() {
return 0;
}
}
public class BasicBound {
public static void main(String[] args) {
Solid<Bound> solid = new Solid<>(new Bound());
System.out.println("" + solid.getX() + solid.getY() + solid.color() + solid.weight());
}
}
第二種,采用繼承基類的形式:
package tinking_in_java.generics;
import java.awt.Color;
/**
* Created by leon on 17-12-19.
*/
class Hold<T> {
T item;
Hold(T t) {
item = t;
}
T getItem() {
return item;
}
}
class Colors2<T extends HashColor> extends Hold<T> {
Colors2(T t) {
super(t);
}
Color color() {
return item.getColor();
}
}
class DimensionColor2<T extends Dimension & HashColor> extends Colors2<T> {
DimensionColor2(T t) {
super(t);
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
}
class Solid2<T extends Dimension & HashColor & Weight> extends DimensionColor2<T> {
Solid2(T t) {
super(t);
}
int weight() {return item.getWeight(); }
}
public class InheritBound {
public static void main(String[] args) {
Solid2<Bound> solid2 = new Solid2<>(new Bound());
System.out.println("" + solid2.getX() + solid2.getY() + solid2.color() + solid2.weight());
}
}
15.10通配符類型:
協(xié)變<? Extends MyClass> :具有任何從MyClass 繼承類的通配符 Pair<? extends Emplee> myPair=new Pair<Mannager>(Bob,Linar) .只可以讀取數(shù)據(jù),不能通過myPair 往里面再次添加數(shù)據(jù).
15.10.2 逆變(使用超類通配符)
<? Super MyClass>甚至可以用<? Super T> 有某個特定類任一的基類來界定.
解讀協(xié)變和逆變:
引入的原因是解決 單一泛型類型的制約.
對于協(xié)變List<? extends Number > numList;
從語義上分析:
修改:因為? 都是繼承Number ,所以按理說 既可以往numList 添加 Integer,也可以往里面添加Float,也可以添加Double,Long.但是這樣就會出現(xiàn)問題,編譯器無法知道究竟往里面添加了什么類型的數(shù)據(jù).[因此為了保證明確性,規(guī)定不能這樣做]
讀取:因為都是繼承Number ,所以這里面的數(shù)據(jù)必然能讀取到Number,因為里面的數(shù)據(jù)要么是Number,要么是Number的子類數(shù)據(jù).[所以允許讀取]
對于逆變List<? Super Integer>
讀取 :按照字面意思 因為容器里面 的數(shù)據(jù)都是 Integer的超類,那么:既可以讀取Integer,也可以讀取Number,還可以讀取Object .這就造成讀取數(shù)據(jù)的不確定性[所以禁止讀取]
修改 :為了保證數(shù)據(jù)的確定性,必須往里面寫入Integer 或者Integer的子類(因為Integer的子類也屬于Integer).
適用場景:生產(chǎn)者(獲取數(shù)據(jù))適用Extends ,消費者(裝入數(shù)據(jù))適用 Super.
例如:
// Collections.java
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
包含泛型相同名字,參數(shù)類型相似的多個接口不能同時被一個類繼承,因為擦除的作用,他們會被當(dāng)做相同的接口.一個類中如果傳有多個泛型T,V ,不能把不同泛型當(dāng)做不同類型數(shù)據(jù)看待進行函數(shù)重載,因為擦除之后他們的類型都是會變成Object.
15.12自限定的類型.
class SelfBounded<T extends SelfBounded<T>>{…}
這就話的意思是 "SelfBounded類接受泛型參數(shù)T,而T由一個邊界類限定,這個邊界就是擁有T作為其參數(shù)的SelfBounded".這個的好處在與,能使得 導(dǎo)出類用自己作為參數(shù)以及 返回類型.但是這在編譯器中并不是強制要求這么做的,一般來說需要要求其他每個用這種方式的人遵循這個原則.
package tinking_in_java.generics;
/**
* Created by leon on 17-12-19.
*/
/**
* 自限定 要求繼承者都必須傳入?yún)?shù)和 返回參數(shù)有相同
*
* @param <T>
*/
class SelfBounded<T extends SelfBounded<T>> {
T elment;
SelfBounded<T> set(T t) {
elment = t;
return this;
}
public T get() {
return elment;
}
}
/**
* 屬于正確理解
*/
class A extends SelfBounded<A> {
}
/**
* 不屬于正確理解,但是編譯器不會報錯
*/
class B extends SelfBounded<A> {
}
/**
* 屬于正確理解,參數(shù)和返回參數(shù)都是C類型
*/
class C extends SelfBounded<C> {
C setAndGet(C c) {
set(c);
return get();
}
}
public class SelfBounding {
}