Java笔记 – 泛型 泛型方法 泛型接口 擦除 边界 通配符(2)

发布于 2014-03-16 | 更新于 2020-09-20

10、通配符

泛型参数表达式中的问号。

首先来看一个例子,可以向导出类型Apple的数组,赋予基类型的数组引用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}

class CovariantArrays {
public static void main(String[] args) {
Fruit[] fruit = new Apple[10];
fruit[0] = new Apple(); // OK
fruit[1] = new Jonathan(); // OK
// Runtime type is Apple[], not Fruit[] or Orange[]:
try {
// Compiler allows you to add Fruit:
// 运行时抛出异常,此时的数组机制知道它处理的是Apple[]
fruit[0] = new Fruit(); // ArrayStoreException
} catch(Exception e) { System.out.println(e); }
try {
// Compiler allows you to add Oranges:
fruit[0] = new Orange(); // ArrayStoreException
} catch(Exception e) { System.out.println(e); }
}
} /* Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
*///:~

我们使用泛型来替代数组,使得错误可以再编译期可以检测到:

1
2
3
4
class NonCovariantGenerics {
// Compile Error: incompatible types:
List<Fruit> flist = new ArrayList<Apple>();
}

泛型是不会自动向上转型的,不能把一个涉及Apple的泛型赋给一个涉及Fruit的泛型。

有时候你想要在两个类型之间建立某种类型的向上转型关系,这正是通配符所允许的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
class GenericsAndCovariance {
public static void main(String[] args) {
// Wildcards allow covariance:
// List<? extends Fruit>:具有任何从Fruit继承的类型的列表,但是为了向上转型为flist,这个类型是什么并没有人关心
// 怎样才能安全地向其中添加对象呢?
List<? extends Fruit> flist = new ArrayList<Apple>();
// Compile Error: can't add any type of object:
// flist.add(new Apple());
// flist.add(new Fruit()); 即使 创建 flist的时候使用 new ArrayList<Fruit>(); 也不可以成功添加
// flist.add(new Object());
flist.add(null); // Legal but uninteresting
// We know that it returns at least Fruit: 可以向上转型为父类
Fruit f = flist.get(0);
}
}

10.1、编译器有多聪明

使用了 ? extends Fruit 的泛型的方法参数,将不能传入任何具体的参数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
class CompilerIntelligence {
public static void main(String[] args) {
// 声明了 List<? extends Fruit> ,编译器不能了解这里需要Fruit的哪个具体子类型,因此不会接受任何类型的Fruit,
// add()方法的参数就变成了“? extends Fruit”,不能加入任何的元素
List<? extends Fruit> flist =
Arrays.asList(new Apple());
// 但是却可以进行转型
Apple a = (Apple)flist.get(0); // No warning
// contains 和 indexOf方法参数类型是Object,因此不涉及任何通配符,编译器允许这个调用。
// 这意味着将由泛型类的设计者来决定哪些调用时安全的,并使用Object类型作为其参数类型
flist.contains(new Apple()); // Argument is 'Object'
flist.indexOf(new Apple()); // Argument is 'Object'
}
}

为了在类型中使用了通配符的情况系禁止contains的这类调用,我们需要在参数列表中使用类型参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
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>(new Apple());
Apple d = Apple.get();
Apple.set(d);
// 普通的泛型Holder<Apple>不能进行向上转型赋值为Holder<Fruit>
// Holder<Fruit> Fruit = Apple; // Cannot upcast
// 但是可以向上转型为Holder<? extends Fruit>
Holder<? extends Fruit> fruit = Apple; // OK
// 在使用get的时候,如果知道更具体的信息,就可以转换为具体的子类了,但也存在这转换异常的风险。
Fruit p = fruit.get();
d = (Apple)fruit.get(); // Returns 'Object'
try {
Orange c = (Orange)fruit.get(); // No warning
} catch(Exception e) { System.out.println(e); }
// 跟上例类型,set不能按照如下调用
// fruit.set(new Apple()); // Cannot call set()
// fruit.set(new Fruit()); // Cannot call set()
// equals接受的是Object,所以也能正常运行
System.out.println(fruit.equals(d)); // OK
}
}
/* Output: (Sample)
java.lang.ClassCastException: Apple cannot be cast to Orange
true
*///:~

10.2、逆变

超类型通配符:可以声明通配符是由某个特定类的rene积累来界定的 <? super MyClass>,也可以使用类型参数 <? super T>这使得你可以安全的传递一个类型对象到泛型类型中,因此,有了超类型通配符,就可以向Collection写入了:

1
2
3
4
5
6
7
8
class SuperTypeWildcards {
// Apple是下界
static void writeTo(List<? super Apple> apples) {
apples.add(new Apple());
apples.add(new Jonathan());
// apples.add(new Fruit()); // Error
}
}

根据如何能够向一个泛型类型“写入”(传递给一个方法),以及如何能够从一个泛型类型中“读取”(从一个方法中返回),来着手思考子类型和超类型边界?

超类型边界放松了在可以向方法传递的参数上所作的限制

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class GenericWriting {
static <T> void writeExact(List<T> list, T item) {
list.add(item);
}
static List<Apple> apples = new ArrayList<Apple>();
static List<Fruit> fruit = new ArrayList<Fruit>();
static void f1() {
writeExact(apples, new Apple());
// writeExact(fruit, new Apple()); // Error:
// Incompatible types: found Fruit, required Apple
}
static <T> void writeWithWildcard(List<? super T> list, T item) {
list.add(item);
}
static void f2() {
writeWithWildcard(apples, new Apple());
// 使用超类型边界之后,可以把Apple添加到类型为Fruit的list中了
writeWithWildcard(fruit, new Apple());
}
public static void main(String[] args) { f1(); f2(); }
}

