文章目录
泛型
泛型实现了参数化类型,这样编写的组件(通常是集合)可以适用于多种类型。
20.2简单泛型
泛型的主要目的之一就是用来约定集合要存储什么类型的对象,并且通过编译器确保规约得以满足。
20.2.1一个元组类库
有时一个方法需要能返回多个对象,而
return
语句只能返回单个对象,解决办法就是创建一个对象,用它打包想要返回的多个对象。当然,可以在每次需要的时候,专门创建一个类来完成这样的工作。但是有了泛型,就可以一劳永逸。同时,还获得了编译时的类型安全。
这个概念称为元组,它是将一组对象直接打包存储于单一对象中。可以从该对象读取其中的元素,但不允许向其中存储新对象。
// 利用继承机制实现长度更长的元组
public class Tuple2<A, B> {
public final A a1;
public final B a2;
public Tuple2(A a, B b) {
a1 = a;
a2 = b;
}
public String rep() {
return a1 + ", " + a2;
}
@Override
public String toString() {
return "(" + rep() + ")";
}
}
public class Tuple3<A, B, C> extends Tuple2<A, B> {
public final C a3;
public Tuple3(A a, B b, C c) {
super(a, b);
a3 = c;
}
@Override
public String rep() {
return super.rep() + ", " + a3;
}
}
public class Tuple4<A, B, C, D> extends Tuple3<A, B, C> {
public final D a4;
public Tuple4(A a, B b, C c, D d) {
super(a, b, c);
a4 = d;
}
@Override
public String rep() {
return super.rep() + ", " + a4;
}
}
public class Tuple5<A, B, C, D, E> extends Tuple4<A, B, C, D> {
public final E a5;
public Tuple5(A a, B b, C c, D d, E e) {
super(a, b, c, d);
a5 = e;
}
@Override
public String rep() {
return super.rep() + ", " + a5;
}
}
20.2.2一个堆栈类
// 用链式结构实现的堆栈
public class LinkedStack<T> {
private static class Node<U> {
U item;
Node<U> next;
Node() {
item = null;
next = null;
}
Node(U item, Node<U> next) {
this.item = item;
this.next = next;
}
boolean end() {
// 使用一个末端标识来判断栈何时为空
return item == null && next == null;
}
}
private Node<T> top = new Node<>(); // 栈顶
public void push(T item) {
top = new Node<>(item, top);
}
public T pop() {
T result = top.item;
if (!top.end()) {
top = top.next;
}
return result;
}
public static void main(String[] args) {
LinkedStack<String> lss = new LinkedStack<>();
for (String s : "Phasers on stun!".split(" ")) {
lss.push(s);
}
String s;
while ((s = lss.pop()) != null) {
System.out.println(s);
}
}
}
20.2.3RandomList
public class RandomList<T> extends ArrayList<T> {
private Random rand = new Random(47);
public T select() {
return get(rand.nextInt(size()));
}
public static void main(String[] args) {
RandomList<String> rs = new RandomList<>();
Arrays.stream("The quick brown fox jumped over the lazy brown dog".split(" "))
.forEach(rs::add);
IntStream.range(0, 11).forEach(i -> {
System.out.print(rs.select() + " ");
});
}
}
20.3泛型接口
泛型也可以应用于接口。例如,生成器,这是一种专门负责创建对象的类。实际上,这是工厂方法设计模式的一种应用。不过,当使用生成器创建新的对象时,它不需要任何参数,而工厂方法一般需要参数。生成器无需额外的信息就知道如何创建新对象。
一般而言,一个生成器只定义一个方法,用于创建对象。例如,java.util.function
类库中的Supplier
就是一个生成器。
20.4泛型方法
类本身可能是泛型的,也可能不是,不过这与它的方法是否是泛型并没有什么关系。
泛型方法独立于类,作为准则,请尽可能使用泛型方法。通常将单个方法泛型化要比将整个类泛型化更清晰易懂。
对于泛型类,必须在实例化该类时指定类型参数。使用泛型方法时,通常不需要指定参数类型,因为编译器会找出这些类型。这称为类型参数判断。
20.4.3简化元组的使用
// 使用类型参数推断和静态导入,把早期的元组重写为更通用的库
public class Tuple {
public static <A, B> Tuple2<A, B> tuple(A a, B b) {
return new Tuple2<>(a, b);
}
public static <A, B, C> Tuple3<A, B, C> tuple(A a, B b, C c) {
return new Tuple3<>(a, b, c);
}
public static <A, B, C, D> Tuple4<A, B, C, D> tuple(A a, B b, C c, D d) {
return new Tuple4<>(a, b, c, d);
}
public static <A, B, C, D, E> Tuple5<A, B, C, D, E> tuple(A a, B b, C c, D d, E e) {
return new Tuple5<>(a, b, c, d, e);
}
}
20.6泛型擦除
Java泛型是使用擦除实现的。这意味着当使用泛型时,任何具体的类型信息都被擦除了,唯一知道的就是在使用一个对象。因此,
List<String>
和List<Integer>
在运行时实际上是相同的类型。它们都被擦除成原生类型List
。
public class HasF {
public void f() {
System.out.println("HasF.f()");
}
}
public class Manipulator<T> {
private T obj;
public Manipulator(T x) {
obj = x;
}
// Error: cannot find symbol: method f()
public void manipulate() {
obj.f();
}
}
为了调用
f()
,必须协助泛型类,给定泛型类一个边界,以此告诉编译器只能接受遵循这个边界的类型。
// 边界<T extends HasF>声明T必须是HasF类型或其子类
public class Manipulator2<T extends HasF> {
private T obj;
public Manipulator2(T x) {
obj = x;
}
public void manipulate() {
obj.f();
}
}
泛型类型参数会擦除到它的第一个边界(可能有多个边界)。编译器实际上会把类型参数替换为它的擦除,例如,
T
擦除到了HasF
。
20.6.2迁移兼容性
在基于擦除的实现中,泛型类型被当作第二类类型处理,即不能在某些重要的上下文使用泛型类型。泛型类型只有在静态类型检测期间才出现,在此之后,程序中的所有泛型类型都将被擦除,替换为它们的非泛型上界。例如,
List<T>
这样的类型会被擦除为List
,普通的类型变量在未指定边界的情况下会被擦除为Object
。
20.6.3擦除的问题
擦除的代价是显著的。泛型不能用于显式地引用运行时类型的操作中,例如转型、
instanceof
操作和new
表达式。因为所有关于参数的类型信息都丢失了,当编写泛型代码时,必须时刻提醒自己,只是看起来拥有有关参数的类型信息而已。
当希望将类型参数不仅仅当作Object
处理时,就需要付出额外努力来管理边界。
20.6.4边界处的动作
因为擦除,会发现泛型最令人困惑的地方是可以表示没有任何意义的事物。
public class ArrayMaker<T> {
private Class<T> kind;
public ArrayMaker(Class<T> kind) {
this.kind = kind;
}
@SuppressWarnings("unchecked")
T[] create(int size) {
// 即使kind被存储为Class<T>,擦除也意味着它实际被存储为没有任何参数的Class。
// 因此,当在使用它时,例如创建数组,Array.newInstance()实际上并未拥有kind
// 所蕴含的类型信息。所以它不会产生具体的结果,因而必须转型,这会产生警告
return (T[]) Array.newInstance(kind, size);
}
public static void main(String[] args) {
ArrayMaker<String> stringMaker = new ArrayMaker<>(String.class);
String[] stringArray = stringMaker.create(9);
System.out.println(Arrays.toString(stringArray));
}
}
如果创建一个集合而不是数组,情况就不同了:
public class ListMaker<T> {
List<T> create() {
// 编译器不会给出任何警告,尽管知道在create()内部的new ArrayList<>()中的<T>
// 被移除了——在运行时,类内部没有任何<T>,因此这看起来毫无意义。但是如果遵循这种思路,
// 将这个表达式改为new ArrayList(),编译器就会发出警告
return new ArrayList<>();
}
public static void main(String[] args) {
ListMaker<String> stringMaker = new ListMaker<>();
List<String> stringList = stringMaker.create();
}
}
如果在创建
List
的同时向其中放入一些对象:
public class FilledList<T> extends ArrayList<T> {
FilledList(Supplier<T> gen, int size) {
// 添加指定size的Supplier所生成的对象到集合中
Suppliers.fill(this, gen, size);
}
public FilledList(T t, int size) {
for (int i = 0; i < size; i++) {
// 即使编译器无法得知add()中的T的任何信息,但它仍可以在编译期确保放入FilledList
// 中的对象是T类型。因此,即使擦除移除了方法或类中的实际类型的信息,编译器仍可以
// 确保方法或类中使用的类型的内部一致性
this.add(t);
}
}
public static void main(String[] args) {
List<String> list = new FilledList<>("Hello", 4);
System.out.println(list);
// Supplier version
List<Integer> ilist = new FilledList<>(() -> 47, 4);
System.out.println(ilist);
}
}
因为擦除移除了方法体中的类型信息,所以在运行时的问题就是边界:即对象进入和离开方法的地点。这些正是编译器在编译期执行类型检查并插入转型代码的地点——对入参的编译器检查和对返回值的转型。
20.7补偿擦除
因为擦除,将失去执行泛型代码中某些操作的能力。无法在运行时知道确切类型:
public class Erased<T> {
private final int SIZE = 100;
public void f() {
// error: illegal generic type for instanceof
if (arg instanceof T) {
}
// error: unexpected type
T var = new T();
// error: generic array creation
T[] array = new T[SIZE];
// warning: [unchecked] unchecked cast
T[] array = (T[]) new Object[SIZE];
}
}
有时,可以对这些问题进行编程,但是有时必须通过引入类型标签来补偿擦除。这意味着为所需的类型显式传递一个
Class
对象,以在类型表达式中使用它。
例如,由于擦除了类型信息,因此instanceof
会失败。类型标签可以使用动态的isInstance()
:
class Building {
}
class House extends Building {
}
public class ClassTypeCapture<T> {
Class<T> kind;
public ClassTypeCapture(Class<T> kind) {
this.kind = kind;
}
public boolean f(Object arg) {
return kind.isInstance(arg);
}
public static void main(String[] args) {
ClassTypeCapture<Building> ctt1 = new ClassTypeCapture<>(Building.class);
System.out.println(ctt1.f(new Building()));
System.out.println(ctt1.f(new House()));
ClassTypeCapture<House> ctt2 = new ClassTypeCapture<>(House.class);
System.out.println(ctt2.f(new Building()));
System.out.println(ctt2.f(new House()));
}
}
// 输出结果:
// true
// true
// false
// true
20.7.1创建类型的实例
试图使用
new T()
是行不通的,部分原因是由于擦除,部分原因是编译器无法验证T
是否具有默认(无参)构造函数。但是在c++中,此操作自然,直接且安全(在编译时检查):
template<class T> class Foo {
T x; // Create a field of type T
T *y; // Pointer to T
public:
// Initialize the pointer
Foo() {
y = new T();
}
};
class Bar {};
int main() {
Foo<Bar> fb;
Foo<int> fi; // ...and it works with primitives
}
Java中的解决方案是传入一个工厂对象,并使用该对象创建新实例。方便的工厂对象只是
Class
对象,因此,如果使用类型标记,则可以使用newInstance()
创建该类型的新对象:
class ClassAsFactory<T> implements Supplier<T> {
Class<T> kind;
ClassAsFactory(Class<T> kind) {
this.kind = kind;
}
@Override
public T get() {
try {
return kind.newInstance();
} catch (InstantiationException | IllegalAccessException e) {
throw new RuntimeException(e);
}
}
}
class Employee {
@Override
public String toString() {
return "Employee";
}
}
public class InstantiateGenericType {
public static void main(String[] args) {
ClassAsFactory<Employee> fe = new ClassAsFactory<>(Employee.class);
System.out.println(fe.get());
ClassAsFactory<Integer> fi = new ClassAsFactory<>(Integer.class);
try {
System.out.println(fi.get());
} catch (Exception e) {
System.out.println(e.getMessage());
}
}
}
由于
Integer
没有无参构造函数,因此会失败抛出异常。由于错误不是在编译时捕获的,因此语言创建者不赞成这种方法,建议使用显式工厂(Supplier
)并约束类型,以便只有实现该工厂的类可以这样创建对象。
public class FactoryConstraint {
public static void main(String[] args) {
System.out.println(new Foo2<>(new IntegerFactory()));
System.out.println(new Foo2<>(new Widget.Factory()));
// 传递Fudge::new仍然会产生工厂行为,因为编译器将对函数方法::new的调用
// 转换为对get()的调用
System.out.println(new Foo2<>(Fudge::new));
}
}
class IntegerFactory implements Supplier<Integer> {
private int i = 0;
@Override
public Integer get() {
return ++i;
}
}
class Widget {
private int id;
Widget(int n) {
id = n;
}
@Override
public String toString() {
return "Widget " + id;
}
public static class Factory implements Supplier<Widget> {
private int i = 0;
@Override
public Widget get() {
return new Widget(++i);
}
}
}
class Fudge {
private static int count = 1;
private int n = count++;
@Override
public String toString() {
return "Fudge " + n;
}
}
class Foo2<T> {
private List<T> x = new ArrayList<>();
Foo2(Supplier<T> factory) {
Suppliers.fill(x, factory, 5);
}
@Override
public String toString() {
return x.toString();
}
}
另一种方法是模板方法设计模式。
public class CreatorGeneric {
public static void main(String[] args) {
XCreator xc = new XCreator();
xc.f();
}
}
abstract class GenericWithCreate<T> {
final T element;
GenericWithCreate() {
element = create();
}
// create()是模板方法,在子类中被重写以生成该类型的对象
abstract T create();
}
class X {
}
class XCreator extends GenericWithCreate<X> {
@Override
X create() {
return new X();
}
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
20.7.2泛型数组
无法创建泛型数组(例如
T[] ts = new T[]
),通用的解决方案是在试图创建泛型数组的时候使用ArrayList
。这样做可以获得数组的行为,并且还具有泛型提供的编译时的类型安全性。
有时,仍然会创建泛型类型的数组。可以通过使编译器满意的方式定义对数组的通用引用:
public class Generic<T> {
}
public class ArrayOfGenericReference {
static Generic<Integer>[] gia;
}
编译器接受此操作而不产生警告。但是永远无法创建具有该确切类型(包括类型参数)的数组,因此有点令人困惑。由于所有数组,无论它们持有什么类型,都具有相同的结构,因此似乎可以创建一个
Object
数组并将其转换为所需的数组类型。实际上,这确实可以编译,但是会产生ClassCastException
:
public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic<Integer>[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
try {
// 即使gia被强制转换为Generic<Integer>[],该信息也仅在编译时存在。
// 在运行时,它仍然是一个Object数组,这会引起问题
gia = (Generic<Integer>[]) new Object[SIZE];
} catch (ClassCastException e) {
System.out.println(e.getMessage());
}
// Runtime type is the raw (erased) type
gia = (Generic<Integer>[]) new Generic[SIZE];
System.out.println(gia.getClass().getSimpleName());
gia[0] = new Generic<>();
// gia[1] = new Object(); // Compile-time error
// Discovers type mismatch at compile time
// gia[2] = new Generic<Double>();
}
}
问题在于,数组会跟踪其实际类型,而该类型是在创建数组时建立的。成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。
public class GenericArray<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
// 不能编写T[] array = new T[sz],因此创建一个Object数组并将其强制转换
array = (T[]) new Object[sz];
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
// Method that exposes the underlying representation
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArray<Integer> gai = new GenericArray<>(10);
try {
// 抛出异常,原因在于实际的运行时类型为Object[]
Integer[] ia = gai.rep();
} catch (ClassCastException e) {
System.out.println(e.getMessage());
}
// This is OK
Object[] oa = gai.rep();
}
}
由于擦除,数组的运行时类型只能是
Object[]
。如果立即将其转换为T[]
,则在编译时会丢失数组的实际类型,并且编译器可能会错过一些潜在的错误检查。因此,最好在集合中使用Object[]
,并在使用数组元素时向T
添加强制类型转换。
public class GenericArray2<T> {
private Object[] array;
public GenericArray2(int sz) {
array = new Object[sz];
}
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; // Unchecked cast
}
public static void main(String[] args) {
GenericArray2<Integer> 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 {
// 抛出异常,原因在于尝试将Object[]强制转换为T[]
Integer[] ia = gai.rep();
} catch (Exception e) {
System.out.println(e);
}
}
}
在内部将数组视为
Object[]
而不是T[]
的优点是,不太可能会忘记数组的运行时类型并意外地引入bug,尽管大多数(也许是全部)此类错误会在运行时被迅速检测到。
对于新代码,可以传入类型标记:
public class GenericArrayWithTypeToken<T> {
private T[] array;
// 类型标记Class<T>被传递到构造函数中以从擦除中恢复
@SuppressWarnings("unchecked")
public GenericArrayWithTypeToken(Class<T> type, int sz) {
array = (T[]) Array.newInstance(type, sz);
}
public void put(int index, T item) {
array[index] = item;
}
public T get(int index) {
return array[index];
}
public T[] rep() {
return array;
}
public static void main(String[] args) {
GenericArrayWithTypeToken<Integer> gai = new GenericArrayWithTypeToken<>(Integer.class, 10);
// This now works
Integer[] ia = gai.rep();
}
}
不幸的是,如果查看java标准库中的源代码,会发现到处都有从
Object
数组到参数化类型的转换。因此,即使在java库源代码中出现了一些习惯的用法,它们也不一定是正确的。
20.8边界
边界允许对泛型使用的参数类型施加约束。尽管这可以强制执行有关应用了泛型类型的规则,但潜在的更重要的效果是可以在绑定的类型中调用方法。
public class EpicBattle {
static <POWER extends SuperHearing> void useSuperHearing(SuperHero<POWER> hero) {
hero.getPower().hearSubtleNoises();
}
static <POWER extends SuperHearing & SuperSmell> void superFind(SuperHero<POWER> hero) {
hero.getPower().hearSubtleNoises();
hero.getPower().trackBySmell();
}
public static void main(String[] args) {
DogPerson dogPerson = new DogPerson();
useSuperHearing(dogPerson);
superFind(dogPerson);
// You can do this:
List<? extends SuperHearing> audioPeople;
// But you can't do this:
// List<? extends SuperHearing & SuperSmell> dogPs;
}
}
interface SuperPower {
}
interface XRayVision extends SuperPower {
void seeThroughWalls();
}
interface SuperHearing extends SuperPower {
void hearSubtleNoises();
}
interface SuperSmell extends SuperPower {
void trackBySmell();
}
class SuperHero<POWER extends SuperPower> {
POWER power;
SuperHero(POWER power) {
this.power = power;
}
POWER getPower() {
return power;
}
}
class SuperSleuth<POWER extends XRayVision> extends SuperHero<POWER> {
SuperSleuth(POWER power) {
super(power);
}
void see() {
power.seeThroughWalls();
}
}
class CanineHero<POWER extends SuperHearing & SuperSmell> extends SuperHero<POWER> {
CanineHero(POWER power) {
super(power);
}
void hear() {
power.hearSubtleNoises();
}
void smell() {
power.trackBySmell();
}
}
class SuperHearSmell implements SuperHearing, SuperSmell {
@Override
public void hearSubtleNoises() {
}
@Override
public void trackBySmell() {
}
}
class DogPerson extends CanineHero<SuperHearSmell> {
DogPerson() {
super(new SuperHearSmell());
}
}
20.9通配符
起始示例要展示数组的一种特殊行为:可以将派生类的数组赋值给基类的引用:
public 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
fruit[0] = new Fruit(); // ArrayStoreException
} catch (Exception e) {
System.out.println(e);
}
try {
// Compiler allows you to add Orange
fruit[0] = new Orange(); // ArrayStoreException
} catch (Exception e) {
System.out.println(e);
}
}
}
class Fruit {
}
class Apple extends Fruit {
}
class Jonathan extends Apple {
}
class Orange extends Fruit {
}
数组对象可以保留有关它们包含的对象类型的规则。
当试图使用泛型集合代替数组时:
public class NonCovariantGenerics {
// Compile error: incompatible types
// Apple的List在类型上不等价于Fruit的List,即使Apple是一种Fruit类型
List<Fruit> flist = new ArrayList<Apple>();
}
但是,有时想在两个类型间建立某种向上转型关系。通配符可以产生这种关系:
public class GenericsAndCovariance {
public static void main(String[] args) {
// Wildcards allow covariance
List<? extends Fruit> flist = 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 it returns at least Fruit
Fruit f = flist.get(0);
}
}
20.9.1编译器有多聪明
public class Holder<T> {
private T value;
public Holder() {
}
public Holder(T value) {
this.value = value;
}
public T get() {
return value;
}
public void set(T value) {
this.value = value;
}
@Override
public boolean equals(Object o) {
return o instanceof Holder && Objects.equals(value, ((Holder) o).value);
}
@Override
public int hashCode() {
return Objects.hashCode(value);
}
public static void main(String[] args) {
Holder<Apple> apple = new Holder<>(new Apple());
Apple d = apple.get();
// 未使用通配符,因此可以调用set方法
apple.set(d);
// Holder<Fruit> fruit = apple; // Cannot upcast
Holder<? extends Fruit> fruit = apple; // OK
Fruit p = fruit.get();
d = (Apple) fruit.get();
try {
Orange c = (Orange) fruit.get(); // No warning
} catch (Exception e) {
System.out.println(e);
}
// set不能工作在Apple和Fruit上,因为set的参数是? extends Fruit,
// 意味着它可以是任何事物,编译器无法验证任何事物的类型安全性
// fruit.set(new Apple()); // Cannot call set()
// fruit.set(new Fruit()); // Cannot call set()
// equals方法可以正常工作,因为它接受的参数是Object而不是T类型
System.out.println(fruit.equals(d));
}
}
20.9.2逆变
还可以走另外一条路,即使用超类型通配符:
<? super MyClass>
,或者甚至使用类型参数:<? super T>
(但是不能声明<T super MyClass>
)。这使得可以安全地传递一个类型对象到泛型类型中。因此,有了超类型通配符,就可以向Collection
写入了。
public class SuperTypeWildcards {
static void writeTo(List<? super Apple> apples) {
apples.add(new Apple());
apples.add(new Jonathan());
// apples.add(new Fruilt()); // Error
}
}
参数
apples
是Apple
的某种基类型的List
,这样就知道向其中添加Apple
或Apple
的子类型是安全的。但是因为Apple
是下界,所以向这样的List
中添加Fruit
是不安全的,因为这将使这个List
敞开口子,从而可以向其中添加非Apple
类型的对象,而这是违反静态类型安全的。
public class GenericReading {
static List<Apple> apples = Arrays.asList(new Apple());
static List<Fruit> fruit = Arrays.asList(new Fruit());
static <T> T readExact(List<T> list) {
return list.get(0);
}
// A static method adapts to each call
static void f1() {
Apple a = readExact(apples);
Fruit f = readExact(fruit);
f = readExact(apples);
}
// A class type is established when the class is instantiated
static class Reader<T> {
T readExact(List<T> list) {
return list.get(0);
}
}
static void f2() {
Reader<Fruit> fruitReader = new Reader<>();
Fruit f = fruitReader.readExact(fruit);
// Fruit a = fruitReader.readExact(apples);
// Error: incompatible types: List<Apple> cannot be converted to List<Fruit>
}
static class CovariantReader<T> {
T readCovariant(List<? extends T> list) {
return list.get(0);
}
}
static void f3() {
CovariantReader<Fruit> fruitReader = new CovariantReader<>();
Fruit f = fruitReader.readCovariant(fruit);
Fruit a = fruitReader.readCovariant(apples);
}
public static void main(String[] args) {
f1();
f2();
f3();
}
}
20.9.3无界通配符
无界通配符
<?>
看起来意味着任何事物,因此使用无界通配符好像等价于使用原生类型。事实上,编译器初看起来是支持这种判断的:
public class UnboundedWildcards1 {
static List list1;
static List<?> list2;
static List<? extends Object> list3;
static void assign1(List list) {
list1 = list;
list2 = list;
// warning: [unchecked] unchecked conversion
// required: List<? extends Object>
// found: List
list3 = list;
}
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());
// warning: [unchecked] unchecked method invocation:
// method assign3 in class UnboundedWildcards1 is applied to
// given types assign3(new ArrayList())
// required: List<? extends Object>
// found: ArrayList
// assign3(new ArrayList());
assign1(new ArrayList<>());
assign2(new ArrayList<>());
assign3(new ArrayList<>());
// Both forms are acceptable as List<?>
List<?> wildList = new ArrayList<>();
wildList = new ArrayList<>();
assign1(wildList);
assign2(wildList);
assign3(wildList);
}
}
编译器其实很少关心使用的是原生类型还是
<?>
。在这些情况中,<?>
可以被认为是一种装饰,但是它仍旧是很有价值的,因为,实际上它是在声明:想用java的泛型来编写这段代码,在这里并不是要用原生类型,但是在当前这种情况下,泛型参数可以持有任何类型。
当在处理多个泛型参数时,有时允许一个参数可以是任何类型,同时为其他参数确定某种特定类型的这种能力会显得很重要:
public class UnboundedWildcards2 {
static Map map1;
static Map<?, ?> map2;
static Map<String, ?> map3;
static void assign1(Map map) {
map1 = 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());
// warning: [unchecked] unchecked method invocation:
// method assign3 in class UnboundedWildcards2 is applied to
// given types assign3(new HashMap())
// required: Map<String, ?>
// found: HashMap
// assign3(new HashMap());
assign1(new HashMap<>());
assign2(new HashMap<>());
assign3(new HashMap<>());
}
}
但是,当拥有的全都是无界通配符时,就像
Map<?, ?>
那样,编译器看起来就无法将其与原生Map
区分开了。不过,编译器处理List<?>
和List<? extends Object>
是不同的。令人困惑的是,编译器并非总是关注像List
和List<?>
之间的这种差异,因此它们看起来就像是相同的事物。事实上,因为泛型参数擦除到它的第一个边界,因此List<?>
看起来等价于List<Object>
,而List
实际上也是List<Object>
。List
实际上表示持有任何Object
类型的原生List
,而List<?>
表示具有某种类型的非原生List
,只是不知道类型是什么。编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异:
public class Wildcards {
/**
* Raw argument.
* 在rawArgs()中,编译器知道Holder是一个泛型类型,因此即使它在这里被表示成一个原生类型,
* 编译器仍旧知道向set()传递一个Object是不安全的。由于它是原生类型,可以将任何类型的对象
* 传递给set(),而这个对象将被向上转型为Object。因此,无论何时,只要使用了原生类型,都会
* 放弃编译期检查。对get()的调用说明了相同的问题:没有任何T类型的对象,结果只能是一个Object
*/
static void rawArgs(Holder holder, Object arg) {
// warning: [unchecked] unchecked call to set(T) as a member of the raw type Holder
// holder.set(arg); where T is a type-variable:
// ^
// T extends Object declared in class Holder
// holder.set(arg);
// Can't do this; don't have any 'T'
// T t = holder.get();
// OK, but type information is lost
Object obj = holder.get();
}
/**
* Like rawArgs(), but errors instead of warnings.
* 人们很自然地会考虑原生Holder与Holder<?>是大致相同的事物。
* 但是unboundedArg()强调它们是不同的。原生Holder将持有任何
* 类型的组合,而Holder<?>将持有具有某种具体类型的同构集合,
* 因此不能只是向其中传递Object
*/
static void unboundedArg(Holder<?> holder, Object arg) {
// error: method set in class Holder<T> cannot be applied to given types;
// holder.set(arg);
// ^
// required: CAP#1
// found: Object
// reason: argument mismatch;
// Object cannot be converted to CAP#1
// where T is a type-variable:
// T extends Object declared in class Holder
// where CAP#1 is a fresh type-variable:
// CAP#1 extends Object from capture of ?
// holder.set(arg);
// Can't do this; don't have any 'T'
// T t = holder.get();
// OK, but type information is lost:
Object obj = holder.get();
}
static <T> T exact1(Holder<T> holder) {
return holder.get();
}
static <T> T exact2(Holder<T> holder, T arg) {
holder.set(arg);
return holder.get();
}
static <T> T wildSubtype(Holder<? extends T> holder, T arg) {
// error: method set in class Holder<T#2> cannot be applied to given types;
// holder.set(arg);
// ^
// required: CAP#1
// found: T#1
// reason: argument mismatch;
// T#1 cannot be converted to CAP#1
// where T#1,T#2 are type-variables:
// T#1 extends Object declared in method <T#1>wildSubtype(Holder<? extends T#1>,T#1)
// T#2 extends Object declared in class Holder
// where CAP#1 is a fresh type-variable:
// CAP#1 extends T#1 from capture of ? extends T#1
// holder.set(arg);
return holder.get();
}
static <T> void wildSupertype(Holder<? super T> holder, T arg) {
// error: incompatible types: CAP#1 cannot be converted to T
// T t = holder.get();
// ^
// where T is a type-variable:
// T extends Object declared in method <T>wildSupertype(Holder<? super T>,T)
// where CAP#1 is a fresh type-variable:
// CAP#1 extends Object super:
// T from capture of ? super T
// T t = holder.get();
holder.set(arg);
Object obj = holder.get();
}
public static void main(String[] args) {
Holder raw = new Holder<>();
// Or:
raw = new Holder();
Holder<Long> qualified = new Holder<>();
Holder<?> unbounded = new Holder<>();
Holder<? extends Long> bounded = new Holder<>();
Long lng = 1L;
// 为了迁移兼容性,rawArgs()将接受所有Holder的不同变体,而不会产生警告
rawArgs(raw, lng);
rawArgs(qualified, lng);
rawArgs(unbounded, lng);
rawArgs(bounded, lng);
// unboundedArg()方法也可以接受相同的所有类型
unboundedArg(raw, lng);
unboundedArg(qualified, lng);
unboundedArg(unbounded, lng);
unboundedArg(bounded, lng);
// 如果向接受确切泛型类型(没有通配符)的方法传递一个原生Holder引用,
// 就会得到一个警告,因为确切的参数期望得到在原生类型中并不存在的信息
// warning: [unchecked] unchecked method invocation:
// method exact1 in class Wildcards is applied to
// given types Object r1 = exact1(raw);
// required: Holder<T>
// found: Holder
// where T is a type-variable: T extends Object declared
// in method <T>exact1(Holder<T>)
Object r1 = exact1(raw);
Long r2 = exact1(qualified);
Object r3 = exact1(unbounded); // Must return Object
Long r4 = exact1(bounded);
// [unchecked] unchecked method invocation:
// method exact2 in class Wildcards is applied to
// given types Long r5 = exact2(raw, lng);
// required: Holder<T>,T
// found: Holder,Long
// where T is a type-variable: T extends Object declared
// in method <T>exact2(Holder<T>,T)
Long r5 = exact2(raw, lng);
Long r6 = exact2(qualified, lng);
// error: method exact2 in class Wildcards cannot be applied to
// given types Long r7 = exact2(unbounded, lng);
// required: Holder<T>,T
// found: Holder<CAP#1>,Long
// reason: inference variable T has incompatible bounds
// equality constraints: CAP#1
// lower bounds: Long
Long r7 = exact2(unbounded, lng);
// error: method exact2 in class Wildcards cannot be applied to
// given types Long r8 = exact2(bounded, lng);
// required: Holder<T>,T
// found: Holder<CAP#1>,Long
// reason: inference variable T has incompatible bounds
// equality constraints: CAP#1
// lower bounds: Long
// where T is a type-variable:
// T extends Object declared in method <T>exact2(Holder<T>,T)
// where CAP#1 is a fresh type-variable:
// CAP#1 extends Long from capture of ? extends Long
Long r8 = exact2(bounded, lng);
// warning: [unchecked] unchecked method invocation:
// method wildSubtype in class Wildcards is applied to given types
// Long r9 = wildSubtype(raw, lng);
// ^
// required: Holder<? extends T>,T
// found: Holder,Long
// where T is a type-variable:
// T extends Object declared in method <T>wildSubtype(Holder<? extends T>,T)
// warning: [unchecked] unchecked conversion
// Long r9 = wildSubtype(raw, lng);
// ^
// required: Holder<? extends T>
// found: Holder
// where T is a type-variable:
// T extends Object declared in method <T>wildSubtype(Holder<? extends T>,T)
Long r9 = wildSubtype(raw, lng);
Long r10 = wildSubtype(qualified, lng);
// OK, but can only return Object
Object r11 = wildSubtype(unbounded, lng);
Long r12 = wildSubtype(bounded, lng);
// warning: [unchecked] unchecked method invocation:
// method wildSupertype in class Wildcards is applied to given types
// wildSupertype(raw, lng);
// ^
// required: Holder<? super T>,T
// found: Holder,Long
// where T is a type-variable:
// T extends Object declared in method <T>wildSupertype(Holder<? super T>,T)
// warning: [unchecked] unchecked conversion
// wildSupertype(raw, lng);
// ^
// required: Holder<? super T>
// found: Holder
// where T is a type-variable:
// T extends Object declared in method <T>wildSupertype(Holder<? super T>,T)
wildSupertype(raw, lng);
wildSupertype(qualified, lng);
// error: method wildSupertype in class Wildcards cannot be applied to given types;
// wildSupertype(unbounded, lng);
// ^
// required: Holder<? super T>,T
// found: Holder<CAP#1>,Long
// reason: cannot infer type-variable(s) T
// (argument mismatch; Holder<CAP#1> cannot be converted to Holder<? super T>)
// where T is a type-variable:
// T extends Object declared in method <T>wildSupertype(Holder<? super T>,T)
// where CAP#1 is a fresh type-variable:
// CAP#1 extends Object from capture of ?
wildSupertype(unbounded, lng);
}
}
20.9.4捕获转换
有一种特殊情况需要使用
<?>
而不是原生类型。如果向一个使用<?>
的方法传递原生类型,那么对编译器来说,可能会推断出实际的类型参数,使得这个方法可以回转并调用另一个使用这个确切类型的方法,这被称为捕获转换,因为未指定的通配符类型被捕获,并被转换为确切类型。
public class CaptureConversion {
// f1()中的类型参数都是确切的,没有通配符或边界
static <T> void f1(Holder<T> holder) {
T t = holder.get();
System.out.println(t.getClass().getSimpleName());
}
/**
* 在f2()中,Holder参数是一个无界通配符,因此它看起来是未知的。但是,在f2()中调用了f1(),
* 而f1()需要一个已知参数。这里所发生的的是:在调用f2()的过程中捕获了参数类型,并在调用f1()
* 时使用了这种类型,可能想知道这项技术是否可以用于写入,但是这要求在传递Holder<?>时同时传递
* 一个具体类型。捕获转换只有在这样的情况下可以工作:即在方法内部,需要使用确切的类型。注意,
* 不能从f2()中返回T,因为T对于f2()来说是未知的。捕获转换十分有趣,但是非常受限
*/
static void f2(Holder<?> holder) {
f1(holder); // Call with captured type
}
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Holder raw = new Holder<>(1);
f1(raw);
f2(raw);
Holder rawBasic = new Holder();
rawBasic.set(new Object());
f2(rawBasic);
}
}
20.10问题
20.10.1任何基本类型都不能作为类型参数
20.10.2实现参数化接口
一个类不能实现同一个泛型接口的两种变体,由于擦除的原因,这两个变体会成为相同的接口。
public class MultipleInterfaceVariants {
}
interface Payable<T> {
}
class Employee implements Payable<Employee> {
}
/**
* 不能编译,因为擦除会将Payable<Employee>和Payable<Hourly>简化为相同的类Payable,
* 这样,就意味着重复实现相同的接口。不过,如果从Payable的两种用法中都移除掉泛型参数(就像
* 编译器在擦除阶段所做的那样)这段代码就可以编译。
* 在使用某些更基本的java接口,例如Comparable<T>时,这个问题可能会变得十分令人恼火
*/
class Hourly extends Employee implements Payable<Hourly> {
}
20.10.4重载
public class UserList<W, T> {
void f(List<T> v) {}
void f(List<W> v) {}
}
因为擦除,所以重载方法产生了相同的类型签名。
因而,当擦除后的参数不能产生唯一的参数列表时,必须提供不同的方法名。
20.10.5基类劫持接口
public class ComparablePet implements Comparable<ComparablePet> {
@Override
public int compareTo(ComparablePet o) {
return 0;
}
}
/**
* 不幸的是,这不能工作。一旦Comparable的类型参数设置为ComparablePet,
* 其他的实现类只能比较ComparablePet
*/
class Cat extends ComparablePet implements Comparable<Cat> {
// error: Comparable cannot be inherited with
// different arguments: <Cat> and <ComparablePet>
public int compareTo(Cat arg) {
return 0;
}
}
public class Hamster extends ComparablePet implements Comparable<ComparablePet> {
@Override
public int compareTo(ComparablePet arg) {
return 0;
}
}
// Or just:
class Gecko extends ComparablePet {
public int compareTo(ComparablePet arg) {
return 0;
}
}
20.11自限定的类型
在java泛型中,有一个似乎经常性出现的惯用法,它相当令人费解:
class SelfBounded<T extends SelfBounded<T>> {
// ...
}
20.11.1古怪的循环泛型
为了理解自限定类型的含义,从这个惯用法的一个简单版本入手,它没有自限定的边界。
class GenericType<T> {}
public class CuriouslyRecurringGeneric
extends GenericType<CuriouslyRecurringGeneric> {}
创建一个新类,它继承自一个泛型类型,这个泛型类型接受新类的名字作为其参数。由于java中的泛型关乎参数和返回类型,因此它能够产生使用子类作为其参数和返回类型的基类。它还能将子类用作其域类型,尽管这些将被擦除为
Object
的类型。
public class BasicHolder<T> {
T element;
void set(T arg) {
element = arg;
}
T get() {
return element;
}
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
public class Subtype extends BasicHolder<Subtype> {}
public class CRGWithBasicHolder {
public static void main(String[] args) {
Subtype st1 = new Subtype(), st2 = new Subtype();
st1.set(st2);
Subtype st3 = st1.get();
st1.f();
}
}
新类
Subtype
接受的参数和返回的值具有Subtype
类型而不仅仅是基类BasicHolder
类型,即基类用子类替代其参数。这意味着泛型基类变成了一种其所有子类的公共功能的模板,但是这些功能对于其所有参数和返回值,将使用子类。也就是说,在所产生的类中将使用确切类型而不是基类型。因此,在Subtype
中,传递给set()
的参数和从get()
返回的类型都是确切的Subtype
。
20.11.2自限定
限定将采取额外的步骤,强制泛型当作其自身的边界参数来使用。观察所产生的类可以如何使用以及不可以如何使用:
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());
}
}
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> {
}
// 还可以从使用了另一个SelfBounded参数的SelfBounded中导出
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: type parameter D is not within its bound
// class E extends SelfBounded<D> {}
// 可以编译,自限定的惯用法不是强制执行的
class F extends SelfBounded {}
自限定所做的,就是要求在继承关系中,像下面这样使用这个类:
class A extends SelfBounded<A> {}
这会强制要求将正在定义的类当作参数传递给基类。
自限定的参数可以保证类型参数必须与正在被定义的类相同。因此很明显,自限定限制只能强制作用于继承关系。如果使用自限定,就应该了解这个类所用的类型参数将与使用这个参数的类具有相同的基类型。这会强制要求使用这个类的每个人都要遵循这种形式。还可以将自限定用于泛型方法:
public 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());
}
}
这可以防止这个方法被应用于除上述形势的自限定参数之外的任何事物上。
20.11.3参数协变
自限定类型的价值在于它们可以产生协变参数类型,方法参数类型会随子类而变化。
自限定泛型事实上将产生确切的导出类型作为其返回值:
public class GenericsAndReturnTypes {
void test(Getter g) {
Getter result = g.get();
GenericGetter gg = g.get(); // Also the base type
}
}
interface GenericGetter<T extends GenericGetter<T>> {
T get();
}
interface Getter extends GenericGetter<Getter> {
}
然而,在非泛型代码中,参数类型不能随子类型发生变化:
public class OrdinaryArguments {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
DerivedSetter ds = new DerivedSetter();
ds.set(base);
// Compiles--overloaded, not overridden!
ds.set(derived);
}
}
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)");
}
}
但是,在使用自限定类型时,在子类中只有一个方法,并且这个方法接受子类而不是基类作为其参数:
public class SelfBoundingAndCovariantArguments {
void testA(Setter s1, Setter s2, SelfBoundSetter sbs) {
s1.set(s2);
// error: method set in interface SelfBoundSetter<T> cannot be applied to given types;
// s1.set(sbs);
// ^
// required: Setter
// found: SelfBoundSetter
// reason: argument mismatch;
// s1.set(sbs);
}
}
interface SelfBoundSetter<T extends SelfBoundSetter<T>> {
void set(T arg);
}
interface Setter extends SelfBoundSetter<Setter> {
}
20.12动态类型安全
因为可以向java5之前的代码传递泛型集合,所以旧式代码仍旧有可能破坏集合。Java5的
java.util.Collections
中有一组便利工具,可以解决在这种情况下的类型检查问题:checkedCollection()
、checkedList()
、checkedMap()
、checkedSet()
、checkedSortedMap()
和checkedSortedSet()
。
受检查的集合在试图插入类型不正确的对象时抛出ClassCastException
:
public class CheckedList {
@SuppressWarnings("unchecked")
static void oldStyleMethod(List probablyDogs) {
probablyDogs.add(new Cat());
}
public static void main(String[] args) {
List<Dog> dogs1 = new ArrayList<>();
oldStyleMethod(dogs1); // Quietly accepts a Cat
List<Dog> dogs2 = Collections.checkedList(new ArrayList<>(), Dog.class);
try {
oldStyleMethod(dogs2); // Throws an exception
} catch (Exception e) {
System.out.println("Expected: " + e);
}
// Derived types work fine:
List<Pet> pets = Collections.checkedList(new ArrayList<>(), Pet.class);
pets.add(new Dog());
pets.add(new Cat());
}
}
20.13泛型异常
由于擦除的原因,
catch
语句不能捕获泛型类型的异常,因为在编译期和运行时都必须知道异常的确切类型。泛型类也不能直接或间接继承自Throwable
(这将进一步阻止去定义那些不能捕获的泛型异常)。但是,类型参数可能会在一个方法的throws
子句中用到。这使得可以编写随检查异常类型变化的泛型代码。
public class ThrowGenericException {
public static void main(String[] args) {
ProcessRunner<String, Failure1> runner = new ProcessRunner<>();
for (int i = 0; i < 3; i++) {
runner.add(new Processor1());
}
try {
System.out.println(runner.processAll());
} catch (Failure1 e) {
System.out.println(e);
}
ProcessRunner<Integer, Failure2> runner2 = new ProcessRunner<>();
for (int i = 0; i < 3; i++) {
runner2.add(new Processor2());
}
try {
System.out.println(runner2.processAll());
} catch (Failure2 e) {
System.out.println(e);
}
}
}
interface Processor<T, E extends Exception> {
void process(List<T> resultCollector) throws E;
}
class ProcessRunner<T, E extends Exception> extends ArrayList<Processor<T, E>> {
List<T> processAll() throws E {
List<T> resultCollector = new ArrayList<>();
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;
@Override
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;
@Override
public void process(List<Integer> resultCollector) throws Failure2 {
if (count-- == 0) {
resultCollector.add(47);
} else {
resultCollector.add(11);
}
if (count < 0) {
throw new Failure2();
}
}
}
20.14混型
混型最基本的概念是混合多个类的能力,以产生一个可以表示混型中所有类型的类。混型的价值之一是它们可以将特性和行为一致地应用于多个类之上。因此,混型有点类似于面向切面编程,而切面经常被建议用来解决混型问题。
20.14.1C++中的混型
在c++中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在c++中,可以很容易地创建混型,因为c++能够记住其模板参数的类型:
// 有两个混型类型:一个使得可以在每个对象中混入拥有一个时间戳这样的属性,
// 而另一个可以混入一个序列号
#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() {
// mixin1, mixin2所产生的类型拥有所混入类型的所有方法。可以将混型看作是一种功能,
// 它可以将现有类映射到新的子类上
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;
}
遗憾的是,java泛型不允许这样。擦除会忘记其基类类型,因此,泛型类不能直接继承自一个泛型参数。
20.14.2与接口混合
一种更常见的推荐解决方案是使用接口来产生混型效果:
public class Mixins {
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());
}
}
interface TimeStamped {
long getStamp();
}
class TimeStampedImp implements TimeStamped {
private final long timeStamp;
public TimeStampedImp() {
timeStamp = new Date().getTime();
}
@Override
public long getStamp() {
return timeStamp;
}
}
interface SerialNumbered {
long getSerialNumber();
}
class SerialNumberedImp implements SerialNumbered {
private static long counter = 1;
private final long serialNumber = counter++;
@Override
public long getSerialNumber() {
return serialNumber;
}
}
interface Basic {
void set(String val);
String get();
}
class BasicImp implements Basic {
private String value;
@Override
public void set(String val) {
value = val;
}
@Override
public String get() {
return value;
}
}
/**
* Mixin类基本上是在使用委托,因此每个混入类型都要求在Mixin中有一个相应的域,
* 必须在Mixin中编写所有必须的方法,将方法调用转发给恰当的对象,但是当使用更复杂的混型时,
* 代码数量会急剧增加
*/
class Mixin extends BasicImp implements TimeStamped, SerialNumbered {
private TimeStamped timeStamp = new TimeStampedImp();
private SerialNumbered serialNumber = new SerialNumberedImp();
@Override
public long getStamp() {
return timeStamp.getStamp();
}
@Override
public long getSerialNumber() {
return serialNumber.getSerialNumber();
}
}
20.14.3使用装饰器模式
当观察混型的使用方式时,就会发现混型的概念好像与装饰器设计模式关系很近。前面的示例可以被改写为使用装饰器:
/**
* 尽管可以添加多个层,但是最后一层才是实际的类型,因此只有最后一层的方法是可视的,
* 其明显的缺陷是它只能有效地工作于装饰中的一层(最后一层),而混型方法显然会更自然一些。
* 因此,装饰器只是对由混型提出的问题的一种局限的解决方案
*/
public 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
}
}
class Basic {
private String value;
public void set(String val) {
value = val;
}
public String get() {
return value;
}
}
class Decorator extends Basic {
protected Basic basic;
Decorator(Basic basic) {
this.basic = basic;
}
@Override
public void set(String val) {
basic.set(val);
}
@Override
public String get() {
return basic.get();
}
}
class TimeStamped extends Decorator {
private final long timeStamp;
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++;
SerialNumbered(Basic basic) {
super(basic);
}
public long getSerialNumber() {
return serialNumber;
}
}
20.14.4与动态代理混合
可以使用动态代理来创建一种比装饰器更贴近混型模型的机制。通过使用动态代理,所产生的类的动态类型将会是已经混入的组合类型。由于动态代理的限制,每个被混入的类都必须是某个接口的实现:
public class DynamicProxyMixin {
public static void main(String[] args) {
// tuple方法返回一个Tuple2的实例
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());
}
}
class MixinProxy implements InvocationHandler {
Map<String, Object> delegatesByMethod;
// Tuple2是一个泛型类,其中有两个由类型参数组成的public final域,分别为a1和a2
@SuppressWarnings("unchecked")
MixinProxy(Tuple2<Object, Class<?>>... pairs) {
delegatesByMethod = new HashMap<>();
for (Tuple2<Object, Class<?>> pair : pairs) {
for (Method method : pair.a2.getMethods()) {
String methodName = method.getName();
// The first interface in the map
// implements the method.
if (!delegatesByMethod.containsKey(methodName)) {
delegatesByMethod.put(methodName, pair.a1);
}
}
}
}
@Override
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(Tuple2... pairs) {
Class[] interfaces = new Class[pairs.length];
for (int i = 0; i < pairs.length; i++) {
interfaces[i] = (Class) pairs[i].a2;
}
ClassLoader cl = pairs[0].a1.getClass().getClassLoader();
return Proxy.newProxyInstance(cl, interfaces, new MixinProxy(pairs));
}
}
由于只有动态类型而不是静态类型才包含所有的混入类型,因此这仍旧不如c++的方式好,因为可以在具有这些类型的对象上调用方法之前,会被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显更接近真正的混型。
20.15潜在类型机制
要编写能够尽可能广泛地应用的代码,为了实现这一点,需要各种途径来放松对代码将要作用的类型所作的限制,同时不丢失静态类型检查的好处。然后,就可以编写出无需修改就可以应用于更多情况的代码,即更加泛化的代码。
Java泛型看起来是向这一方向迈进了一步,但是,当要在泛型类型上执行操作(即调用Object
方法之外的方法)时,就会产生问题。擦除强制要求指定可能会用到的泛型类型的边界,以安全地调用代码中的泛型对象上的具体方法。这是对泛化概念的一种明显的限制,因为必须限制泛型类型,使它们继承自特定的类,或者实现特定的接口。在某些情况下,最终可能会使用普通类或普通接口,因为限定边界的泛型可能会和指定类或接口没有任何区别。
某些编程语言提供的一种解决方案称为潜在类型机制或结构化类型机制,即只要求实现某个方法子集,而不是某个特定类或接口,从而放松了这种限制(并且可以产生更加泛化的代码)。正由于此,潜在类型机制使得可以横跨类继承结构,调用不属于某个公共接口的方法。因此,实际上一段代码可以声明:不关心是什么类型,只要可以speak()
和sit()
即可。由于不要求具体类型,因此代码就可以更加泛化。
20.15.2C++中的潜在类型
#include <iostream>
using namespace std;
class Dog {
public:
void speak() {
cout << "Arf!" << endl;
}
void sit() {
cout << "Sitting" << endl;
}
void reproduce() {
}
};
class Robot {
public:
void speak() {
cout << "Click!" << endl;
}
void sit() {
cout << "Clank!" << endl;
}
void oilChange() {
}
};
template<class T> void perform(T anything) {
// 不关心其参数的具体类型,方法签名一样即可
anything.speak();
anything.sit();
}
int main() {
Dog d;
Robot r;
perform(d);
perform(r);
}
20.15.4Java中的直接潜在类型
因为泛型是在这场竞赛的后期才添加到java中,因此没有任何机会可以去实现任何类型的潜在类型机制,因此 java没有对这种特性的支持。所以,初看起来,java的泛型机制比支持潜在类型机制的语言更缺乏泛化性(使用擦除来实现java泛型的实现有时称为第二类泛型类型)。例如,在java8之前如果我们试图用java实现上面的示例,那么就会被强制要求使用一个类或接口,并在边界表达式中指定它:
public interface Performs {
void speak();
void sit();
}
class PerformingDog extends Dog implements Performs {
@Override
public void speak() {
System.out.println("Woof!");
}
@Override
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 DogsAndRobots {
public static void main(String[] args) {
Communicate.perform(new PerformingDog());
Communicate.perform(new Robot());
}
}
// 但是要注意,perform()其实可以不需要使用泛型来工作,可以被简单地指定为接受一个Performs对象
class CommunicateSimply {
static void perform(Performs performer) {
performer.speak();
performer.sit();
}
}
public class SimpleDogsAndRobots {
public static void main(String[] args) {
CommunicateSimply.perform(new PerformingDog());
CommunicateSimply.perform(new Robot());
}
}
20.16对缺乏潜在类型机制的补偿
尽管java不直接支持潜在类型机制,但是这并不意味着泛型代码不能在不同的类型层次结构之间应用。也就是说,仍旧可以创建真正的泛型代码,但是这需要付出一些额外的努力。
20.16.1反射
可以使用的一种方式是反射:
public class LatentReflection {
public static void main(String[] args) {
CommunicateReflectively.perform(new SmartDog());
CommunicateReflectively.perform(new Robot());
CommunicateReflectively.perform(new Mime());
}
}
// Does not implement Performs
class Mime {
public void walkAgainstTheWind() {
}
public void sit() {
System.out.println("Pretending to sit");
}
public void pushInvisibleWalls() {
}
@Override
public String toString() {
return "Mime";
}
}
// Does not implement Performs
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 (SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e) {
throw new RuntimeException(speaker.toString(), e);
}
}
}
20.17Java8中的辅助潜在类型
Java8中的非绑定方法引用能够产生一种潜在类型的形式,以满足创建一段可工作在不相干类型上的代码。因为java最初并不是如此设计,所以结果可想而知,比其他语言中要尴尬一些。但是,至少现在成为了可能,只是缺乏令人惊艳之处。
// PerformingDogA和RobotA与DogsAndRobots中的相同,不同之处在于它们不用继承通用接口Performs
class PerformingDogA extends Dog {
public void speak() {
System.out.println("Woof!");
}
public void sit() {
System.out.println("Sitting");
}
public void reproduce() {
}
}
class RobotA {
public void speak() {
System.out.println("Click!");
}
public void sit() {
System.out.println("Clank!");
}
public void oilChange() {
}
}
class CommunicateA {
/**
* 在没有约束的P上生成,只要可以使用Consumer<P>,它在这里就可以是任何东西,
* 这些Consumer<P>代表不带参数的P方法的未绑定方法引用。因此,可以将任何符合
* 签名的未绑定方法引用传递给CommunicateA.perform()
*/
public static <P> void perform(P performer, Consumer<P> action1,
Consumer<P> action2) {
action1.accept(performer);
action2.accept(performer);
}
}
public class DogsAndRobotMethodReferences {
public static void main(String[] args) {
CommunicateA.perform(new PerformingDogA(),
PerformingDogA::speak, PerformingDogA::sit);
CommunicateA.perform(new RobotA(),
RobotA::speak, RobotA::sit);
CommunicateA.perform(new Mime(),
Mime::walkAgainstTheWind, Mime::pushInvisibleWalls);
}
}
20.17.1使用Suppliers
类的通用方法
通过辅助潜在类型,可以定义其他部分中使用的
Suppliers
类。此类包含使用生成器填充Collection
的工具方法:
public class Suppliers {
// Create a collection and fill it
public static <T, C extends Collection<T>> C create(
Supplier<C> factory, Supplier<T> gen, int n) {
return Stream.generate(gen).limit(n).collect(factory, C::add, C::addAll);
}
// Fill an existing collection
public static <T, C extends Collection<T>> C fill(
C coll, Supplier<T> gen, int n) {
Stream.generate(gen).limit(n).forEach(coll::add);
return coll;
}
// Use an unbound method reference to
// produce a more general method
public static <H, A> H fill(
H holder, BiConsumer<H, A> adder, Supplier<A> gen, int n) {
Stream.generate(gen).limit(n).forEach(a -> adder.accept(holder, a));
return holder;
}
}