上一篇文章 Java神秘的泛型擦除 中提到,泛型擦除会默认擦除到 Object 这个边界,因此可以在泛型对象上调用 Object 的任何方法,而不能实际泛型类型的方法。
泛型边界
然而,泛型是可以通过 extends 指定边界的。通过指定边界,我们就不必再局限于只能调用 Object 的方法,我们现在就可以根据指定的边界做更多有意义的事情。
interface IHello {
void sayHello();
}
class Person<T extends IHello> {
private final T item;
public Person(T t) {
item = t;
}
public void hello() {
item.sayHello();
}
}
泛型类Person的泛型参数通过 extends 指定了一个边界 IHello,那么泛型的类型就被限定为 IHello 接口或者它的实现类,因此在 Person 的 hello()
方法中可以调用泛型对象的 sayHello()
方法。
让我们用一段代码测试下
Person<IHello> p = new Person<>(() -> System.out.println("Hello!!!"));
p.hello(); // 输出为 Hello!!!
我们创建了一个 Person<IHello>
对象,在构造函数中,使用 lambda 表达式创建一个 IHello
接口的实现。可以看到输出结果正是 lambda 表达式的输出。
从这个例子可以发现,虽然 Java 泛型是用擦除实现的,但是可以通过指定泛型边界来弥补一点缺陷。
扩展泛型边界
通过 extends 关键字可以为泛型同时指定多个边界,这样就可以通过泛型对象使用多个边界的功能。下面的例子通过类的继承,扩展泛型的边界。
package com.example.lib;
interface IHello {
void sayHello();
}
class Attribute {
protected String name;
}
class Person<T extends IHello> {
protected final T item;
public Person(T t) {
item = t;
}
public void hello() {
item.sayHello();
}
}
class HelloWithAttr extends Attribute implements IHello {
public HelloWithAttr(String name) {
this.name = name;
}
@Override
public void sayHello() {
System.out.println("Hello~");
}
}
class NamedPerson<T extends Attribute & IHello> extends Person<T> {
public NamedPerson(T t) {
super(t);
}
public String getName() {
return item.name;
}
}
public class Test {
public static void main(String[] args) {
NamedPerson<HelloWithAttr> p = new NamedPerson<>(new HelloWithAttr("David"));
p.hello();
System.out.println(p.getName());
}
}
Person 类的泛型通过 extends 关键字,限定边界为 IHello 接口,这样一来,泛型对象就可以使用 IHello 接口的 sayHello()
方法,这正是在 Hello 类的 hello()
方法中所做的。
NamedPerson 类继承 Person 类,再为泛型添加一个边界 Attribute 。如此一来,泛型对象不止可以使用 IHello 接口的方法,也可以使用 Attribute 的方法和属性。正如在 NamedPerson 类的 getName()
方法中所看到的,通过泛型对象 item
,可以获取 Attribute 类的 name
变量。
注意,通过 extends 扩展边界,如果边界有类和接口,类要放在接口的前面。
结束
与 C++ 比较,我们会觉得 Java 的泛型没那么简单易用,但是我们要掌握 Java 泛型能做什么,不能做什么,才能发挥 Java 泛型的真正力量。虽然说这个力量没有 C++ 那么强大,但是我们不得不面对这样一个现实。