下面继续看一个关于协变和通配符的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
class GenericReading {
// readExact使用了精确类型
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);
Fruit f = readExact(fruit);
f = readExact(apples);
}
// 如果有一个泛型类,当你创建这个类的实例时,要为这个类确定参数,就像在f2()中看到的,确定了类型后,就不能传递其他类型 的参数了。
static class Reader<T> {
T readExact(List<T> list) { return list.get(0); }
}
static void f2() {
Reader<Fruit> fruitReader = new Reader<Fruit>();
Fruit f = fruitReader.readExact(fruit);
// Fruit a = fruitReader.readExact(apples); // Error:
// readExact(List<Fruit>) cannot be
// applied to (List<Apple>).
}
// 为了解决这个问题,可以考虑使用子类型边界(向上转换为T,超类边界是为了让具体的子类可用)
static class CovariantReader<T> {
T readCovariant(List<? extends T> list) {
return list.get(0);
}
}
static void f3() {
CovariantReader<Fruit> fruitReader =
new CovariantReader<Fruit>();
Fruit f = fruitReader.readCovariant(fruit);
Fruit a = fruitReader.readCovariant(apples);
}
public static void main(String[] args) {
f1(); f2(); f3();
}
} ///:~

10.3、无边界通配符

无界通配符<?>看起来意味着“任何事物”,因此使用无界通配符好像等价于使用原生类型。

编译器初看起来是支持这种判断的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
class UnboundedWildcards1 {
static List list1;
static List<?> list2;
static List<? extends Object> list3;
static void assign1(List list) {
list1 = list;
list2 = list;
// list3 = list; // Warning: unchecked conversion 可以看出<?>和<? extends Object>是不同的
// Found: List, Required: List<? extends Object>
}
static void assign2(List<?> list) {
list1 = list;
list2 = list;
list3 = list;
}
static void assign3(List<? extends Object> list) {
list1 = list;
list2 = list;
list3 = list;
}
public static void main(String[] args) {
assign1(new ArrayList());
assign2(new ArrayList());
// assign3(new ArrayList()); // Warning:
// Unchecked conversion. Found: ArrayList
// Required: List<? extends Object>
assign1(new ArrayList<String>());
assign2(new ArrayList<String>());
assign3(new ArrayList<String>());
// Both forms are acceptable as List<?>:
List<?> wildList = new ArrayList();
wildList = new ArrayList<String>();
assign1(wildList);
assign2(wildList);
assign3(wildList);
}
}

在这些情况中,<?>可以被认为是一种装饰,但它仍旧很有价值,声明了“我是想用Java的泛型来编写这段代码,我在这里并不是要用原生类型但在这种情况下,泛型参数可以持有任何类型。”

下面展示无界通配符的一个重要应用:当处理多个泛型参数时有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型的这种能力会显得很重要:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class UnboundedWildcards2 {
static Map map1;
static Map<?,?> map2;
static Map<String,?> map3;
static void assign1(Map map) { map1 = map; }
// 当全是通配符时,编译器就无法将其与原生Map区分开来了
static void assign2(Map<?,?> map) { map2 = map; }
static void assign3(Map<String,?> map) { map3 = map; }
public static void main(String[] args) {
assign1(new HashMap());
assign2(new HashMap());
// assign3(new HashMap()); // Warning:
// Unchecked conversion. Found: HashMap
// Required: Map<String,?>
assign1(new HashMap<String,Integer>());
assign2(new HashMap<String,Integer>());
assign3(new HashMap<String,Integer>());
}
}

List

  • List表示持有任何Object类型的原生List
  • List<?>表示具有某种特定类型的非原生List,只是我们不知道那种类型是什么

编译器什么时候才会关注原生类型和涉及无界通配符的类型之间的差异呢?

下面用例子演示下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
class Wildcards {
// Raw argument:
// Holder是一个泛型类,这里表示称原生类型,但是编译器仍就知道向set传递一个Object是不安全的。
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();
}
// Similar to rawArgs(), but errors instead of warnings:
// 这里演示了<?>和原生类型是不同的:
static void unboundedArg(Holder<?> holder, Object arg) {
// 原生Holder将持有任何类型的组合,而Holder<?>将持有具有某种具体类型的同构集合,因此不能只是向其中传递Object
holder.set(arg); // Error:
// 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;
}
// 在Holder类型上的限制被放松为包括持有任何扩展自T的对象的Holder,
// 传入了Holder<Apple>之后,为了防止将Orange放置到Holder<Apple>,
// 对set的调用都是不允许的,但是你仍旧知道任何来自Holder<? extends Fruit的对象至少是Fruit,因此get()是允许的
static <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;
}
// 展示超类型通配
static <T> void wildSupertype(Holder<? super T> holder, T arg) {
// holder可以是持有任何T的基类型的容器,因此,set()可以接受T,因为任何可以工作于基类的对象都可以多态地作用于导出类(这里就是T)
holder.set(arg);
// T t = holder.get(); // Error: 由holder持有的类型可以是任何超类型,因此唯一安全的类型就是Object
// Incompatible types: found Object, required T

// OK, but type information has been lost:
Object obj = holder.get();
}
public static void main(String[] args) {
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 Object
Long 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)
}
}

exact2()具有最多的限制,因为它希望精确地得到一个Holder,以及一个具有类型T的参数,正是由此,它将产生错误或警告,除非提供确切的参数。又是这样很好,但是如果它过于受限,那么就可以使用通配符,这取决于是否想要从泛型参数中返回类型确定的返回值(wildSubtype())或者是想要向泛型参数传递类型确定的参数(wildSupertype())

使用确切类型来替代通配符的好处是可以用泛型参数来做更多的事,但是使用通配符使得你必须接受范围更宽的参数化类型作为参数。因此,必须逐个情况地权衡利弊,找到更适合你的需求的方法。

10.4、捕获转换

