Think in Java第四版 读书笔记9第15章 泛型
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章 泛型相关推荐
- Think in Java第四版 读书笔记10 第16章 数组
Think in Java第四版 读书笔记10 第16章 数组 数组和容器很像 但他们有一些差别 16.1 数组为什么特殊 数组与容器的区别主要在效率和存储类型 效率:数组是简单的线性序列 使得数组的 ...
- Think in Java第四版 读书笔记8第14章 类型信息(RTTI与反射)
Java如何在运行时识别对象和类的信息? 1.RTTI(Run-time type information) 它假定我们在编译时已经知道了所有类型 2.反射 它允许我们在运行时发现和使用类的信息 14 ...
- Think in Java第四版 读书笔记7第13章 字符串
本章内容 1.string的基本使用 2.string拼接符 + 3.Object方法toString 4.String的常用方法 5.String的格式化输出 6.正则表达式 13.1 不可变字符串 ...
- Think in Java第四版 读书笔记6第12章 异常处理
12.1 概念 异常可以将"在正常时候执行的代码"和"发生错误时的代码"相分离,达到结构清晰的目的. a.受检查异常checkedException 编译器强制 ...
- Think in Java第四版 读书笔记5第11章
第十一章 持有对象(主要讲容器类) 概要 通常程序中我们需要一些容器来存储对象或者对象的引用 在java中承担这一责任的是数组和容器类 数组VS容器类 数组存在一个缺陷:长度固定不够灵活 而容器类则没 ...
- Think in Java第四版 读书笔记3第七章第八章
第七章复用类 复用代码的方式 1组合 2继承 方式1组合 public class Box {String boxName;public Box(String s) {System.out.print ...
- Think In Java第四版读书笔记
02-一切都是对象 将一切都"看作"对象,操纵的标识符实际是指向一个对象的"句柄". 可将这一情形想象成用遥控板(句柄)操纵电视机(对象). String s; ...
- Think in Java第四版 读书笔记2
习题答案 http://greggordon.org/java/tij4/solutions.htm 第四章 控制流程(本节很简单,很多内容掠过) if else if else if while d ...
- Think in Java第四版 读书笔记1
第一章对象导论(Java的几个重要部分) 访问控制的目的: 1.权限控制 2.类创建者修改某些实现而不会影响类使用者 代码复用的方式: 1.继承 2.组合(composition UML中实心菱形+实 ...
最新文章
- 角色权限(Role)和系统权限(System)的几个澄清实验
- Android10.0 Binder通信原理(八)-Framework层分析
- 服务器发送消息技术,知识科普:IM聊天应用是如何将消息发送给对方的?(非技术篇)...
- 终于知道为什么NVIDIA的linux驱动这么烂了,一波官方文档告诉你:Linux GPU Driver Developer’s Guide
- influxDB框架 数据存储 TSM 数据操作等详解
- atitit.groovy 语法特性
- pg数据库update + select left join
- 使用 DISM 工具检查并修复 Windows 系统文件
- python tokenize_python – 滥用nltk的word_tokenize(已发送)的后果
- 聊聊什么是自动化测试,什么是自动化测试框架
- 使用插件实现ecplise js/jquery智能提示
- 打开 Excel 提示 “文件格式和扩展名不匹配,文件可能已损坏或不安全” 的解决办法
- SqlServer-IN写法(普通、存储过程)
- abp修改默认返回格式
- wan口设置已断开(服务器无响应),无线路由器wan口设置显示已断开
- Airship2:云和容器的声明性生命周期管理系统
- UnityEditor查找引用和批量替换资源工具
- 关于数值策划在使用Excel表时的一点想法
- MATLAB安装失败,MATLAB软件总是运行特别慢原因分析
- python爬虫案例——证券之星股票数据采集
热门文章
- java oxm_spring使用OXM进行对象XML映射解析
- easyui不同的jsp页面之间混乱_JSP+SSM+Mysql实现的图书馆预约占座管理系统
- Qt Quick QMl学习笔记 之图片浏览器
- JavaScript 真值和假值
- 查找某一字符串在目标字符串中所在的位置
- ASA 5.0/8.0/9.0 杂记
- 降低成本是永恒的追求(xamarin)
- Runtime.getRuntime().exec()调用外部程序
- uva 10891 - Game of Sum
- css鼠标经过table文字变色,有没有可能用css实现当table被鼠标hover的时候,table列变色?...