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); // ErrorHolder3<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: finalSystem.out.println(g());}
} /** Output: (80% match) (hi, 47) (Amphibian@1f6a7b9, hi, 47)*/// :~

在上述例子中 返回时的new语句似乎有点烦,后面会将他优化


15.2.2一个堆栈类(使用泛型实现自定义Stack)

//不使用LinkedList 而使用自定义的内部链式存储机制来实现stackpublic 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) {// 构造函数 参数*2this.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> {// 存储特定类型对象的容器 内部包含一个ArrayListprivate ArrayList<T> storage = new ArrayList<T>();private Random rand = new Random(47);public void add(T item) {// 新增itemstorage.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 {//随机返回一种Coffeereturn (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 implementedthrow 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个数 返回1return 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 implementedthrow 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 Tstatic <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 Fibonaccipublic 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并bpublic static <T> Set<T> union(Set<T> a, Set<T> b) {Set<T> result = new HashSet<T>(a);result.addAll(b);return result;}//a交bpublic 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交Bpublic 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) {//将接口方法存储在ArrayListSystem.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之间的 Productpublic 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的ArrayListGenerators.fill(this, Product.generator, nProducts);}
}class Aisle extends ArrayList<Shelf> {//Aisle是一个Shelf数组public Aisle(int nShelves, int nProducts) {//创建长度为nShelves的ArrayList<Shelf> 每一个Shelf元素中填充了nProducts个Productfor (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 Tpublic 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的方法fprivate 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_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic void set(java.lang.Object);Code:0: aload_01: aload_12: putfield      #2                  // Field obj:Ljava/lang/Object;5: returnpublic java.lang.Object get();Code:0: aload_01: getfield      #2                  // Field obj:Ljava/lang/Object;4: areturnpublic static void main(java.lang.String[]);Code:0: new           #3                  // class SimpleHolder3: dup4: invokespecial #4                  // Method "<init>":()V7: astore_18: aload_19: ldc           #5                  // String Item11: invokevirtual #6                  // Method set:(Ljava/lang/Object;)V14: aload_115: invokevirtual #7                  // Method get:()Ljava/lang/Object;18: checkcast     #8                  // class java/lang/String21: astore_222: 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.javaC:\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_01: invokespecial #1                  // Method java/lang/Object."<init>":()V4: returnpublic void set(T);Code:0: aload_01: aload_12: putfield      #2                  // Field obj:Ljava/lang/Object;5: returnpublic T get();Code:0: aload_01: getfield      #2                  // Field obj:Ljava/lang/Object;4: areturnpublic static void main(java.lang.String[]);Code:0: new           #3                  // class generics/GenericHolder3: dup4: invokespecial #4                  // Method "<init>":()V7: astore_18: aload_19: ldc           #5                  // String Item11: invokevirtual #6                  // Method set:(Ljava/lang/Object;)V14: aload_115: invokevirtual #7                  // Method get:()Ljava/lang/Object;18: checkcast     #8                  // class java/lang/String21: astore_222: return
}*/

可以看到他们的反编译结果是一样的,这也一定程度解释了java的泛型是如何做到兼容性的。Java的泛型更多的由编译器确保 而编译结果看起来就像是没有泛型一样。(个人观点,还是没理解书里说的边界是什么)


15.8 擦除的补偿

由于泛型的擦除 所以在泛型代码中某些操作能力会被丢失。