下面演示一下捕获转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
class CaptureConversion {
// f1()的类型参数都是确切的,没有通配符或者边界
static <T> void f1(Holder<T> holder) {
T t = holder.get();
System.out.println(t.getClass().getSimpleName());
}
// 是一个无界通配符,看起来是未知的,但是f2里面调用的f1的参数类型是要已知的,
// 这里发生的是:参数类型在调用f2()的过程中被捕获,因此它可以再对f1的调用中被使用
static void f2(Holder<?> holder) {
f1(holder); // Call with captured type
}
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Holder raw = new Holder<Integer>(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<Double>(1.0);
f2(wildcarded);
}
} /* Output:
Integer
Object
Double
*///:~

捕获转换非常有趣,但是非常受限:捕获转换在这个情况下才会工作,即在方法内部,需要使用确切的类型的时候,注意,不能从f2()中返回T,因为T对于f2()来说是未知的。

11 问题

11.1、任何基本类型都不能作为类型参数

可以使用基本类型的包装类,使用容器的时候,自动包装机制会把基本类型转换为包装类,但是记住:自动包装无法用于数组,所以泛型数组不能传入基本类型的数组。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class Chapter15_11_1 {

// 使用 t 填充数组
public static <T> T[] fill(T[] a, T t){
for(int i=0; i<a.length; i++){
a[i] = t;
}
return a;
}

public static void main(String[] args){
fill(new Integer[10], 3);
// fill(new int[10], 3); //编译失败,因为自动包装机制不能应用于数组,因此这无法工作。
}
}

11.2、实现参数化接口

一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。

1
2
3
4
5
6
7
8
9
10
interface Payable<T>{}

class Employee implements Payable<Employee>{}

/**
* 下面不能编译通过,因为擦除将会将 Payable<Employee> 和 Payable<Hourly> 简化为相同的类Payable。
* 去掉泛型,却可以通过编译。
*
*/
class Hourly extends Employee implements Payable<Hourly>{}

11.3、转型和警告

使用带有泛型类型参数的转型或indtanceof不会有任何效果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class FixedSizeStack<T> {
private int index = 0;
private Object[] storage;
public FixedSizeStack(int size) {
storage = new Object[size];
}
public void push(T item) { storage[index++] = item; }
@SuppressWarnings("unchecked")
public T pop() {
// 转型 unchecked cast警告,由于擦除的原因,编译器无法知道这个转型是否安全
// 实际上只是将Object转型为Object
return (T)storage[--index];
}
}

public class Chapter15_11_3 {
public static final int SIZE = 10;
public static void main(String[] args) {
FixedSizeStack<String> strings =
new FixedSizeStack<String>(SIZE);
for(String s : "A B C D E F G H I J".split(" "))
strings.push(s);
for(int i = 0; i < SIZE; i++) {
String s = strings.pop();
System.out.print(s + " ");
}
}

@SuppressWarnings("unchecked")
public void f(String filepath) throws Exception{
// 下面演示由readObject()方法读取转型
ObjectInputStream in = new ObjectInputStream(new FileInputStream(filepath));
// 如果没有压制的注解,则会阐释警告 Unchecked cast from Object to List<Circle>
// List<Circle> circles = (List<Circle>)in.readObject();

// 如果想继续使用泛型的情况下不产生警告,则可以使用Java EE5中的使用泛型类来转型
List<Circle> circles = List.class.cast(in.readObject());

// 但是你继续添加如下转型是仍会得到一个警告
// Type safety: Unchecked cast from List to List<Circle>
circles = (List<Circle>)List.class.cast(in.readObject());

}
}
​```java
### 11.4、重载

由于擦除的原因,重载方法将产生相同的类型签名
​```java
class UseList<W,T> {
// 错误:Method f(List<T>) has the same erasure f(List<E>) as another method in type UseList<W,T>
void f(List<T> v) {}
void f(List<W> v) {}
}

11.5、基类劫持了接口

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
class ComparablePet implements Comparable<ComparablePet> {
public int compareTo(ComparablePet arg) { return 0; }
}
// 报错:The interface Comparable cannot be implemented more than once with different arguments:
// Comparable<ComparablePet> and Comparable<TomCat>
// 基类ComparablePet劫持了Comparable接口,只能进行ComparablePet的比较,而不能进行TomCat的比较
class TomCat extends ComparablePet implements Comparable<TomCat>{
// Error: Comparable cannot be inherited with
// different arguments: <Cat> and <Pet>
public int compareTo(TomCat arg) { return 0; }
}

/**
* 下面演示实现ComparablePet中的相同接口的可行性:
* 这只是与覆盖基类中的方法相同
*/
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; }
}

12 自限定的类型

12.1、古怪的循环泛型

下面演示一个循环泛型的例子

1
2
3
class GenericType<T>{}
// 解释:创建一个新类,继承自一个泛型类型,这个泛型类型接受新类的名字作为其参数。
class CuriouslyRecurringGeneric extends GenericType<CuriouslyRecurringGeneric> {}

下面演示一下循环泛型的作用

首先创建一个泛型类

1
2
3
4
5
6
7
8
class BasicHolder<T> {
T element;
void set(T arg) { element = arg; }
T get() { return element; }
void f() {
System.out.println(element.getClass().getSimpleName());
}
}

实现循环泛型类,作用:基类BasicHolder用导出类Subtype替代其方法间传递的参数。

1
class Subtype extends BasicHolder<Subtype> {}

使用

1
2
3
4
5
6
7
8
9
public class Chapter15_12_1 {
public static void main(String[] args) {
Subtype st1 = new Subtype(), st2 = new Subtype();
// 传递给set()的参数和从get()返回的类型都是确切的Subtype
st1.set(st2);
Subtype st3 = st1.get();
st1.f();
}
}

12.2、自限定

首先看一个没有自限定的例子,BasicHolder可以使用任何类型作为其泛型参数:

1
2
3
4
5
6
7
8
9
10
11
12
class Other {}

class BasicOther extends BasicHolder<Other> {}

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();
}
}

我们使用自限定类型其实就是为了要求在继承关系中,像下面这样使用这个类

1
class A extends SelfBounded{}

这会强制要求将正在定义的子类当做参数传递给基类

