多态是一种面向对象思想的泛化机制。你可以将方法的参数类型设为基类,这样的方法就可以接受任何派生类作为参数,包括暂时还不存在的类。这样的方法更通用,应用范围更广。在类内部也是如此,在任何使用特定类型的地方,基类意味着更大的灵活性。除了 final 类(或只提供私有构造函数的类)任何类型都可被扩展,所以大部分时候这种灵活性是自带的。
Java 的设计者曾说过,这门语言的灵感主要来自 C++ 。尽管如此,学习 Java 时基本不用参考 C++ 。
但是,Java 中的泛型需要与 C++ 进行对比,理由有两个:首先,理解 C++ 模板(泛型的主要灵感来源,包括基本语法)的某些特性,有助于理解泛型的基础理念。同时,非常重要的一点是,你可以了解 Java 泛型的局限是什么,以及为什么会有这些局限。最终的目标是明确 Java 泛型的边界,让你成为一个程序高手。只有知道了某个技术不能做什么,你才能更好地做到所能做的(部分原因是,不必浪费时间在死胡同里)。
第二个原因是,在 Java 社区中,大家普遍对 C++ 模板有一种误解,而这种误解可能会令你在理解泛型的意图时产生偏差。
// generics/Diamond.java
class Bob {}
public class Diamond<T> {
public static void main(String[] args) {
GenericHolder<Bob> h3 = new GenericHolder<>();
h3.set(new Bob());
}
}
// onjava/Tuple2.java
package onjava;
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() + ")";
}
}
// onjava/Tuple3.java
package onjava;
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;
}
}
// onjava/Tuple4.java
package onjava;
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;
}
}
// onjava/Tuple5.java
package onjava;
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;
}
}
演示需要,再定义两个类:
// generics/Amphibian.java
public class Amphibian {}
// generics/Vehicle.java
public class Vehicle {}
// generics/RandomList.java
import java.util.*;
import java.util.stream.*;
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() + " "));
}
}
输出结果:
brown over fox quick quick dog brown The brown lazy brown
// generics/coffee/Coffee.java
package generics.coffee;
public class Coffee {
private static long counter = 0;
private final long id = counter++;
@Override
public String toString() {
return getClass().getSimpleName() + " " + id;
}
}
// generics/coffee/Latte.java
package generics.coffee;
public class Latte extends Coffee {}
// generics/coffee/Mocha.java
package generics.coffee;
public class Mocha extends Coffee {}
// generics/coffee/Cappuccino.java
package generics.coffee;
public class Cappuccino extends Coffee {}
// generics/coffee/Americano.java
package generics.coffee;
public class Americano extends Coffee {}
// generics/coffee/Breve.java
package generics.coffee;
public class Breve extends Coffee {}
// generics/IterableFibonacci.java
// Adapt the Fibonacci class to make it Iterable
import java.util.*;
public class IterableFibonacci
extends Fibonacci implements Iterable<Integer> {
private int n;
public IterableFibonacci(int count) { n = count; }
@Override
public Iterator<Integer> iterator() {
return new Iterator<Integer>() {
@Override
public boolean hasNext() { return n > 0; }
@Override
public Integer next() {
n--;
return IterableFibonacci.this.get();
}
@Override
public void remove() { // Not implemented
throw new UnsupportedOperationException();
}
};
}
public static void main(String[] args) {
for(int i : new IterableFibonacci(18))
System.out.print(i + " ");
}
}
// generics/GenericVarargs.java
import java.util.ArrayList;
import java.util.List;
public class GenericVarargs {
@SafeVarargs
public static <T> List<T> makeList(T... args) {
List<T> result = new ArrayList<>();
for (T item : args)
result.add(item);
return result;
}
public static void main(String[] args) {
List<String> ls = makeList("A");
System.out.println(ls);
ls = makeList("A", "B", "C");
System.out.println(ls);
ls = makeList(
"ABCDEFFHIJKLMNOPQRSTUVWXYZ".split(""));
System.out.println(ls);
}
}
/* Output:
[A]
[A, B, C]
[A, B, C, D, E, F, F, H, I, J, K, L, M, N, O, P, Q, R,
S, T, U, V, W, X, Y, Z]
*/
// onjava/BasicSupplier.java
// Supplier from a class with a no-arg constructor
package onjava;
import java.util.function.Supplier;
public class BasicSupplier<T> implements Supplier<T> {
private Class<T> type;
public BasicSupplier(Class<T> type) {
this.type = type;
}
@Override
public T get() {
try {
// Assumes type is a public class:
return type.newInstance();
} catch (InstantiationException |
IllegalAccessException e) {
throw new RuntimeException(e);
}
}
// Produce a default Supplier from a type token:
public static <T> Supplier<T> create(Class<T> type) {
return new BasicSupplier<>(type);
}
}
此类提供了产生以下对象的基本实现:
是 public 的。 因为 BasicSupplier 在单独的包中,所以相关的类必须具有 public 权限,而不仅仅是包级访问权限。
// generics/CountedObject.java
public class CountedObject {
private static long counter = 0;
private final long id = counter++;
public long id() {
return id;
}
@Override
public String toString() {
return "CountedObject " + id;
}
}
// onjava/Tuple.java
// Tuple library using type argument inference
package onjava;
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);
}
}
对于泛型方法的另一个示例,请考虑由 Set 表示的数学关系。这些被方便地定义为可用于所有不同类型的泛型方法:
// onjava/Sets.java
package onjava;
import java.util.HashSet;
import java.util.Set;
public class Sets {
public static <T> Set<T> union(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<>(a);
result.addAll(b);
return result;
}
public static <T>
Set<T> intersection(Set<T> a, Set<T> b) {
Set<T> result = new HashSet<>(a);
result.retainAll(b);
return result;
}
// Subtract subset from superset:
public static <T> Set<T>
difference(Set<T> superset, Set<T> subset) {
Set<T> result = new HashSet<>(superset);
result.removeAll(subset);
return result;
}
// Reflexive--everything not in the intersection:
public static <T> Set<T> complement(Set<T> a, Set<T> b) {
return difference(union(a, b), intersection(a, b));
}
}
前三个方法通过将第一个参数的引用复制到新的 HashSet 对象中来复制第一个参数,因此不会直接修改参数集合。因此,返回值是一个新的 Set 对象。
// generics/ErasedTypeEquivalence.java
import java.util.*;
public class ErasedTypeEquivalence {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}
/* Output:
true
*/
// generics/LostInformation.java
import java.util.*;
class Frob {}
class Fnorkle {}
class Quark<Q> {}
class Particle<POSITION, MOMENTUM> {}
public class LostInformation {
public static void main(String[] args) {
List<Frob> list = new ArrayList<>();
Map<Frob, Fnorkle> map = new HashMap<>();
Quark<Fnorkle> quark = new Quark<>();
Particle<Long, Double> p = new Particle<>();
System.out.println(Arrays.toString(list.getClass().getTypeParameters()));
System.out.println(Arrays.toString(map.getClass().getTypeParameters()));
System.out.println(Arrays.toString(quark.getClass().getTypeParameters()));
System.out.println(Arrays.toString(p.getClass().getTypeParameters()));
}
}
/* Output:
[E]
[K,V]
[Q]
[POSITION,MOMENTUM]
*/
擦除的代价是显著的。泛型不能用于显式地引用运行时类型的操作中,例如转型、instanceof 操作和 new 表达式。因为所有关于参数的类型信息都丢失了,当你在编写泛型代码时,必须时刻提醒自己,你只是看起来拥有有关参数的类型信息而已。
考虑如下的代码段:
class Foo<T> {
T var;
}
看上去当你创建一个 Foo 实例时:
Foo<Cat> f = new Foo<>();
classFoo 中的代码应该知道现在工作于 Cat 之上。泛型语法也在强烈暗示整个类中所有 T 出现的地方都被替换,就像在 C++ 中一样。但是事实并非如此,当你在编写这个类的代码时,必须提醒自己:“不,这只是一个 Object“。
另外,擦除和迁移兼容性意味着,使用泛型并不是强制的,尽管你可能希望这样:
// generics/ErasureAndInheritance.java
class GenericBase<T> {
private T element;
public void set(T arg) {
element = arg;
}
public T get() {
return element;
}
}
class Derived1<T> extends GenericBase<T> {}
class Derived2 extends GenericBase {} // No warning
// class Derived3 extends GenericBase<?> {}
// Strange error:
// unexpected type
// required: class or interface without bounds
public class ErasureAndInteritance {
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Derived2 d2 = new Derived2();
Object obj = d2.get();
d2.set(obj); // Warning here!
}
}
// generics/SimpleHolder.java
public class SimpleHolder {
private Object obj;
public void set(Object obj) {
this.obj = obj;
}
public Object get() {
return obj;
}
public static void main(String[] args) {
SimpleHolder holder = new SimpleHolder();
holder.set("Item");
String s = (String) holder.get();
}
}
如果用 javap -c SimpleHolder 反编译这个类,会得到如下内容(经过编辑):
public void set(java.lang.Object);
0: aload_0
1: aload_1
2: putfield #2; // Field obj:Object;
5: return
public java.lang.Object get();
0: aload_0
1: getfield #2; // Field obj:Object;
4: areturn
public static void main(java.lang.String[]);
0: new #3; // class SimpleHolder
3: dup
4: invokespecial #4; // Method "<init>":()V
7: astore_1
8: aload_1
9: ldc #5; // String Item
11: invokevirtual #6; // Method set:(Object;)V
14: aload_1
15: invokevirtual #7; // Method get:()Object;
18: checkcast #8; // class java/lang/String
21: astore_2
22: return
set() 和 get() 方法存储和产生值,转型在调用 get() 时接受检查。
现在将泛型融入上例代码中:
// generics/GenericHolder2.java
public class GenericHolder2<T> {
private T obj;
public void set(T obj) {
this.obj = obj;
}
public T get() {
return obj;
}
public static void main(String[] args) {
GenericHolder2<String> holder = new GenericHolder2<>();
holder.set("Item");
String s = holder.get();
}
}
// generics/Erased.java
// {WillNotCompile}
public class Erased<T> {
private final int SIZE = 100;
public void f(Object arg) {
// 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 对象,以在类型表达式中使用它。
// generics/ClassTypeCapture.java
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()));
}
}
/* Output:
true
true
false
true
*/
编译器来保证类型标签与泛型参数相匹配。
创建类型的实例
试图在 Erased.java 中 new T() 是行不通的,部分原因是由于擦除,部分原因是编译器无法验证 T 是否具有默认(无参)构造函数。但是在 C++ 中,此操作自然,直接且安全(在编译时检查):
// generics/InstantiateGenericType.cpp
// C++, not Java!
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() 创建该类型的新对象:
// generics/InstantiateGenericType.java
import java.util.function.Supplier;
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());
}
}
}
/* Output:
Employee
java.lang.InstantiationException: java.lang.Integer
*/
// generics/CreatorGeneric.java
abstract class GenericWithCreate<T> {
final T element;
GenericWithCreate() {
element = 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());
}
}
public class CreatorGeneric {
public static void main(String[] args) {
XCreator xc = new XCreator();
xc.f();
}
}
/* Output:
X
*/
GenericWithCreate 包含 element 字段,并通过无参构造函数强制其初始化,该构造函数又调用抽象的 create() 方法。这种创建方式可以在子类中定义,同时建立 T 的类型。
// generics/ArrayOfGeneric.java
public class ArrayOfGeneric {
static final int SIZE = 100;
static Generic<Integer>[] gia;
@SuppressWarnings("unchecked")
public static void main(String[] args) {
try {
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>();
}
}
/* Output:
[Ljava.lang.Object; cannot be cast to [LGeneric;
Generic[]
*/
问题在于数组会跟踪其实际类型,而该类型是在创建数组时建立的。因此,即使 gia 被强制转换为 Generic<Integer>[] ,该信息也仅在编译时存在(并且没有 @SuppressWarnings 注解,将会收到有关该强制转换的警告)。在运行时,它仍然是一个 Object 数组,这会引起问题。成功创建泛型类型的数组的唯一方法是创建一个已擦除类型的新数组,并将其强制转换。
让我们看一个更复杂的示例。考虑一个包装数组的简单泛型包装器:
// generics/GenericArray.java
public class GenericArray<T> {
private T[] array;
@SuppressWarnings("unchecked")
public GenericArray(int sz) {
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 {
Integer[] ia = gai.rep();
} catch (ClassCastException e) {
System.out.println(e.getMessage());
}
// This is OK:
Object[] oa = gai.rep();
}
}
/* Output:
[Ljava.lang.Object; cannot be cast to
[Ljava.lang.Integer;
*/
和以前一样,我们不能说 T[] array = new T[sz] ,所以我们创建了一个 Object 数组并将其强制转换。
GenericArray.java uses unchecked or unsafe operations.
Recompile with -Xlint:unchecked for details.
在这里,我们收到了一个警告,我们认为这是有关强制转换的。
但是要真正确定,请使用 -Xlint:unchecked 进行编译:
GenericArray.java:7: warning: [unchecked] unchecked cast array = (T[])new Object[sz]; ^ required: T[] found: Object[] where T is a type-variable: T extends Object declared in class GenericArray 1 warning
// generics/GenericArrayWithTypeToken.java
import java.lang.reflect.Array;
public class GenericArrayWithTypeToken<T> {
private T[] array;
@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];
}
// Expose the underlying representation:
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 文献中推荐使用类型标记技术,例如 Gilad Bracha 的论文《Generics in the Java Programming Language》,他指出:“例如,这种用法已广泛用于新的 API 中以处理注解。” 我发现此技术在人们对于舒适度的看法方面存在一些不一致之处;有些人强烈喜欢本章前面介绍的工厂方法。
// generics/CovariantArrays.java
class Fruit {}
class Apple extends Fruit {}
class Jonathan extends Apple {}
class Orange extends Fruit {}
public class CovariantArrays {
public static void main(String[] args) {
Fruit[] fruit = new Apple[10];
fruit[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 Oranges:
fruit[0] = new Orange(); // ArrayStoreException
} catch (Exception e) {
System.out.println(e);
}
}
}
/* Output:
java.lang.ArrayStoreException: Fruit
java.lang.ArrayStoreException: Orange
main() 中的第一行创建了 Apple 数组,并赋值给一个 Fruit 数组引用。这是有意义的,因为 Apple 也是一种 Fruit,因此 Apple 数组应该也是一个 Fruit 数组。
但是,如果实际的数组类型是 Apple[],你可以在其中放置 Apple 或 Apple 的子类型,这在编译期和运行时都可以工作。但是你也可以在数组中放置 Fruit 对象。这对编译器来说是有意义的,因为它有一个 Fruit[] 引用——它有什么理由不允许将 Fruit 对象或任何从 Fruit 继承出来的对象(比如 Orange),放置到这个数组中呢?因此在编译期,这是允许的。然而,运行时的数组机制知道它处理的是 Apple[],因此会在向数组中放置异构类型时抛出异常。
// generics/NonCovariantGenerics.java
// {WillNotCompile}
import java.util.*;
public class NonCovariantGenerics {
// Compile Error: incompatible types:
List<Fruit> flist = new ArrayList<Apple>();
}
尽管你在首次阅读这段代码时会认为“不能将一个 Apple 集合赋值给一个 Fruit 集合”。记住,泛型不仅仅是关于集合,它真正要表达的是“不能把一个涉及 Apple 的泛型赋值给一个涉及 Fruit 的泛型”。如果像在数组中的情况一样,编译器对代码的了解足够多,可以确定所涉及到的集合,那么它可能会留下一些余地。但是它不知道任何有关这方面的信息,因此它拒绝向上转型。然而实际上这也不是向上转型—— Apple 的 List 不是 Fruit 的 List。Apple 的 List 将持有 Apple 和 Apple 的子类型,Fruit 的 List 将持有任何类型的 Fruit。是的,这包括 Apple,但是它不是一个 Apple 的 List,它仍然是 Fruit 的 List。Apple 的 List 在类型上不等价于 Fruit 的 List,即使 Apple 是一种 Fruit 类型。
// generics/GenericsAndCovariance.java
import java.util.*;
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);
}
}
flist 的类型现在是 List<? extends Fruit>,你可以读作“一个具有任何从 Fruit 继承的类型的列表”。然而,这实际上并不意味着这个 List 将持有任何类型的 Fruit。通配符引用的是明确的类型,因此它意味着“某种 flist 引用没有指定的具体类型”。因此这个被赋值的 List 必须持有诸如 Fruit 或 Apple 这样的指定类型,但是为了向上转型为 flist,这个类型是什么没人在意。
List 必须持有一种具体的 Fruit 或 Fruit 的子类型,但是如果你不关心具体的类型是什么,那么你能对这样的 List 做什么呢?如果不知道 List 中持有的对象是什么类型,你怎能保证安全地向其中添加对象呢?就像在 CovariantArrays.java 中向上转型一样,你不能,除非编译器而不是运行时系统可以阻止这种操作的发生。你很快就会发现这个问题。
你可能认为事情开始变得有点走极端了,因为现在你甚至不能向刚刚声明过将持有 Apple 对象的 List 中放入一个 Apple 对象。是的,但编译器并不知道这一点。List<? extends Fruit> 可能合法地指向一个 List<Orange>。一旦执行这种类型的向上转型,你就丢失了向其中传递任何对象的能力,甚至传递 Object 也不行。
另一方面,如果你调用了一个返回 Fruit 的方法,则是安全的,因为你知道这个 List 中的任何对象至少具有 Fruit 类型,因此编译器允许这么做。
编译器有多聪明
现在你可能会猜想自己不能去调用任何接受参数的方法,但是考虑下面的代码:
// generics/CompilerIntelligence.java
import java.util.*;
public class CompilerIntelligence {
public static void main(String[] args) {
List<? extends Fruit> flist = Arrays.asList(new Apple());
Apple a = (Apple) flist.get(0); // No warning
flist.contains(new Apple()); // Argument is 'Object'
flist.indexOf(new Apple()); // Argument is 'Object'
}
}
这里对 contains() 和 indexOf() 的调用接受 Apple 对象作为参数,执行没问题。这是否意味着编译器实际上会检查代码,以查看是否有某个特定的方法修改了它的对象?
// generics/Holder.java
public class Holder<T> {
private T value;
public Holder() {}
public Holder(T val) {
value = val;
}
public void set(T val) {
value = val;
}
public T get() {
return value;
}
@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();
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);
}
// fruit.set(new Apple()); // Cannot call set()
// fruit.set(new Fruit()); // Cannot call set()
System.out.println(fruit.equals(d)); // OK
}
}
/* Output
java.lang.ClassCastException: Apple cannot be cast to Orange
false
*/
Holder 有一个接受 T 类型对象的 set() 方法,一个返回 T 对象的 get() 方法和一个接受 Object 对象的 equals() 方法。正如你所见,如果创建了一个 Holder<Apple>,就不能将其向上转型为 Holder<Fruit>,但是可以向上转型为 Holder<? extends Fruit>。如果调用 get(),只能返回一个 Fruit——这就是在给定“任何扩展自 Fruit 的对象”这一边界后,它所能知道的一切了。如果你知道更多的信息,就可以将其转型到某种具体的 Fruit 而不会导致任何警告,但是存在得到 ClassCastException 的风险。set() 方法不能工作在 Apple 和 Fruit 上,因为 set() 的参数也是"? extends Fruit",意味着它可以是任何事物,编译器无法验证“任何事物”的类型安全性。
但是,equals() 方法可以正常工作,因为它接受的参数是 Object 而不是 T 类型。因此,编译器只关注传递进来和要返回的对象类型。它不会分析代码,以查看是否执行了任何实际的写入和读取操作。
// generics/SuperTypeWildcards.java
import java.util.*;
public class SuperTypeWildcards {
static void writeTo(List<? super Apple> apples) {
apples.add(new Apple());
apples.add(new Jonathan());
// apples.add(new Fruit()); // Error
}
}
参数 apples 是 Apple 的某种基类型的 List,这样你就知道向其中添加 Apple 或 Apple 的子类型是安全的。但是因为 Apple 是下界,所以你知道向这样的 List 中添加 Fruit 是不安全的,因为这将使这个 List 敞开口子,从而可以向其中添加非 Apple 类型的对象,而这是违反静态类型安全的。 下面的示例复习了一下逆变和通配符的的使用:
// generics/GenericReading.java
import java.util.*;
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();
}
}
readExact() 方法使用了精确的类型。如果使用这个没有任何通配符的精确类型,就可以向 List 中写入和读取这个精确类型。另外,对于返回值,静态的泛型方法 readExact() 可以有效地“适应”每个方法调用,并能够从 List<Apple> 中返回一个 Apple ,从 List<Fruit> 中返回一个 Fruit ,就像在 f1() 中看到的那样。因此,如果可以摆脱静态泛型方法,那么在读取时就不需要协变类型了。 然而对于泛型类来说,当你创建这个类的实例时,就要为这个类确定参数。就像在 f2() 中看到的,fruitReader 实例可以从 List<Fruit> 中读取一个 Fruit ,因为这就是它的确切类型。但是 List<Apple> 也应该产生 Fruit 对象,而 fruitReader 不允许这么做。 为了修正这个问题,CovariantReader.readCovariant() 方法将接受 List<?extends T> ,因此,从这个列表中读取一个 T 是安全的(你知道在这个列表中的所有对象至少是一个 T ,并且可能是从 T 导出的某种对象)。在 f3() 中,你可以看到现在可以从 List<Apple> 中读取 Fruit 了。
但是,当你拥有的全都是无界通配符时,就像在 Map<?,?> 中看到的那样,编译器看起来就无法将其与原生 Map 区分开了。另外, UnboundedWildcards1.java 展示了编译器处理 List<?> 和 List<? extends Object> 是不同的。 令人困惑的是,编译器并非总是关注像 List 和 List<?> 之间的这种差异,因此它们看起来就像是相同的事物。事实上,因为泛型参数擦除到它的第一个边界,因此 List<?> 看起来等价于 List<Object> ,而 List 实际上也是 List<Object> ——除非这些语句都不为真。List 实际上表示“持有任何 Object 类型的原生 List ”,而 List<?> 表示“具有某种特定类型的非原生 List ,只是我们不知道类型是什么。” 编译器何时才会关注原生类型和涉及无界通配符的类型之间的差异呢?下面的示例使用了前面定义的 Holder<T> 类,它包含接受 Holder 作为参数的各种方法,但是它们具有不同的形式:作为原生类型,具有具体的类型参数以及具有无界通配符参数:
// generics/Wildcards.java
// Exploring the meaning of wildcards
public class Wildcards {
// Raw argument:
static void rawArgs(Holder holder, Object arg) {
//- holder.set(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
// 1 warning
// 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:
static void unboundedArg(Holder<?> holder, Object arg) {
//- holder.set(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 ?
// 1 error
// 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) {
//- holder.set(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
// 1 error
return holder.get();
}
static <T> void wildSupertype(Holder<? super T> holder, T arg) {
holder.set(arg);
//- T t = holder.get();
// 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
// 1 error
// OK, but type information is lost:
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(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);
// 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>)
// warning: [unchecked] unchecked conversion
// 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>)
// 2 warnings
Long r2 = exact1(qualified);
Object r3 = exact1(unbounded); // Must return Object
Long r4 = exact1(bounded);
//- Long r5 = exact2(raw, lng);
// warning: [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)
// warning: [unchecked] unchecked conversion
// Long r5 = exact2(raw, lng);
// ^
// required: Holder<T>
// found: Holder
// where T is a type-variable:
// T extends Object declared in
// method <T>exact2(Holder<T>,T)
// 2 warnings
Long r6 = exact2(qualified, lng);
//- Long r7 = exact2(unbounded, 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
// 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 Object from capture of ?
// 1 error
//- Long r8 = exact2(bounded, 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
// 1 error
//- Long r9 = wildSubtype(raw, 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)
// 2 warnings
Long r10 = wildSubtype(qualified, lng);
// OK, but can only return Object:
Object r11 = wildSubtype(unbounded, lng);
Long r12 = wildSubtype(bounded, lng);
//- wildSupertype(raw, 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)
// 2 warnings
wildSupertype(qualified, lng);
//- wildSupertype(unbounded, 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 ?
// 1 error
//- wildSupertype(bounded, lng);
// error: method wildSupertype in class Wildcards
// cannot be applied to given types;
// wildSupertype(bounded, 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 Long from capture of
// ? extends Long
// 1 error
}
}
// generics/CaptureConversion.java
public class CaptureConversion {
static <T> void f1(Holder<T> holder) {
T t = holder.get();
System.out.println(t.getClass().getSimpleName());
}
static void f2(Holder<?> holder) {
f1(holder); // Call with captured type
}
@SuppressWarnings("unchecked")
public static void main(String[] args) {
Holder raw = new Holder<>(1);
f1(raw);
// warning: [unchecked] unchecked method invocation:
// method f1 in class CaptureConversion
// is applied to given types
// f1(raw);
// ^
// required: Holder<T>
// found: Holder
// where T is a type-variable:
// T extends Object declared in
// method <T>f1(Holder<T>)
// warning: [unchecked] unchecked conversion
// f1(raw);
// ^
// required: Holder<T>
// found: Holder
// where T is a type-variable:
// T extends Object declared in
// method <T>f1(Holder<T>)
// 2 warnings
f2(raw); // No warnings
Holder rawBasic = new Holder();
rawBasic.set(new Object());
// warning: [unchecked] unchecked call to set(T)
// as a member of the raw type Holder
// rawBasic.set(new Object());
// ^
// where T is a type-variable:
// T extends Object declared in class Holder
// 1 warning
f2(rawBasic); // No warnings
// Upcast to Holder<?>, still figures it out:
Holder<?> wildcarded = new Holder<>(1.0);
f2(wildcarded);
}
}
/* Output:
Integer
Integer
Object
Double
*/
// generics/ByteSet.java
import java.util.*;
public class ByteSet {
Byte[] possibles = { 1,2,3,4,5,6,7,8,9 };
Set<Byte> mySet = new HashSet<>(Arrays.asList(possibles));
// But you can't do this:
// Set<Byte> mySet2 = new HashSet<>(
// Arrays.<Byte>asList(1,2,3,4,5,6,7,8,9));
}
// generics/GenericCast.java
import java.util.*;
import java.util.stream.*;
class FixedSizeStack<T> {
private final int size;
private Object[] storage;
private int index = 0;
FixedSizeStack(int size) {
this.size = size;
storage = new Object[size];
}
public void push(T item) {
if(index < size)
storage[index++] = item;
}
@SuppressWarnings("unchecked")
public T pop() {
return index == 0 ? null : (T)storage[--index];
}
@SuppressWarnings("unchecked")
Stream<T> stream() {
return (Stream<T>)Arrays.stream(storage);
}
}
public class GenericCast {
static String[] letters = "ABCDEFGHIJKLMNOPQRS".split("");
public static void main(String[] args) {
FixedSizeStack<String> strings =
new FixedSizeStack<>(letters.length);
Arrays.stream("ABCDEFGHIJKLMNOPQRS".split(""))
.forEach(strings::push);
System.out.println(strings.pop());
strings.stream()
.map(s -> s + " ")
.forEach(System.out::print);
}
}
/* Output:
S
A B C D E F G H I J K L M N O P Q R S
*/
NeedCasting.java uses unchecked or unsafe operations.
Recompile with -Xlint:unchecked for details.
And if you follow the instructions and recompile with -
Xlint:unchecked :(如果遵循这条指示,使用-Xlint:unchecked来重新编译:)
NeedCasting.java:10: warning: [unchecked] unchecked cast
List<Widget> shapes = (List<Widget>)in.readObject();
required: List<Widget>
found: Object
1 warning
// generics/ClassCasting.java
import java.io.*;
import java.util.*;
public class ClassCasting {
@SuppressWarnings("unchecked")
public void f(String[] args) throws Exception {
ObjectInputStream in = new ObjectInputStream(
new FileInputStream(args[0]));
// Won't Compile:
// List<Widget> lw1 =
// List<>.class.cast(in.readObject());
List<Widget> lw2 = List.class.cast(in.readObject());
}
}
但是,不能转型到实际类型( List<Widget> )。也就是说,不能声明:
List<Widget>.class.cast(in.readobject())
甚至当你添加一个像下面这样的另一个转型时:
(List<Widget>)List.class.cast(in.readobject())
仍旧会得到一个警告。
重载
下面的程序是不能编译的,即使它看起来是合理的:
// generics/UseList.java
// {WillNotCompile}
import java.util.*;
public class UseList<W, T> {
void f(List<T> v) {}
void f(List<W> v) {}
}
因为擦除,所以重载方法产生了相同的类型签名。
因而,当擦除后的参数不能产生唯一的参数列表时,你必须提供不同的方法名:
// generics/UseList2.java
import java.util.*;
public class UseList2<W, T> {
void f1(List<T> v) {}
void f2(List<W> v) {}
}
幸运的是,编译器可以检测到这类问题。
基类劫持接口
假设你有一个实现了 Comparable 接口的 Pet 类:
// generics/ComparablePet.java
public class ComparablePet implements Comparable<ComparablePet> {
@Override
public int compareTo(ComparablePet o) {
return 0;
}
}
// generics/RestrictedComparablePets.java
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;
}
}
// generics/CuriouslyRecurringGeneric.java
class GenericType<T> {}
public class CuriouslyRecurringGeneric
extends GenericType<CuriouslyRecurringGeneric> {}
这可以按照 Jim Coplien 在 C++ 中的古怪的循环模版模式的命名方式,称为古怪的循环泛型(CRG)。“古怪的循环”是指类相当古怪地出现在它自己的基类中这一事实。 为了理解其含义,努力大声说:“我在创建一个新类,它继承自一个泛型类型,这个泛型类型接受我的类的名字作为其参数。”当给出导出类的名字时,这个泛型基类能够实现什么呢?好吧,Java 中的泛型关乎参数和返回类型,因此它能够产生使用导出类作为其参数和返回类型的基类。它还能将导出类型用作其域类型,尽管这些将被擦除为 Object 的类型。下面是表示了这种情况的一个泛型类:
// generics/BasicHolder.java
public class BasicHolder<T> {
T element;
void set(T arg) { element = arg; }
T get() { return element; }
void f() {
System.out.println(element.getClass().getSimpleName());
}
}
// generics/Unconstrained.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
class Other {}
class BasicOther extends BasicHolder<Other> {}
public class Unconstrained {
public static void main(String[] args) {
BasicOther b = new BasicOther();
BasicOther b2 = new BasicOther();
b.set(new Other());
Other other = b.get();
b.f();
}
}
/* Output:
Other
*/
// generics/SelfBounding.java
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 cannot force the idiom:
class F extends SelfBounded {}
public class SelfBounding {
public static void main(String[] args) {
A a = new A();
a.set(new A());
a = a.set(new A()).get();
a = a.get();
C c = new C();
c = c.setAndGet(new C());
}
}
自限定所做的,就是要求在继承关系中,像下面这样使用这个类:
class A extends SelfBounded<A>{}
这会强制要求将正在定义的类当作参数传递给基类。
自限定的参数有何意义呢?它可以保证类型参数必须与正在被定义的类相同。正如你在 B 类的定义中所看到的,还可以从使用了另一个 SelfBounded 参数的 SelfBounded 中导出,尽管在 A 类看到的用法看起来是主要的用法。对定义 E 的尝试说明不能使用不是 SelfBounded 的类型参数。 遗憾的是, F 可以编译,不会有任何警告,因此自限定惯用法不是可强制执行的。如果它确实很重要,可以要求一个外部工具来确保不会使用原生类型来替代参数化类型。 注意,可以移除自限定这个限制,这样所有的类仍旧是可以编译的,但是 E 也会因此而变得可编译:
// generics/NotSelfBounded.java
public class NotSelfBounded<T> {
T element;
NotSelfBounded<T> set(T arg) {
element = arg;
return this;
}
T get() { return element; }
}
class A2 extends NotSelfBounded<A2> {}
class B2 extends NotSelfBounded<A2> {}
class C2 extends NotSelfBounded<C2> {
C2 setAndGet(C2 arg) {
set(arg);
return get();
}
}
class D2 {}
// Now this is OK:
class E2 extends NotSelfBounded<D2> {}
// generics/SelfBoundingMethods.java
// (c)2017 MindView LLC: see Copyright.txt
// We make no guarantees that this code is fit for any purpose.
// Visit http://OnJava8.com for more book information.
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());
}
}
// generics/GenericsAndReturnTypes.java
interface GenericGetter<T extends GenericGetter<T>> {
T get();
}
interface Getter extends GenericGetter<Getter> {}
public class GenericsAndReturnTypes {
void test(Getter g) {
Getter result = g.get();
GenericGetter gg = g.get(); // Also the base type
}
}
注意,这段代码不能编译,除非是使用囊括了协变返回类型的 Java 5。
然而,在非泛型代码中,参数类型不能随子类型发生变化:
// generics/OrdinaryArguments.java
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 OrdinaryArguments {
public static void main(String[] args) {
Base base = new Base();
Derived derived = new Derived();
DerivedSetter ds = new DerivedSetter();
ds.set(derived);
// Compiles--overloaded, not overridden!:
ds.set(base);
}
}
/* Output:
DerivedSetter.set(Derived)
OrdinarySetter.set(Base)
*/
在 C++ 中,使用多重继承的最大理由,就是为了使用混型。但是,对于混型来说,更有趣、更优雅的方式是使用参数化类型,因为混型就是继承自其类型参数的类。在 C++ 中,可以很容易地创建混型,因为 C++ 能够记住其模版参数的类型。 下面是一个 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:
test string 1 1452987605 1
test string 2 1452987605 2
*/
// generics/decorator/Decoration.java
// {java generics.decorator.Decoration}
package generics.decorator;
import java.util.*;
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; }
}
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
}
}
// generics/DynamicProxyMixin.java
import java.lang.reflect.*;
import java.util.*;
import onjava.*;
import static onjava.Tuple.*;
class MixinProxy implements InvocationHandler {
Map<String, Object> delegatesByMethod;
@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));
}
}
public class DynamicProxyMixin {
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:
Hello
1494331653339
1
*/
因为只有动态类型而不是静态类型才包含所有的混入类型,因此这仍旧不如 C++ 的方式好,因为可以在具有这些类型的对象上调用方法之前,你被强制要求必须先将这些对象向下转型到恰当的类型。但是,它明显地更接近于真正的混型。 为了让 Java 支持混型,人们已经做了大量的工作朝着这个目标努力,包括创建了至少一种附加语言( Jam 语言),它是专门用来支持混型的。
Go 没有 class 关键字,但是可以使用上述形式创建等效的基本类:它通常不定义为类,而是定义为 struct ,在其中定义数据字段(此处不存在)。 对于每种方法,都以 func 关键字开头,然后(为了将该方法附加到您的类上)放在括号中,该括号包含对象引用,该对象引用可以是任何标识符,但是我在这里使用 this 来提醒您,就像在 C ++ 或 Java 中的 this 一样。 然后,在Go中像这样定义其余的函数。
// generics/SimpleQueue.java
// A different kind of Iterable collection
import java.util.*;
public class SimpleQueue<T> implements Iterable<T> {
private LinkedList<T> storage = new LinkedList<>();
public void add(T t) { storage.offer(t); }
public T get() { return storage.poll(); }
@Override
public Iterator<T> iterator() {
return storage.iterator();
}
}
// onjava/Suppliers.java
// A utility to use with Suppliers
package onjava;
import java.util.*;
import java.util.function.*;
import java.util.stream.*;
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;
}
}
你可以从 《Adding Wildcards to the Java Programming Language》中学到更多关于通配符的知识,作者是 Torgerson、Ernst、Hansen、von der Ahe、Bracha 和 Gafter,地址是 http://www.jot.fm/issues/issue_2004_12/article5。
Neal After 对于 Java 问题(尤其是擦除)的看法可以从这里找到:http://www.infoq.com/articles/neal-gafter-on-java。