public class Erased<T> {private final int SIZE = 100;public static void f(Object arg) {if(arg instanceof T) {}          // ErrorT var = new T();                 // ErrorT[] array = new T[SIZE];         // ErrorT[] 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的filedT* 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 实现HasColorT 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和WeightT 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可以存储在SolidSolid<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> extendsColoredDimension2<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> {// 能力限制为XRayVisionSuperSleuth(POWER power) {super(power);}void see() {power.seeThroughWalls();}
}class CanineHero<POWER extends SuperHearing & SuperSmell> extendsSuperHero<POWER> {// 能力限制为SuperHearing和SuperSmellCanineHero(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) {//返回类型限制为SuperHearinghero.getPower().hearSubtleNoises();}static <POWER extends SuperHearing & SuperSmell> void superFind(SuperHero<POWER> hero) {//返回类型限制为SuperHearing & SuperSmellhero.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(); // OKfruit[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; // OKFruit 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);//返回一个AppleFruit f = readExact(fruit);//返回一个Fruitf = 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>();//泛型确定为FruitFruit 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赋值给FruitFruit 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;}//子类(上界)通配符 适用于getstatic <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;}//父类(下界)通配符 适用于setstatic <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 ObjectLong 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使用了无界通配符 并且调用了没有使用通配符的方法f1f1(holder); // Call with captured type}@SuppressWarnings("unchecked")public static void main(String[] args) {Holder raw = new Holder<Integer>(1);f1(raw); // Produces warningsf2(raw); // No warningsHolder rawBasic = new Holder();rawBasic.set(new Object()); // Warningf2(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 Employeeimplements 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 castList<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 conversionList<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) {//注意返回值 返回的是泛型类型Telement = 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 OKclass 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://返回类型允许修改(修改为范围更小的类型)@OverrideDerived 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-boundedvoid 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 listList<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!");elseresultCollector.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诞生很久之后添加的新功能,其兼容性使得程序的升级变得相当复杂,而且,本章也讨论了泛型的各种缺陷,因此引入泛型究竟是好是坏,这个问题值得深思。

Think in Java第四版 读书笔记9第15章 泛型相关推荐

  1. Think in Java第四版 读书笔记10 第16章 数组

    Think in Java第四版 读书笔记10 第16章 数组 数组和容器很像 但他们有一些差别 16.1 数组为什么特殊 数组与容器的区别主要在效率和存储类型 效率:数组是简单的线性序列 使得数组的 ...

  2. Think in Java第四版 读书笔记8第14章 类型信息(RTTI与反射)

    Java如何在运行时识别对象和类的信息? 1.RTTI(Run-time type information) 它假定我们在编译时已经知道了所有类型 2.反射 它允许我们在运行时发现和使用类的信息 14 ...

  3. Think in Java第四版 读书笔记7第13章 字符串

    本章内容 1.string的基本使用 2.string拼接符 + 3.Object方法toString 4.String的常用方法 5.String的格式化输出 6.正则表达式 13.1 不可变字符串 ...

  4. Think in Java第四版 读书笔记6第12章 异常处理

    12.1 概念 异常可以将"在正常时候执行的代码"和"发生错误时的代码"相分离,达到结构清晰的目的. a.受检查异常checkedException 编译器强制 ...

  5. Think in Java第四版 读书笔记5第11章

    第十一章 持有对象(主要讲容器类) 概要 通常程序中我们需要一些容器来存储对象或者对象的引用 在java中承担这一责任的是数组和容器类 数组VS容器类 数组存在一个缺陷:长度固定不够灵活 而容器类则没 ...

  6. Think in Java第四版 读书笔记3第七章第八章

    第七章复用类 复用代码的方式 1组合 2继承 方式1组合 public class Box {String boxName;public Box(String s) {System.out.print ...

  7. Think In Java第四版读书笔记

    02-一切都是对象 将一切都"看作"对象,操纵的标识符实际是指向一个对象的"句柄". 可将这一情形想象成用遥控板(句柄)操纵电视机(对象). String s; ...

  8. Think in Java第四版 读书笔记2

    习题答案 http://greggordon.org/java/tij4/solutions.htm 第四章 控制流程(本节很简单,很多内容掠过) if else if else if while d ...

  9. Think in Java第四版 读书笔记1

    第一章对象导论(Java的几个重要部分) 访问控制的目的: 1.权限控制 2.类创建者修改某些实现而不会影响类使用者 代码复用的方式: 1.继承 2.组合(composition UML中实心菱形+实 ...

最新文章

  1. 角色权限(Role)和系统权限(System)的几个澄清实验
  2. Android10.0 Binder通信原理(八)-Framework层分析
  3. 服务器发送消息技术,知识科普:IM聊天应用是如何将消息发送给对方的?(非技术篇)...
  4. 终于知道为什么NVIDIA的linux驱动这么烂了,一波官方文档告诉你:Linux GPU Driver Developer’s Guide
  5. influxDB框架 数据存储 TSM 数据操作等详解
  6. atitit.groovy 语法特性
  7. pg数据库update + select left join
  8. 使用 DISM 工具检查并修复 Windows 系统文件
  9. python tokenize_python – 滥用nltk的word_tokenize(已发送)的后果
  10. 聊聊什么是自动化测试,什么是自动化测试框架
  11. 使用插件实现ecplise js/jquery智能提示
  12. 打开 Excel 提示 “文件格式和扩展名不匹配,文件可能已损坏或不安全” 的解决办法
  13. SqlServer-IN写法(普通、存储过程)
  14. abp修改默认返回格式
  15. wan口设置已断开(服务器无响应),无线路由器wan口设置显示已断开
  16. Airship2:云和容器的声明性生命周期管理系统
  17. UnityEditor查找引用和批量替换资源工具
  18. 关于数值策划在使用Excel表时的一点想法
  19. MATLAB安装失败,MATLAB软件总是运行特别慢原因分析
  20. python爬虫案例——证券之星股票数据采集

热门文章

  1. java oxm_spring使用OXM进行对象XML映射解析
  2. easyui不同的jsp页面之间混乱_JSP+SSM+Mysql实现的图书馆预约占座管理系统
  3. Qt Quick QMl学习笔记 之图片浏览器
  4. JavaScript 真值和假值
  5. 查找某一字符串在目标字符串中所在的位置
  6. ASA 5.0/8.0/9.0 杂记
  7. 降低成本是永恒的追求(xamarin)
  8. Runtime.getRuntime().exec()调用外部程序
  9. uva 10891 - Game of Sum
  10. css鼠标经过table文字变色,有没有可能用css实现当table被鼠标hover的时候,table列变色?...