下面看一个自限定类型的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
class SelfBounded<T extends SelfBounded<T>> {
T element;
SelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}

class A extends SelfBounded<A> {}
class B extends SelfBounded<A> {} // Also OK

class C extends SelfBounded<C> {
C setAndGet(C arg) { 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

// Alas, you can do this, so you can't force the idiom:
class F extends SelfBounded {}

public class Chapter15_12_2 {
public static void main(String[] args) {
A a = new A();

// 直接使用SelfBounded,传入类似A这样的子类
SelfBounded<A> bounded = new SelfBounded<A>();

a.set(new A());
a = a.set(new A()).get();
a = a.get();
C c = new C();
c = c.setAndGet(new C());
}
}

还可以将自限定用于泛型方法

1
2
3
4
5
6
7
8
class SelfBoundingMethods {
static <T extends SelfBounded<T>> T f(T arg) {
return arg.set(arg).get();
}
public static void main(String[] args) {
A a = f(new A());
}
}

12.3、参数协变

前一节演示的自限定类型的价值在于可以产生协变参数类型:方法参数类型会随子类而变化。

而自限定类型还可以产生于子类类型相同的返回值,如前一节的B类,但这并不是重要的,因为协变返回类型是在JavaSE5中引入的,之前的JDK版本并不能编译自限定

1
2
3
4
5
6
class Base {}
class Derived extends Base {}

interface OrdinaryGetter {
Base get();
}

子接口继承OrdinaryGetter,其中的get()方法返回值为到处的雷系Derived,这在早先的Java版本是不合法的

1
2
3
4
5
6
7
8
9
10
interface DerivedGetter extends OrdinaryGetter {
// Return type of overridden method is allowed to vary:
Derived get();
}

class CovariantReturnTypes {
void test(DerivedGetter d) {
Derived d2 = d.get();
}
}

下面演示一下自限定类型中导出类的方法接受导出类型而不是及类型为参数的

首先看一下非泛型代码中,参数不能随子类型发生变化的例子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class OrdinarySetter {
void set(Base base) {
System.out.println("OrdinarySetter.set(Base)");
}
}

class DerivedSetter extends OrdinarySetter {
void set(Derived derived) {
System.out.println("DerivedSetter.set(Derived)");
}
}

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); // 这里ds实际上有两个方法,在继承的时候,set方法被重载了,而不是覆盖了。
}
}

而在自限定中,是这样的:编译器不能识别将基类型当做参数传递给set()的尝试,因为没有任何方法具有这样的签名。

实际上,这个参数已经被覆盖了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface SelfBoundSetter<T extends SelfBoundSetter<T>> {
void set(T arg);
}

interface Setter extends SelfBoundSetter<Setter> {}

class SelfBoundingAndCovariantArguments {
void testA(Setter s1, Setter s2, SelfBoundSetter sbs) {
s1.set(s2);
// s1.set(sbs); // 错误,不存在这样的方法,这个方法已被Setter子类覆盖
// set(Setter) in SelfBoundSetter<Setter>
// cannot be applied to (SelfBoundSetter)
}
}

而没有使用自限定类型的情况下,普通的基础机制就会介入,这个时候方法就会被重载,就像在非泛型的情况下一样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class GenericSetter<T> { // 非自限定类型
void 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 Chapter15_12_3 {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
DerivedGS dgs = new DerivedGS();
dgs.set(derived);
dgs.set(base); // 编译通过,DerivedGS中的set()方法被重载,非覆盖
}
}

13、动态类型安全

没有使用泛型之前的代码,是不能限定放入容器的元素类型的,所以旧式的代码有可能会破坏你的容器。

JavaSE5中有一组静态方法可以检查类型问题:checkedCollection(), checkedList(), checkedMap(), checkedSet(), checkedSortedMap(), checkedSortedSet()

下面演示一下这种情况:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
interface Pet{}
class Dog implements Pet{}
class Cat implements Pet{}
public class Chapter15_13 {

// 假设oldStyleMethod()是遗留的代码
@SuppressWarnings("unchecked")
static void oldStyleMethod(List probablyDogs) {
probablyDogs.add(new Cat());
}

public static void main(String[] args) {
// 没有检验之前插入是没有问题的
List<Dog> dogs1 = new ArrayList<Dog>();
oldStyleMethod(dogs1); // Quietly accepts a Cat
// 检验之后抛出 ClassCastException
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());
}
}

14、异常

泛型使用于异常是非常受限的,catch语句不能捕获泛型类型的异常,因为在编译器和运行时都必须知道异常的确切类型,泛型类也不能直接或间接的继承自Throwable(这将进一步阻止你去定义不能捕获的泛型异常),但是,类型参数可能会在一个方法的throws子句中用到,这使得你可以编写随检查型异常的类而发生变化的泛型代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
interface Processor<T,E extends Exception> {
void process(List<T> resultCollector) throws E; // 抛出泛型异常
}

ProcessRunner 是一个可变数组,保存Processor

class ProcessRunner<T,E extends Exception> extends ArrayList<Processor<T,E>> {
// processAll()方法依次执行所有的Processor,resultCollector是收集参数,存储process的结果
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;
public void
process(List<String> resultCollector) throws Failure1 {
if(count-- > 1)
resultCollector.add("Hep!");
else
resultCollector.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 Chapter15_14 {

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());
try {
System.out.println(runner.processAll()); // [Hep!, Hep!, Ho!]
} 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());
try {
// 由于Processor2中的count为2,所以执行第三个Processor2的时候抛出异常了
System.out.println(runner2.processAll());
} catch(Failure2 e) {
System.out.println(e);
}
}
}

15 混型

混型最基本的概念:混合多个类的能力,混型的价值之一是可以将特性和行为一致地应用于多个类之上。

混型有点面向切面编程的味道。

15.1、C++中的混型

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
/******************************************
* C++代码
******************************************/
//: generics/Mixins.cpp
#include <string>
#include <ctime>
#include <iostream>
using namespace std;

