Think in Java第四版 读书笔记9第15章 泛型
泛型:适用于很多很多的类型
与其他语言相比 Java的泛型可能有许多局限 但是它还是有很多优点的。
本章介绍java泛型的局限和优势以及java泛型如何发展成现在这个样子的。
Java的语言设计灵感来自C++,虽然我们学习Java时不需要参考C++,但是有时与C++进行比较可以加深理解
泛型就是一个例子
之所以将Java的泛型与C++进行比较原因有二
1.比较后会理解泛型的基础,同时你会了解Java泛型的局限性以及为什么会有这些局限性。(理解某个技术不能做什么 才能更好地做到所能做到的,不必浪费时间在死胡同乱转–Think in JAVA作者讲的很精辟!)
2.人们对C++模板有一种误解,这种误解可能导致我们在理解泛型的意图产生偏差。
泛型最常见的使用是在容器类上,让我们从最简单的开始吧
例子:存放单个指定类型对象的容器
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 h3 = new Holder3(new Automobile());
Automobile a = h3.get(); // No cast needed
// h3.set("Not an Automobile"); // Error
// h3.set(1); // Error
Holder3 holder3 = new Holder3("abc");
String string = holder3.get();
}
} ///:~
像这样 在创建Holder3对象时必须指定存储的类型,跟存储Object相比,泛型在编译期就可以确定存放和取出的对象类型,
泛型的一个核心作用是告诉编译器使用的类型
由于return只能返回一个对象,那么要实现返回多个对象的需求 如何实现呢?这里就可以使用元组的概念了。
我们可以创建一个对象A 该对象A持有需要返回的多个其他对象,返回那一个对象A,就可以实现返回多个对象的效果了,这也是元组的概念。另外,如果元组只允许读取不允许重新赋值或新增对象(只读)就可以叫做数据传送对象/信使
元组可以是任意长度 任意类型的,我们举一个2维元组的例子
public class TwoTuple {
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 extends TwoTuple {
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 f() {
// Autoboxing converts the int to Integer:
return new TwoTuple("hi", 47);
}
static ThreeTuple g() {
return new ThreeTuple(new Amphibian(),
"hi", 47);
}
public static void main(String[] args) {
TwoTuple ttsi = f();
System.out.println(ttsi);
// ttsi.first = "there"; // Compile error: final
System.out.println(g());
}
} /*
* Output: (80% match) (hi, 47) (Amphibian@1f6a7b9, hi, 47)
*/// :~
在上述例子中 返回时的new语句似乎有点烦,后面会将他优化
15.2.2一个堆栈类(使用泛型实现自定义Stack)
//不使用LinkedList 而使用自定义的内部链式存储机制来实现stack
public class LinkedStack {
private static class Node {// 模拟链表节点
U item;// 当前节点内容
Node next;// 链表的下一个节点
Node() {// 默认构造函数 当前内容与下一节点都为null 用于初始化末端哨兵
item = null;
next = null;
}
Node(U item, Node next) {// 构造函数 参数*2
this.item = item;
this.next = next;
}
boolean end() {// 链表的当前和下一个节点均为空 则链表为空
return item == null && next == null;
}
}
private Node top = new Node(); // End sentinel末端哨兵 初始化为空节点
// 该节点一直在栈顶
public void push(T item) {// 入栈操作
top = new Node(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 lss = new LinkedStack();
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
*/// :~
public class RandomList {
// 存储特定类型对象的容器 内部包含一个ArrayList
private ArrayList storage = new ArrayList();
private Random rand = new Random(47);
public void add(T item) {// 新增item
storage.add(item);
}
public T select() {// 随机取出一个元素
return storage.get(rand.nextInt(storage.size()));
}
public static void main(String[] args) {
RandomList rs = new RandomList();
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
*/// :~
生成器(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, Iterable {
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 {
//随机返回一种Coffee
return (Coffee) types[rand.nextInt(types.length)].newInstance();
// Report programmer errors at run time:
} catch (Exception e) {
throw new RuntimeException(e);
}
}
//自定义迭代器
class CoffeeIterator implements Iterator {
int count = size;
public boolean hasNext() {
return count > 0;
}
public Coffee next() {
count--;
return CoffeeGenerator.this.next();
}
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
//实现Iterable的方法
public Iterator 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 {
private int count = 0;
public Integer next() {
return fib(count++);
}
private int fib(int n) {//当n比较大时 递归效率很低
if (n < 2){//第0 和第1个数 返回1
return 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 {
private int n;
public IterableFibonacci(int count) {//参数用于判断是否遍历结束
n = count;
}
public Iterator iterator() {
return new Iterator() {
public boolean hasNext() {
return n > 0;
}
public Integer next() {
n--;
return IterableFibonacci.this.next();
}
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void 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
*/// :~
到目前位置 我们使用的泛型都是作用在类上的 泛型同样可以作用于方法,即泛型方法。一个类是否有泛型方法与是否是泛型类没有关系
如果使用泛型方法就可以达到目的 那么使用泛型方法而不是使用泛型类(使整个类泛型化)。这样的结构更清晰。
另外需要注意静态方法需要使用泛型能力只能使其成为泛型方法(泛型类的泛型无法使用在静态方法上)
原因:
public class Atest {
//static void testA(T t){}//编译报错:Cannot make a static reference to the non-static type T
static void testA(K k){}//泛型方法
}
泛型方法:在返回值前加上泛型参数列表
例子:泛型方法的定义
public class GenericMethods {
public 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方法时 我们可以传入不同的类型参数,看起来就像该方法被重载了无数次,可以接受任意类型参数
传入基本类型时 自动装箱机制会起到作用,将基本类型转换成指定的包装类类型
虽然编译器可以做一些类型推断 但是仅限于有限的情况,如果比较复杂,就不行了 比如
Map
我们就需要重复写2次冗长的参数类型
有没有方法避免这个呢?
我们可以写一个工具类来生成一些容器
import java.util.*;
public class New {
public static Map map() {
return new HashMap();
}
public static List list() {
return new ArrayList();
}
public static LinkedList lList() {
return new LinkedList();
}
public static Set set() {
return new HashSet();
}
public static Queue queue() {
return new LinkedList();
}
// Examples:
public static void main(String[] args) {
Map> sls = New.map();
List ls = New.list();
LinkedList lls = New.lList();
Set ss = New.set();
Queue qs = New.queue();
}
} // /:~
有了上述的工具类 我们的声明就简单了
Map> coffeeMap1 = New.map();
即可
看起来我们的工具类似乎起到一定的简化作用 但是真的这样吗,我们的初始目的是简化类型的声明,但是其他人在阅读代码时 还需要阅读New类的作用,这似乎与不使用new类时的效率不相上下。
从上面我们也可以看到 类型推断只在赋值时起作用,其他时候并不起作用。
如果你将泛型方法的返回值作为参数传递给另一个方法 类型推断将会失效。
比如下面的例子
public class LimitsOfInference {
static void f(Map> 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.
特别的
1)使用在定义该方法的类时要使用this关键字 即类似
this.> map()
2)使用static的方法 必须在点操作符前加上类名即类似
New.> map()
(此时map方法是静态方法)
map方法是静态方法的显示的类型说明 案例
public class ExplicitTypeSpecification {
static void f(Map> coffee) {
}
public static void main(String[] args) {
f(New.> map());
}
} // /:~
要明确 显示的类型说明仅使用在非赋值语句
泛型方法与可变参数列表可以很好的共存
public class GenericVarargs {
public static List makeList(T... args) {//可变参数结合泛型的方法
List result = new ArrayList();
for(T item : args)
result.add(item);
return result;
}
public static void main(String[] args) {
List 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)
泛型结合Collection的案例
import generics.coffee.*;
import java.util.*;
import net.mindview.util.*;
public class Generators {
//注意这里使用了接口Generator 它的实现有CoffeeGenerator Fibonacci
public static Collection fill(Collection coll, Generator gen,
int n) {
for (int i = 0; i < n; i++)
coll.add(gen.next());
return coll;
}
public static void main(String[] args) {
Collection coffee = fill(new ArrayList(),
new CoffeeGenerator(), 4);
for (Coffee c : coffee)
System.out.println(c);
Collection fnumbers = fill(new ArrayList(),
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,
*/// :~
public interface Generator {//这就是泛型接口
T next();
} // /:~
//使用该生成器需要2个条件
//1.使用者是public类
//2.使用者拥有无参构造函数(默认构造方法)
public class BasicGenerator implements Generator {
private Class type;
public BasicGenerator(Class 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 Generator create(Class type) {
return new BasicGenerator(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 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
*/// :~
使用类型推断+static方法 优化元组工具,使其更通用的工具类库
结合15.2.1的各种元组类 用Tuple统一结合起来专门生成对象
public class Tuple {
public static TwoTuple tuple(A a, B b) {
return new TwoTuple(a, b);
}
public static ThreeTuple tuple(A a, B b, C c) {
return new ThreeTuple(a, b, c);
}
} // /:~
使用:
static TwoTuple f2() { return tuple("hi", 47); }
static ThreeTuple g() {
return tuple(new Amphibian(), "hi", 47);
}
15.4.6 一个Set实用工具
//表示数学里面的关系
public class Sets {
//a并b
public static Set union(Set a, Set b) {
Set result = new HashSet(a);
result.addAll(b);
return result;
}
//a交b
public static Set intersection(Set a, Set b) {
Set result = new HashSet(a);
result.retainAll(b);
return result;
}
//去掉superset中 superset与subset的交集
public static Set difference(Set superset, Set subset) {
Set result = new HashSet(superset);
result.removeAll(subset);
return result;
}
// A并B 去掉 A交B
public static Set complement(Set a, Set 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 set1 = EnumSet.range(A, N);
Set set2 = EnumSet.range(H, T);
print("set1: " + set1);
print("set2: " + set2);
print("union(set1, set2): " + union(set1, set2));
Set 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 methodSet(Class> type) {//将集合类的方法存储到TreeSet 存储在set为了去重
Set result = new TreeSet();
for (Method m : type.getMethods())
result.add(m.getName());
return result;
}
static void interfaces(Class> type) {//将接口方法存储在ArrayList
System.out.print("Interfaces in " + type.getSimpleName() + ": ");
List result = new ArrayList();
for (Class> c : type.getInterfaces())
result.add(c.getSimpleName());
System.out.println(result);
}
static Set 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 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]
**/
public interface Generator { T next(); } ///:~
public class Generators {
public static Collection fill(Collection coll, Generator 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 generator() {
return new Generator() {
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 generator = new Generator() {
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 line = new LinkedList();
//生成15个Customer对象 放入line中
Generators.fill(line, Customer.generator(), 15);
List tellers = new ArrayList();
//生成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
*/// :~
利用泛型可以轻松地将A B C D等不同数据结构组合起来构成一个新的数据结构,比如
public class TwoTuple {
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 Collection fill(Collection coll, Generator 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 generator = new Generator() {
private Random rand = new Random(47);
//随机产生一个id<1000 描述为Test 价格为0-1000之间的 Product
public Product next() {
return new Product(rand.nextInt(1000), "Test", Math.round(rand
.nextDouble() * 1000.0) + 0.99);
}
};
}
class Shelf extends ArrayList {//Shelf是一个Product数组
public Shelf(int nProducts) {//产生nProducts个Product的ArrayList
Generators.fill(this, Product.generator, nProducts);
}
}
class Aisle extends ArrayList {//Aisle是一个Shelf数组
public Aisle(int nShelves, int nProducts) {//创建长度为nShelves的ArrayList 每一个Shelf元素中填充了nProducts个Product
for (int i = 0; i < nShelves; i++)
add(new Shelf(nProducts));
}
}
//class CheckoutStand {
//}
//
//class Office {
//}
public class Store extends ArrayList {//Store是一个Aisle数组
// private ArrayList checkouts = new ArrayList();
// 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
//ArrayList与ArrayList是否是不同的类型呢?
//我们可以将String放入ArrayList 却不能放入ArrayList
//所以他们是不同的类型? 但是输出结果似乎出乎意料
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList().getClass();
Class c2 = new ArrayList().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 {}
class Particle {}
public class LostInformation {
public static void main(String[] args) {
List list = new ArrayList();
Map map = new HashMap();
Quark quark = new Quark();
Particle p = new Particle();
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泛型学习的一个最大障碍
C++泛型例子
#include
using namespace std;
template 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 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 {
private T obj;
public Manipulator(T x) {
obj = x;
}
// Error: 编译报错 The method f() is undefined for the type T
public void manipulate() {
obj.f();//由于类型擦除 Java无法将obj能调用f方法的需求映射到实际类型HasF上
//为了能调用f方法 我们需要协助泛型类 给定泛型边界,告诉编译器遵循边界类型。
}
}
public class Manipulation {
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator manipulator = new Manipulator(hf);
manipulator.manipulate();
}
} // /:~
我们对上述例子稍加修改 就可以编译成功了
//这里有了边界
class Manipulator2 {//通过T extends HasF让Java编译器知道T也有HasF的方法f
private T obj;
public Manipulator2(T x) {
obj = x;
}
public void manipulate() {
obj.f();
}
public static void main(String[] args) {
HasF hf = new HasF();
Manipulator2 manipulator = new Manipulator2(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 {
private T obj;
public ReturnGenericType(T x) {
obj = x;
}
public T get() {//将返回确切的类型 如果不使用泛型 只能返回HasF类型
return obj;
}
} // /:~
Java的泛型不是一开始就有的产物,而是在SE 5.0引入的。之所以使用擦除,是为了兼容性。即使用了泛型的客户端仍然可以使用非泛型的类库,
并且使用了泛型的类库也可以使用在非泛型的客户端上。为了实现这一需求,Java采用擦除这一特性来实现泛型,即泛型只在特殊时期起作用,
过了这一时期,泛型就好像不存在一样,这样 不管程序是泛型的还是非泛型的,通通都可以看成没有使用泛型,也就不存在兼容性问题了。
为什么要兼容性:假设一个类库开发了很长时间,但是该类库不支持泛型,那么对于需要使用泛型的客户端,如果没有兼容性,该类库就废了。
泛型类型只在静态检查期间才出现,静态检查之后 所有泛型类型会被擦除,例如List会被擦除为List,普通的泛型类型会被擦除为Object。
Java实现泛型 需要从非泛化的代码向泛化代码转变 同时不能破坏现有类库。
擦除的代价是显著的,泛型只存在于静态检查,,而不能使用在运行时,比如强制类型转换 instanceof和new等操作符。在编写代码时,应该提醒自己
泛型只是看起来好像拥有参数类型信息,这只是暂时性的。
class GenericBase {
private T element;
public void set(T arg) {
arg = element;
}
public T get() {
return element;
}
}
class Derived1 extends GenericBase {//可以使用泛型
}
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); // 在这里出现警告,没有使用泛型来规定参数类型!
}
} // /:~
由于擦除 泛型有一个令人困惑的地方:可以表示没有任何意义的事物
例如:
public class ArrayMaker {//泛型类
private Class kind;
public ArrayMaker(Class 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 stringMaker = new ArrayMaker(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 {
List create() {
//虽然在调用new的时候 在运行时擦除了String的类型信息
//new ArrayList()看起来写成new ArrayList()也无所谓 但这样编译器会警告,没有进行类型检查
return new ArrayList();
}
public static void main(String[] args) {
//没有警告
ListMaker stringMaker = new ListMaker();
List stringList = stringMaker.create();
}
} ///:~
泛型使用在List
public class FilledListMaker {
List create(T t, int n) {
List result = new ArrayList();//擦除了类型
for (int i = 0; i < n; i++){
result.add(t);//但是还可以确保对象是T类型 这一点由编译器保证
}
return result;
}
public static void main(String[] args) {
FilledListMaker stringMaker = new FilledListMaker();
List 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_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public void set(java.lang.Object);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field obj:Ljava/lang/Object;
5: return
public java.lang.Object get();
Code:
0: aload_0
1: getfield #2 // Field obj:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #3 // class SimpleHolder
3: dup
4: invokespecial #4 // Method "":()V
7: astore_1
8: aload_1
9: ldc #5 // String Item
11: invokevirtual #6 // Method set:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7 // Method get:()Ljava/lang/Object;
18: checkcast #8 // class java/lang/String
21: astore_2
22: return
}
*/
public class GenericHolder {
private T obj;
public void set(T obj) {
this.obj = obj;
}
public T get() {
return obj;
}
public static void main(String[] args) {
GenericHolder holder = new GenericHolder();
holder.set("Item");
String s = holder.get();
}
}
/**
C:\Users\hjcai\Desktop>javac GenericHolder.java
C:\Users\hjcai\Desktop>javap -c GenericHolder
Warning: Binary file GenericHolder contains generics.GenericHolder
Compiled from "GenericHolder.java"
public class generics.GenericHolder {
public generics.GenericHolder();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public void set(T);
Code:
0: aload_0
1: aload_1
2: putfield #2 // Field obj:Ljava/lang/Object;
5: return
public T get();
Code:
0: aload_0
1: getfield #2 // Field obj:Ljava/lang/Object;
4: areturn
public static void main(java.lang.String[]);
Code:
0: new #3 // class generics/GenericHolder
3: dup
4: invokespecial #4 // Method "":()V
7: astore_1
8: aload_1
9: ldc #5 // String Item
11: invokevirtual #6 // Method set:(Ljava/lang/Object;)V
14: aload_1
15: invokevirtual #7 // Method get:()Ljava/lang/Object;
18: checkcast #8 // class java/lang/String
21: astore_2
22: return
}
*/
可以看到他们的反编译结果是一样的,这也一定程度解释了java的泛型是如何做到兼容性的。Java的泛型更多的由编译器确保 而编译结果看起来就像是没有泛型一样。(个人观点,还是没理解书里说的边界是什么)
由于泛型的擦除 所以在泛型代码中某些操作能力会被丢失。
public class Erased {
private final int SIZE = 100;
public static void f(Object arg) {
if(arg instanceof T) {} // Error
T var = new T(); // Error
T[] array = new T[SIZE]; // Error
T[] array = (T)new Object[SIZE]; // Unchecked warning
}
} ///:~
可以看到 instanceof 以及 new操作符 都不能直接作用在泛型上,那么如何解决这一问题呢
既然运行时类型信息被擦除了,那么我们可以在擦除前保存类型信息
class Building {
}
class House extends Building {
}
public class ClassTypeCapture {
Class kind;//用于保存类型信息
public ClassTypeCapture(Class kind) {//创建对象时保存类型信息
this.kind = kind;
}
public boolean f(Object arg) {//判断类型的方法
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture ctt1 = new ClassTypeCapture(
Building.class);
System.out.println(ctt1.f(new Building()));//动态判断类型
System.out.println(ctt1.f(new House()));
ClassTypeCapture ctt2 = new ClassTypeCapture(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
} /*
* Output: true true false true
*/// :~
Java不能创建泛型对象 new T()的原因有二
1.由于泛型擦除了类型信息
2.不知道具体的T是否具有默认无参构造函数
在C++中是如何创建泛型类型的对象的呢?
// C++, not Java!
// C++可以直接创建泛型类型对象因为它在编译期就会被检查
template class Foo {
T x; // 创建一个类型为T的filed
T* y; // 指向T的指针
public:
// 初始化指针:
Foo() { y = new T(); }//创建了一个泛型
};
class Bar {};
int main() {
Foo fb;
Foo fi; //对基本类型同样适用
} ///:~
如果我们想要像C++一样创建泛型类型 需要做额外工作:可以使用工厂对象
import static net.mindview.util.Print.*;
//不完善的工厂
class ClassAsFactory {
T x;//工厂保存了类型信息
public ClassAsFactory(Class 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 fe = new ClassAsFactory(
Employee.class);
print("ClassAsFactory succeeded");
try {
ClassAsFactory fi = new ClassAsFactory(
Integer.class);
} catch (Exception e) {
//创建Integer对象时失败 因为Integer没有默认无参构造函数,在调用newInstance时失败
print("ClassAsFactory failed");
}
}
} /*
* Output: ClassAsFactory succeeded ClassAsFactory failed
*/// :~
对上述代码进行优化 使用显示的工厂
interface FactoryI {
T create();
}
class Foo2 {
private T x;//保存类型信息
public > Foo2(F factory) {
x = factory.create();
}
// ...
}
class IntegerFactory implements FactoryI {//专门创建Integer的工厂
public Integer create() {
return new Integer(0);//不使用newInstance创建对象 而使用new创建对象 以免异常
}
}
class Widget {
public static class Factory implements FactoryI {//专门创建Widget的工厂
public Widget create() {
return new Widget();//不使用newInstance创建对象 而使用new创建对象 以免异常
}
}
}
public class FactoryConstraint {
public static void main(String[] args) {
new Foo2(new IntegerFactory());
new Foo2(new Widget.Factory());
}
} // /:~
使用模板方法可以达到同样的目的
abstract class GenericWithCreate {
final T element;// 用于保存类型信息
GenericWithCreate() {
System.out.println("1");
element = create();//保存类型信息的实际地点
}
abstract T create();
}
class X {
}
class Creator extends GenericWithCreate {
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
*/// :~
但是不管是哪一种方式,他们都会通过保存类型信息来创建泛型对象
本节的例子有点多 讨论的内容有以下几点
1.想要创建泛型数组可以使用ArrayList代替
2.非要使用数组的情况 在内部使用Object 在返回时转型为泛型类型
3.可以在创建泛型数组时传递一个类型标记 用于恢复被擦除的类型
4.Java的源码中也有大量Object数组转型为泛型数组的代码 这会产生大量警告。因此即使代码是写在源码中的 也不代表这就是正确的写法
如前Erased.java所述 不能创建泛型数组,可以使用ArrayList代替
这里你可以获得数组的行为以及编译期的类型安全
public class ListOfGenerics {
private List array = new ArrayList();
public void add(T item) {
array.add(item);
}
public T get(int index) {
return array.get(index);
}
} // /:~
class Generic {
}
public class ArrayOfGeneric {
static final int SIZE = 100;
// 编译器接受这样的声明 但却无法创建一个确切类型的数组
static Generic[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
// 可以编译 但是运行错误 [Ljava.lang.Object; cannot be cast to [Lgenerics.Generic;
// gia = (Generic[])new Object[SIZE];
// 运行时类型是原始(擦除了)类型 即Object类型
gia = (Generic[]) new Generic[SIZE];
System.out.println(gia.getClass().getSimpleName());
gia[0] = new Generic();
// gia[1] = new Object(); // 编译错误
// 编译时发现类型不匹配
// gia[2] = new Generic();
}
} /*
public class GenericArray {
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 gai = new GenericArray(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 {
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 gai = new GenericArray2(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 {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArrayWithTypeToken(Class type, int sz) {//传递了一个类型标记Class 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 gai = new GenericArrayWithTypeToken(
Integer.class, 10);
// This now works:
Integer[] ia = gai.rep();
}
} // /:~
extends使用在泛型边界上和普通情况的例子
interface HasColor {
java.awt.Color getColor();
}
class Colored {
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 {
// 多边界:
// 可以看到这里的extends和普通继承关系的 extends 不同
// 这里的extends被Java重写了
class ColoredDimension {//ColoredDimension持有一个T 该类型继承Dimension 实现HasColor
T 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 {//Solid持有一个T 该类型继承Dimension 实现HasColor和Weight
T 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可以存储在Solid
Solid solid = new Solid(new Bounded());
solid.color();
solid.getY();
solid.weight();
}
} // /:~
//例子 如何添加泛型边界限制//每个层次都加入边界限制
class HoldItem {//HoldItem持有一个对象 item item类型没有限制
T item;
HoldItem(T item) {
this.item = item;
}
T getItem() {
return item;
}
}
//前面的是泛型边界限制 后面的HoldItem是继承的意思
//Colored2继承HoldItem 它也持有一个对象item item限制为
class Colored2 extends HoldItem {
Colored2(T item) {
super(item);
}
java.awt.Color color() {
return item.getColor();
}
}
//ColoredDimension2继承Colored2 它也持有一个对象item item限制为
//当前类的限制其覆盖范围必须小于等于继承类的限制
class ColoredDimension2 extends Colored2 {
ColoredDimension2(T item) {
super(item);
}
int getX() {
return item.x;
}
int getY() {
return item.y;
}
int getZ() {
return item.z;
}
}
//进一步限制泛型类型
class Solid2 extends
ColoredDimension2 {
Solid2(T item) {
super(item);
}
int weight() {
return item.weight();
}
}
public class InheritBounds {
public static void main(String[] args) {
Solid2 solid2 = new Solid2(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 power;// 超级英雄具有能力
SuperHero(POWER power) {
this.power = power;
}
POWER getPower() {
return power;
}
}
class SuperSleuth extends SuperHero {// 能力限制为XRayVision
SuperSleuth(POWER power) {
super(power);
}
void see() {
power.seeThroughWalls();
}
}
class CanineHero extends
SuperHero {// 能力限制为SuperHearing和SuperSmell
CanineHero(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满足CanineHero泛型的限制
DogBoy() {
super(new SuperHearSmell());
}
}
public class EpicBattle {
// 边界在泛型方法的使用
// Bounds in generic methods:
static void useSuperHearing(
SuperHero hero) {//返回类型限制为SuperHearing
hero.getPower().hearSubtleNoises();
}
static void superFind(
SuperHero hero) {//返回类型限制为SuperHearing & SuperSmell
hero.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;
}
} // /:~
我们在前面的章节和本章前部分使用过通配符?
在本节 我们会更深入地讨论该问题。
入手点是:可以向子类类型的数组赋予父类的数组引用。
例子:可以向子类类型的数组赋予父类的数组引用的例子(数组的协变)
//从本例子可以发现 数组的类型检查 在编译和运行时的类型检查可能不同
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(); // OK
fruit[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 flist = new ArrayList();
} // /:~
//使用通配符可以在两个类型建立向上转型的关系(通配符支持协变)
public class GenericsAndCovariance {
public static void main(String[] args) {
// 通配符允许协变:
List extends Fruit> flist = new ArrayList();
// List extends Fruit> 可以理解为flist是一个List,该list的所有持有对象都是Fruit或者其子类
// List extends Fruit> flist期望的引用是任意fruit或者其子类 但是它不关心具体是什么类型
// 只要是fruit的子类即可
// 由于通配符的向上转型功能 new ArrayList();实际转型为new ArrayList()
// ?又代表了不确定的类型 那么编译器就不知道实际存储的类型了 因此添加任何类型的对象都会报错
// 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);
}
} // /:~
解释可能不是很清楚 但是只要记住 使用了通配符声明的引用,无法调用任何参数类型为泛型的方法,因为它不知道当前的类型
注意使用通配符之后 不是所有的方法都无法调用 而是方法参数为泛型类型的方法,无法调用 比如下面的例子
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 {
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 = new Holder();
Apple d = apple.get();
apple.set(d);
// Holder Fruit = apple; // 泛型不支持协变 通配符才支持
Holder extends Fruit> fruit = apple; // OK
Fruit 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
*/// :~
之前我们使用的是 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 apples = new ArrayList();
static List fruit = new ArrayList();
static void writeExact(List 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 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 readExact(List list) {
return list.get(0);
}
static List apples = Arrays.asList(new Apple());
static List fruit = Arrays.asList(new Fruit());
// A static method adapts to each call:
static void f1() {
Apple a = readExact(apples);//返回一个Apple
Fruit f = readExact(fruit);//返回一个Fruit
f = readExact(apples);//返回一个Apple赋值给Fruit
}
// If, however, you have a class, then its type is
// established when the class is instantiated:
static class Reader {
T readExact(List list) {
return list.get(0);
}
}
static void f2() {
Reader fruitReader = new Reader();//泛型确定为Fruit
Fruit f = fruitReader.readExact(fruit);
// Fruit a = fruitReader.readExact(apples); // Error:
// readExact(List) cannot be
// applied to (List).
// 如15.10所述 泛型不支持协变
}
static class CovariantReader {
T readCovariant(List extends T> list) {//使用上界通配符来读取
return list.get(0);
}
}
static void f3() {
CovariantReader fruitReader = new CovariantReader();
//可以从fruit列表读取fruit或者apples列表读取apple赋值给Fruit
Fruit f = fruitReader.readCovariant(fruit);
Fruit a = fruitReader.readCovariant(apples);
}
public static void main(String[] args) {
f1();
f2();
f3();
}
} // /:~
上面两个例子分别显示了上界通配符和下界通配符的使用场景
> 看起来意味着任何事物 那么它和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
15.10.4 捕获转换
//f2使用了无界通配符 但它调用f1时 f1仍然可以知道具体类型
public class CaptureConversion {
static void f1(Holder holder) {//该方法没有使用通配符 因此没有边界
T t = holder.get();
System.out.println(t.getClass().getSimpleName());
}
static void f2(Holder> holder) {//f2使用了无界通配符 并且调用了没有使用通配符的方法f1
f1(holder); // Call with captured type
}
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Holder raw = new Holder(1);
f1(raw); // Produces warnings
f2(raw); // No warnings
Holder rawBasic = new Holder();
rawBasic.set(new Object()); // Warning
f2(rawBasic); // No warnings
// Upcast to Holder>, still figures it out:
Holder> wildcarded = new Holder(1.0);
f2(wildcarded);
}
} /*
* Output: Integer Object Double
*/// :~
泛型的各种问题
由于基本类型都有包装类 因此 大部分问题都可以解决 比如像下面这个
//使用包装类创建泛型集合类
public class ByteSet {
Byte[] possibles = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
Set mySet = new HashSet(Arrays.asList(possibles));
// But you can't do this:
// Set mySet2 = new HashSet(
// Arrays.asList(1,2,3,4,5,6,7,8,9));
} // /:~
//包装类无法解决基本类型无法作为泛型类型的所有问题
// Fill an array using a generator:
class FArray {
public static T[] fill(T[] a, Generator 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
*/// :~
一个类不能实现泛型接口的两种变体
比如下面这个例子 看Hourly类 由于父类实现了Payable ,因此Hourly类实现了Payable和Payable两个接口
interface Payable {}
class Employee implements Payable {}
class Hourly extends Employee
implements Payable {} ///:~
//报错
//The interface Payable cannot be implemented more than once with different arguments: Payable and Payable
//也就是说编译器认为Payable和Payable是相同接口
//原因是类型擦除
我们必须在一些情况加上@SuppressWarnings(“unchecked”) 但是按道理讲是没有必要的 (泛型强制转换看起来无效)
//加了泛型 仍然需要转型
public class NeedCasting {
@SuppressWarnings("unchecked")
public void f(String[] args) throws Exception {
ObjectInputStream in = new ObjectInputStream(new FileInputStream(
args[0]));
//泛型转型似乎没有效果 仍然提示需要转型
List shapes = (List) 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 cast
List shapes = (List) in.readObject();
^
required: List
found: Object
1 warning
看起来(List)不是一个强制转换
**/
看另一个例子 这里我们使用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 lw1 =
// List.class.cast(in.readObject());
List lw2 = List.class.cast(in.readObject());
List lw3 = (List) List.class.cast(in.readObject());//不加@SuppressWarnings和lw2一样的警告
}
} // /:~
/**
* 当我们去掉@SuppressWarnings("unchecked")时 仍然有警告
C:\Users\hjcai\Desktop>javac -Xlint:unchecked ClassCasting.java
ClassCasting.java:13: warning: [unchecked] unchecked conversion
List lw2 = List.class.cast(in.readObject());
^
required: List
found: List
ClassCasting.java:15: warning: [unchecked] unchecked cast
(List)List.class.cast(in.readObject());
^
required: List
found: List
2 warnings
**/
// {CompileTimeError} (Won't compile)
import java.util.*;
public class UseList {
//由于类型擦除 这两个方法在编译器看来是同一个方法 因此无法编译
void f(List v) {
}
void f(List v) {
}
} // /:~
public class UseList2 {
//必须使用不同的方法名以示区别才可以编译
void f1(List v) {}
void f2(List v) {}
} ///:~
//ComparablePet实现Comparable接口 期望可以进行比较
public class ComparablePet implements Comparable {
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 and Comparable
//由于擦除 父类子类的实现接口相同 这里如果不使用泛型 可以编译,使用泛型导致报错
//因为Comparable的参数类型在父类已经确定 子类无法修改类型
class Cat extends ComparablePet implements Comparable{
// Error: Comparable cannot be inherited with
// different arguments: and
public int compareTo(Cat arg) { return 0; }
} ///:~
//要想覆盖基类的Comparable接口
//只能确保类型完全相同
//方式1
class Hamster extends ComparablePet implements Comparable {
public int compareTo(ComparablePet arg) {
return 0;
}
}
//方式二
// Or just:
class Gecko extends ComparablePet {
public int compareTo(ComparablePet arg) {
return 0;
}
} // /:~
我们也许会看到类似
class MyTest
}
之类的声明,这就是循环泛型,这看起来很难理解,让我们从简单的入手
先不使用自限定边界(不使用extends泛型边界)
public class BasicHolder {
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 {
}
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 看起来变成了所有子类的一个公共模板
上面的BasicHolder可以使用仍以类型作为其泛型参数 比如:
class Other {
}
class BasicOther extends BasicHolder {
}
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> {//基类使用自限定泛型类型
//可以对比BasicHolder 容易理解
T element;
SelfBounded set(T arg) {//注意返回值 返回的是泛型类型T
element = arg;
return this;
}
T get() {
return element;
}
}
class A extends SelfBounded {//强制要求A类传递给基类 使用A类当作泛型类型
}
class B extends SelfBounded {//虽然可以这么写 但是很少这么用
//由于A extends SelfBounded 因此
//A类满足>
} // Also OK
class C extends SelfBounded {
C setAndGet(C arg) {//新增方法 参数和返回值都是确切类型(C)
set(arg);
return get();
}
}
class D {
}
// Can't do this:
// class E extends SelfBounded {}
// 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 {//该类和BasicHolder基本一致
T element;
NotSelfBounded set(T arg) {
element = arg;
return this;
}
T get() {
return element;
}
}
class A2 extends NotSelfBounded {
}
class B2 extends NotSelfBounded {
}
class C2 extends NotSelfBounded {
C2 setAndGet(C2 arg) {
set(arg);
return get();
}
}
class D2 {
}
// Now this is OK:
class E2 extends NotSelfBounded {//自限定限制只能强制作用于继承关系
} // /:~
不使用自限定泛型例子
class Base {
}
class Derived extends Base {
}
interface OrdinaryGetter {
Base get();
}
interface DerivedGetter extends OrdinaryGetter {
// Return type of overridden method is allowed to vary:
//返回类型允许修改(修改为范围更小的类型)
@Override
Derived get();
}
public class CovariantReturnTypes {
void test(DerivedGetter d) {
Derived d2 = d.get();
}
} // /:~
使用自限定类型
//作用和CovariantReturnTypes一样 关注返回类型
interface GenericGetter> {
T get();
}
interface Getter extends GenericGetter {
}
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> {
void set(T arg);
}
interface Setter extends SelfBoundSetter {//子类只有一个set方法 并且参数类型是子类类型
}
public class SelfBoundingAndCovariantArguments {
void testA(Setter s1, Setter s2, SelfBoundSetter sbs) {
s1.set(s2);
// s1.set(sbs); // Error:不能使用父类类型
// set(Setter) in SelfBoundSetter
// cannot be applied to (SelfBoundSetter)
}
} // /:~
不使用自限定泛型
//对比OrdinaryArguments 关注参数类型
class GenericSetter { // Not self-bounded
void set(T arg) {
System.out.println("GenericSetter.set(Base)");
}
}
class DerivedGS extends GenericSetter {
//没有使用自限定类型 将可以进行重载
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)
*/// :~
结论:使用自限定类型 子类将使用确切的子类类型,如果不使用自限定类型,方法可以被重载(参数类型方式重载)
由于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 list
List dogs1 = new ArrayList();
oldStyleMethod(dogs1); // 悄悄地插入一个Cat
//参数:检查的列表 列表中的类型
List dogs2 = Collections.checkedList(new ArrayList(),
Dog.class);
try {
oldStyleMethod(dogs2); // Throws an exception
} catch (Exception e) {
System.out.println(e);
}
// Derived types work fine:
// 基类类型列表正常工作
List pets = Collections.checkedList(new ArrayList(),
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
*/// :~
将泛型应用于异常是受限的,因为
1.由于擦除,在运行时catch语句不能知晓异常的确切类型
2.泛型类不能继承Throwable(The generic class XXX may not subclass java.lang.Throwable)
但是可以以另外一种形式引入异常
interface Processor {//以第二个参数的形式将Exception引入
void process(List resultCollector) throws E;
}
class ProcessRunner extends ArrayList> {
List processAll() throws E {
List resultCollector = new ArrayList();
for (Processor processor : this)
processor.process(resultCollector);
return resultCollector;
}
}
class Failure1 extends Exception {
}
class Processor1 implements Processor {
static int count = 3;//注意是static的 所有对象公用
public void process(List resultCollector) throws Failure1 {
//初始值为3
//第一次3 > 1add("Hep!")
//第二次2 > 1add("Hep!")
//第三次1不大于1add("Ho!")
//且没有抛出异常
//如果把count初始值改为小于等于1的值 将会抛出异常
if (count-- > 1)
resultCollector.add("Hep!");
else
resultCollector.add("Ho!");
if (count < 0)
throw new Failure1();
}
}
class Failure2 extends Exception {
}
class Processor2 implements Processor {
static int count = 2;
public void process(List 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 runner = new ProcessRunner();
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 runner2 = new ProcessRunner();
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.1& 15.15.2
由于JAVA不支持多重继承 只能用接口来模拟这种情况
C++使用混型比Java容易得多,原因在于它支持多重继承 Java要实现混型 其代码量远大于C++
15.15.3
书中提到Java的混型与装饰器模式很类似,这里不是很理解。装饰器模式在于装饰者和被装饰者都继承了同一个基类
而混型的关键在于多重继承。也许是理解还不够到位,看不出来相似性。
虽然说书中将混型的例子用装饰器模式实现了,但是其实混型和装饰器还是有很大不同的。关于装饰器可以参考我之前的链接
https://blog.csdn.net/u011109881/article/details/81051385
书中接下来将例子修改为使用装饰器模式实现 但是感觉和真正的混型差别很大。
15.15.4 与动态代理混合
有些看不明白。。。
所谓潜在类型机制是指一种类型 只要该类型满足具有某些特定方法 就属于一种特殊类型(和实现了某个接口的类很类似的概念)
在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 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中实现潜在类型机制以及如何优化。 但实际中 潜在类型机制似乎并没有广泛的使用,这部分我本身也看得云里雾里 就先跳过了。
即使没有泛型 Java的强制转换其实不是很遭,泛型的最根本是解决将Dog类型的对象插入到Cat列表,但其实这类问题很容易发现,但是,因为泛型是java诞生很久之后添加的新功能,其兼容性使得程序的升级变得相当复杂,而且,本章也讨论了泛型的各种缺陷,因此引入泛型究竟是好是坏,这个问题值得深思。