template<class T> class TimeStamped : public T {
long timeStamp;
public:
TimeStamped() { timeStamp = time(0); }
long getStamp() { return timeStamp; }
};

template<class T> class SerialNumbered : public T {
long serialNumber;
static long counter;
public:
SerialNumbered() { serialNumber = counter++; }
long getSerialNumber() { return serialNumber; }
};

//Define and initialize the static storage:
template<class T> long SerialNumbered<T>::counter = 1;

class Basic {
string value;
public:
void set(string val) { value = val; }
string get() { return value; }
};

int main() {
TimeStamped<SerialNumbered<Basic> > mixin1, mixin2;
mixin1.set("test string 1");
mixin2.set("test string 2");
cout << mixin1.get() << " " << mixin1.getStamp() <<
" " << mixin1.getSerialNumber() << endl;
cout << mixin2.get() << " " << mixin2.getStamp() <<
" " << mixin2.getSerialNumber() << endl;
} /* Output: (Sample)
test string 1 1129840250 1
test string 2 1129840250 2
*///:~
​```java
### 15.2、与接口混合

使用接口来产生混型的例子,Mixin类基本上是在使用代理,因此,每个混入类型都要求在Mixin中有一个相应的域,而你必须在Mixin中编写所有必须的方法,将方法调用转发给恰当的对象。
​```java
interface TimeStamped { long getStamp(); }

class TimeStampedImp implements TimeStamped {
private final long timeStamp;
public TimeStampedImp() {
timeStamp = new Date().getTime();
}
public long getStamp() { return timeStamp; }
}

interface SerialNumbered { long getSerialNumber(); }

class SerialNumberedImp implements SerialNumbered {
private static long counter = 1;
private final long serialNumber = counter++;
public long getSerialNumber() { return serialNumber; }
}

interface Basic {
public void set(String val);
public String get();
}

class BasicImp implements Basic {
private String value;
public void set(String val) { value = val; }
public String get() { return value; }
}

Mixin类基本上是在使用代理,因此,每个混入类型都要求在Mixin中有一个相应的域。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
class Mixin extends BasicImp
implements TimeStamped, SerialNumbered {
// 混入类型对应的域
private TimeStamped timeStamp = new TimeStampedImp();
// 混入类型对应的域
private SerialNumbered serialNumber =
new SerialNumberedImp();
// 在Mixin中编写所有必须的方法,将方法调用转发给恰当的对象:
public long getStamp() { return timeStamp.getStamp(); }
public long getSerialNumber() {
return serialNumber.getSerialNumber();
}
}

public class Chapter15_15_2 {

public static void main(String[] args) {
Mixin mixin1 = new Mixin(), mixin2 = new Mixin();
mixin1.set("test string 1");
mixin2.set("test string 2");
System.out.println(mixin1.get() + " " +
mixin1.getStamp() + " " + mixin1.getSerialNumber());
System.out.println(mixin2.get() + " " +
mixin2.getStamp() + " " + mixin2.getSerialNumber());
}
}

缺点:当使用更复杂的混型时,代码数量会急速增加

15.3、使用装饰器模式

当你观察混型的使用方式时,就会发现混型概念好像与装饰器设计模式关系很近。

装饰器模糊使用分层对象来动态透明地向单个对象中添加责任。装饰器指定包装在最初的对象周围的所有对象都具有相同的基本接口。

某些事物是可装饰的,可以通过将其他类包装在这个可装饰对象的四周,来将功能分层。

装饰器是通过使用组合和形式化结构来实现的,而混型是基于继承的。

因此可以将基于参数化类型的混型当做是一种泛型装饰器机制,这种机制不需要装饰器设计模式的继承结构。

上一节的例子可以改写为使用装饰器:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
class Basic {
private String value;
public void set(String val) { value = val; }
public String get() { return value; }
}

class Decorator extends Basic {
protected Basic basic;
public Decorator(Basic basic) { this.basic = basic; }
public void set(String val) { basic.set(val); }
public String get() { return basic.get(); }
}

class TimeStamped extends Decorator {
private final long timeStamp;
public TimeStamped(Basic basic) {
super(basic);
timeStamp = new Date().getTime();
}
public long getStamp() { return timeStamp; }
}

class SerialNumbered extends Decorator {
private static long counter = 1;
private final long serialNumber = counter++;
public SerialNumbered(Basic basic) { super(basic); }
public long getSerialNumber() { return serialNumber; }
}

class Decoration {
public static void main(String[] args) {
TimeStamped t = new TimeStamped(new Basic());
TimeStamped t2 = new TimeStamped(
new SerialNumbered(new Basic()));
//! t2.getSerialNumber(); // Not available
SerialNumbered s = new SerialNumbered(new Basic());
SerialNumbered s2 = new SerialNumbered(
new TimeStamped(new Basic()));
//! s2.getStamp(); // Not available
}
}

缺点:从main方法注释掉的两行代码可以发现,使用装饰器所产生的对象类型是最后被装饰的类型,尽管可以添加多个层,但是最后一层才是实际的类型,因此只有最后一层方法是可视的。因此,对于装饰器来说,其明显的缺陷是它只能有效地工作于装饰中的最后一层,而混型方法显然会更自然一些。因此,装饰器只是对由混型提出的问题的一种局限的解决方案。

15.4、与动态代理混合

可以使用动态代理来创建一种比装饰器更贴近混型模型的机制,通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。

由于动态代理的限制,每个被混入的类都必须是某个接口的实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class MixinProxy implements InvocationHandler {
Map<String,Object> delegatesByMethod;
public MixinProxy(TwoTuple<Object,Class<?>>... pairs) {
delegatesByMethod = new HashMap<String,Object>();
for(TwoTuple<Object,Class<?>> pair : pairs) {
for(Method method : pair.second.getMethods()) {
String methodName = method.getName();
// The first interface in the map
// implements the method.
if (!delegatesByMethod.containsKey(methodName))
delegatesByMethod.put(methodName, pair.first);
}
}
}
public Object invoke(Object proxy, Method method,
Object[] args) throws Throwable {
String methodName = method.getName();
Object delegate = delegatesByMethod.get(methodName);
return method.invoke(delegate, args);
}
@SuppressWarnings("unchecked")
public static Object newInstance(TwoTuple... pairs) {
Class[] interfaces = new Class[pairs.length];
for(int i = 0; i < pairs.length; i++) {
interfaces[i] = (Class)pairs[i].second;
}
ClassLoader cl =
pairs[0].first.getClass().getClassLoader();
return Proxy.newProxyInstance(
cl, interfaces, new MixinProxy(pairs));
}
}

public class Chapter15_15_4 {

public static void main(String[] args) {
Object mixin = MixinProxy.newInstance(
tuple(new BasicImp(), Basic.class),
tuple(new TimeStampedImp(), TimeStamped.class),
tuple(new SerialNumberedImp(),SerialNumbered.class));
Basic b = (Basic)mixin;
TimeStamped t = (TimeStamped)mixin;
SerialNumbered s = (SerialNumbered)mixin;
b.set("Hello");
System.out.println(b.get());
System.out.println(t.getStamp());
System.out.println(s.getSerialNumber());
}
}
/* Output: (Sample)
Hello
1132519137015
1
*///:~

缺点:因为只有动态类型而不是非静态类型才包含所有的混入类型,因此这仍不如C++的方式好,因为可以再具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型为恰当的类型。

但是,这明显更接近于真正的混型。

16、潜在类型机制

Java泛型中,当要在泛型类型上执行操作时,就会产生问题,因为擦除要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对“泛化”概念的一种明显的限制,因为必须限制你的泛型类型,使他们继承自特定的类,或者特定的接口。在某些情况下,你最终可能会使用普通类或者普通接口,因为限定边界的泛型和可能会和指定类或接口没有任何区别。

某些编程语言提供的一种解决方法称为潜在雷系机制或结构化类型机制(鸭子类型机制:如果它走起来像鸭子,并且叫起来也像鸭子,那么你就可以将它当做鸭子对待。)

潜在类型机制使得你可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:“我不关心你是什么类型,只要你可以speak()和sit()即可。”由于不要求具体类型,因此代码就可以更加泛化了。

两种支持潜在类型机制的语言:Python和C++。

下面一段Python代码演示下潜在类型机制的支持:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Dog:
def speak(self):
print "Arf!"
def sit(self):
print "Sitting"
def repoduce(self)
pass

class Robot:
def speak(self):
print "Click!"
def sit(self):
print "Clank!"
def repoduce(self)
pass

def perform(anything):
anything.spead()
anything.sit()
*/

perform的anything参数只是一个标示符,它必须能够执行perform()期望它执行的操作,因此这里隐含着一个接口,但是从来都不必显示地写出这个接口——它是潜在的。perform不关心其参数的类型,因此我们可以向它传递任何对象,只要该对象支持speak()和sit()方法,否则,得到运行时异常。

Java的泛型是后来才添加的,因此没有任何机会可以去实现任何类型的潜在类型机制。

如果试图用Java实现上面的示例,就会被强制要求使用一个类或者接口,并在边界表达式中指定它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
interface Performs {
void speak();
void sit();
}

class PerformingDog extends Dog implements Performs {
public void speak() { System.out.println("Woof!"); }
public void sit() { System.out.println("Sitting"); }
public void reproduce() {}
}

class Robot implements Performs {
public void speak() { System.out.println("Click!"); }
public void sit() { System.out.println("Clank!"); }
public void oilChange() {}
}

class Communicate {
public static <T extends Performs>
void perform(T performer) {
performer.speak();
performer.sit();
}
}

public class Chapter15_16 {

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!
*/

注意:perform()不需要使用泛型来工作,它可以被简单的指定为接受一个Performs对象:

1
2
3
4
5
6
class Communicate {
public static void perform(Performs performer) {
performer.speak();
performer.sit();
}
}

17、对缺乏潜在类型机制的补偿

17.1、反射

对于潜在类型机制的一种补偿,可以使用的一种方式是反射,下面的perform()方法就是用了潜在类型机制:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
class Mime {
public void walkAgainstTheWind() {}
public void sit() { System.out.println("Pretending to sit"); }
public void pushInvisibleWalls() {}
public String toString() { return "Mime"; }
}

class SmartDog {
public void speak() { System.out.println("Woof!"); }
public void sit() { System.out.println("Sitting"); }
public void reproduce() {}
}

class CommunicateReflectively {
public static void perform(Object speaker) {
Class<?> spkr = speaker.getClass();
try {
try {
Method speak = spkr.getMethod("speak");
speak.invoke(speaker);
} catch(NoSuchMethodException e) {
System.out.println(speaker + " cannot speak");
}
try {
Method sit = spkr.getMethod("sit");
sit.invoke(speaker);
} catch(NoSuchMethodException e) {
System.out.println(speaker + " cannot sit");
}
} catch(Exception e) {
throw new RuntimeException(speaker.toString(), e);
}
}
}

public class Chapter15_17_1 {

public static void main(String[] args) {
CommunicateReflectively.perform(new SmartDog());
CommunicateReflectively.perform(new Robot());
CommunicateReflectively.perform(new Mime());
}
}

/* Output:
Woof!
Sitting
Click!
Clank!
Mime cannot speak
Pretending to sit
*///:~

17.2、将一个方法应用于序列

上一节通过反射类型实现的潜在类型机制把所有类型检查都转移到了运行时,因此许多情况下并不是我们所希望的。

下面创建一个apply()方法,它能够将任何方法f应用于某个序列seq中的所有对象,通过反射和可变参数args传递方法的参数来实现。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
class Apply {
// 必须放置边界和通配符,银边使得Apply和FilledList在所有需要的情况下都可以使用,否则,下面的某些Apply和FilledList应用将无法工作。
public static <T, S extends Iterable<? extends T>>
void apply(S seq, Method f, Object... args) {
try {
for(T t: seq)
f.invoke(t, args);
} catch(Exception e) {
// Failures are programmer errors
throw new RuntimeException(e);
}
}
}

class Shape {
public void rotate() { System.out.println(this + " rotate"); }
public void resize(int newSize) {
System.out.println(this + " resize " + newSize);
}
}

class Square extends Shape {}

class FilledList<T> extends ArrayList<T> {
// 类型标记技术是Java文献推荐的技术。但是,有些人强烈地首先工厂方式
public FilledList(Class<? extends T> type, int size) {
try {
for(int i = 0; i < size; i++)
// Assumes default constructor:
add(type.newInstance());
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}

public class Chapter15_17_2 {
public static void main(String[] args) throws Exception {
List<Shape> shapes = new ArrayList<Shape>();
for(int i = 0; i < 10; i++)
shapes.add(new Shape());
Apply.apply(shapes, Shape.class.getMethod("rotate"));
Apply.apply(shapes,
Shape.class.getMethod("resize", int.class), 5);
List<Square> squares = new ArrayList<Square>();
for(int i = 0; i < 10; i++)
squares.add(new Square());
Apply.apply(squares, Shape.class.getMethod("rotate"));
Apply.apply(squares,
Shape.class.getMethod("resize", int.class), 5);

Apply.apply(new FilledList<Shape>(Shape.class, 10),
Shape.class.getMethod("rotate"));
Apply.apply(new FilledList<Shape>(Square.class, 10),
Shape.class.getMethod("rotate"));

SimpleQueue<Shape> shapeQ = new SimpleQueue<Shape>();
for(int i = 0; i < 5; i++) {
shapeQ.add(new Shape());
shapeQ.add(new Square());
}
Apply.apply(shapeQ, Shape.class.getMethod("rotate"));
}
}

17.3、当你并未碰巧拥有正确的接口时

上一节示例的Iterable接口是内建的,如果刚好不存在适合你的接口的时候呢?

下面的例子中,没有预见到对“Addable”接口的需要,所以我们被限制在Collection继承层次结构之内,即便SimpleQueue有一个add()方法,它也不能工作。因为这会将代码限制为只能工作于Collection,因此这样的代码不是特别的繁华。有了潜在类型机制,情况就会不同了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
class Fill {
// 被限制在Collection继承层次结构之内
public static <T> void fill(Collection<T> collection,
Class<? extends T> classToken, int size) {
for(int i = 0; i < size; i++)
// Assumes default constructor:
try {
collection.add(classToken.newInstance());
} catch(Exception e) {
throw new RuntimeException(e);
}
}
}

class Contract {
private static long counter = 0;
private final long id = counter++;
public String toString() {
return getClass().getName() + " " + id;
}
}

class TitleTransfer extends Contract {}

public class Chapter15_17_3 {
public static void main(String[] args) throws Exception {
List<Contract> contracts = new ArrayList<Contract>();
Fill.fill(contracts, Contract.class, 3);
Fill.fill(contracts, TitleTransfer.class, 2);
for(Contract c: contracts)
System.out.println(c);
SimpleQueue<Contract> contractQueue =
new SimpleQueue<Contract>();
// Won't work. fill() is not generic enough:
// Fill.fill(contractQueue, Contract.class, 3);
}
}

/* Output:
Contract 0
Contract 1
Contract 2
TitleTransfer 3
TitleTransfer 4
*///:~

17.4、用适配器仿真潜在类型机制

实际上,潜在类型机制创建了一个包含所需方法的隐式接口。因此它遵循这样的规则:如果我们手工编写了必须的接口,那么它就应该能够解决问题。

从我们拥有的接口中编写代码来产生我们需要的接口,这是适配器设计模式的一个典型示例。我们可以使用适配器来适配已有的接口,以产生想要的接口。

首先创建一个Addable接口,具体的实现由适配器提供。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
interface Addable<T> { void add(T t); }

class Fill2 {
// Classtoken version:
// 用Addable取代前一节的Collection
public static <T> void fill(Addable<T> addable,
Class<? extends T> classToken, int size) {
for(int i = 0; i < size; i++)
try {
addable.add(classToken.newInstance());
} catch(Exception e) {
throw new RuntimeException(e);
}
}
// Generator version:
// 重载的fill,接受一个Generator而不是标记类型。
// 编译器将确保传递的是正确的Generator,因此不会抛出任何异常。
public static <T> void fill(Addable<T> addable,
Generator<T> generator, int size) {
for(int i = 0; i < size; i++)
addable.add(generator.next());
}
}

// To adapt a base type, you must use composition.
// Make any Collection Addable using composition:
// 创建一个Collection的Addable适配器
class AddableCollectionAdapter<T> implements Addable<T> {
private Collection<T> c;
public AddableCollectionAdapter(Collection<T> c) {
this.c = c;
}
public void add(T item) { c.add(item); }
}

// A Helper to capture the type automatically:
class Adapter {
public static <T>
Addable<T> collectionAdapter(Collection<T> c) {
return new AddableCollectionAdapter<T>(c);
}
}

// To adapt a specific type, you can use inheritance.
// Make a SimpleQueue Addable using inheritance:
class AddableSimpleQueue<T>
extends SimpleQueue<T> implements Addable<T> {
public void add(T item) { super.add(item); }
}

class Fill2Test {
public static void main(String[] args) {
// Adapt a Collection:
List<Coffee> carrier = new ArrayList<Coffee>();
// 使用Addable的Collection适配器
Fill2.fill(
new AddableCollectionAdapter<Coffee>(carrier),
Coffee.class, 3);
// Helper method captures the type:
Fill2.fill(Adapter.collectionAdapter(carrier),
Latte.class, 2);
for(Coffee c: carrier)
System.out.println(c);
System.out.println("----------------------");
// Use an adapted class:
AddableSimpleQueue<Coffee> coffeeQueue =
new AddableSimpleQueue<Coffee>();
Fill2.fill(coffeeQueue, Mocha.class, 4);
Fill2.fill(coffeeQueue, Latte.class, 1);
for(Coffee c: coffeeQueue)
System.out.println(c);
}
}
/* Output:
Coffee 0
Coffee 1
Coffee 2
Latte 3

Latte 4

Mocha 5 Mocha 6 Mocha 7 Mocha 8 Latte 9 *///:~

18、将函数对象用作策略

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
//Different types of function objects:
// 运算法则:联合
interface Combiner<T> { T combine(T x, T y); }
// 单参函数
interface UnaryFunction<R,T> { R function(T x); }
// 收集器
interface Collector<T> extends UnaryFunction<T,T> {
T result(); // Extract result of collecting parameter
}
//
interface UnaryPredicate<T> { boolean test(T x); }

class Functional {
// 结合seq中的所有对象
public static <T> T reduce(Iterable<T> seq, Combiner<T> combiner) {
Iterator<T> it = seq.iterator();
if(it.hasNext()) {
T result = it.next();
while(it.hasNext())
result = combiner.combine(result, it.next());
return result;
}
// If seq is the empty list:
return null; // Or throw exception
}
// Take a function object and call it on each object in
// the list, ignoring the return value. The function
// object may act as a collecting parameter, so it is
// returned at the end.
public static <T> Collector<T> forEach(Iterable<T> seq, Collector<T> func) {
for(T t : seq)
func.function(t);
return func;
}
// Creates a list of results by calling a
// function object for each object in the list:
public static <R,T> List<R> transform(Iterable<T> seq, UnaryFunction<R,T> func) {
List<R> result = new ArrayList<R>();
for(T t : seq)
result.add(func.function(t));
return result;
}
// Applies a unary predicate to each item in a sequence,
// and returns a list of items that produced "true":
public static <T> List<T> filter(Iterable<T> seq, UnaryPredicate<T> pred) {
List<T> result = new ArrayList<T>();
for(T t : seq)
if(pred.test(t))
result.add(t);
return result;
}
// To use the above generic methods, we need to create
// function objects to adapt to our particular needs:
static class IntegerAdder implements Combiner<Integer> {
public Integer combine(Integer x, Integer y) {
return x + y;
}
}
static class IntegerSubtracter implements Combiner<Integer> {
public Integer combine(Integer x, Integer y) {
return x - y;
}
}
static class BigDecimalAdder implements Combiner<BigDecimal> {
public BigDecimal combine(BigDecimal x, BigDecimal y) {
return x.add(y);
}
}
static class BigIntegerAdder implements Combiner<BigInteger> {
public BigInteger combine(BigInteger x, BigInteger y) {
return x.add(y);
}
}
static class AtomicLongAdder implements Combiner<AtomicLong> {
public AtomicLong combine(AtomicLong x, AtomicLong y) {
// Not clear whether this is meaningful:
return new AtomicLong(x.addAndGet(y.get()));
}
}
// We can even make a UnaryFunction with an "ulp"
// (Units in the last place):
static class BigDecimalUlp implements UnaryFunction<BigDecimal,BigDecimal> {
public BigDecimal function(BigDecimal x) {
return x.ulp();
}
}
static class GreaterThan<T extends Comparable<T>> implements UnaryPredicate<T> {
private T bound;
public GreaterThan(T bound) { this.bound = bound; }
public boolean test(T x) {
return x.compareTo(bound) > 0;
}
}
static class MultiplyingIntegerCollector implements Collector<Integer> {
private Integer val = 1;
public Integer function(Integer x) {
val *= x;
return val;
}
public Integer result() { return val; }
}
public static void main(String[] args) {
// Generics, varargs & boxing working together:
List<Integer> li = Arrays.asList(1, 2, 3, 4, 5, 6, 7);
Integer result = reduce(li, new IntegerAdder());
System.out.println(result);

result = reduce(li, new IntegerSubtracter());
System.out.println(result);

System.out.println(filter(li, new GreaterThan<Integer>(4)));

System.out.println(forEach(li,
new MultiplyingIntegerCollector()).result());

System.out.println(forEach(filter(li, new GreaterThan<Integer>(4)),
new MultiplyingIntegerCollector()).result());

MathContext mc = new MathContext(7);
List<BigDecimal> lbd = Arrays.asList(
new BigDecimal(1.1, mc), new BigDecimal(2.2, mc),
new BigDecimal(3.3, mc), new BigDecimal(4.4, mc));
BigDecimal rbd = reduce(lbd, new BigDecimalAdder());
System.out.println(rbd);

System.out.println(filter(lbd,
new GreaterThan<BigDecimal>(new BigDecimal(3))));

// Use the prime-generation facility of BigInteger:
List<BigInteger> lbi = new ArrayList<BigInteger>();
BigInteger bi = BigInteger.valueOf(11);
for(int i = 0; i < 11; i++) {
lbi.add(bi);
bi = bi.nextProbablePrime();
}
System.out.println(lbi);

BigInteger rbi = reduce(lbi, new BigIntegerAdder());
System.out.println(rbi);
// The sum of this list of primes is also prime:
System.out.println(rbi.isProbablePrime(5));

List<AtomicLong> lal = Arrays.asList(
new AtomicLong(11), new AtomicLong(47),
new AtomicLong(74), new AtomicLong(133));
AtomicLong ral = reduce(lal, new AtomicLongAdder());
System.out.println(ral);

System.out.println(transform(lbd,new BigDecimalUlp()));
}
}
/* Output:
28
-26
[5, 6, 7]
5040
210
11.000000
[3.300000, 4.400000]
[11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]
311
true
265
[0.000001, 0.000001, 0.000001, 0.000001]
*///:~

本文作者: arthinking

本文链接: https://www.itzhai.comgeneric-method-interface-border-wildcard-2.html

版权声明: 版权归作者所有,未经许可不得转载,侵权必究!联系作者请加公众号。

×
IT宅

关注公众号及时获取网站内容更新。