Java 泛型

1. 定义简单泛型类

public class Pair<T> {
    private T first;
    private T second;

    public Pair() { first = null ; second = null ; }
    public Pair(T first, T second) { this.first = first; this.second = second; }

    public T getFirst() { return first; }
    public T getSecond() { return second; }

    public void setFirst(T newValue) { first = newValue; }
    public void setSecond(T newValue) { second = newValue; }
}

Pair 类引人了一个类型变量 T,用尖括号 ( < > ) 括起来,并放在类名的后面。
泛型类可以有多个类型变量,例如:public class Pair<T, U> 。

2. 泛型方法

class ArrayAlg
{
    public static <T> T getMiddle(T... a)
    {
        return a[a.length / 2];
    }
}

泛型方法可以定义在普通类中,也可以定义在泛型类中。注意,类型变量放在修饰符(这里是 public static) 的后面,返回类型的前面。
当调用一个泛型方法时,在方法名前的尖括号中放入具体的类型:

String middle = ArrayAlg.<String>getMiddle("JohnM", "Q.", "Public");

实际大多数情况下, <String> 可以省略,编译器能够自己推断出参数的类型。

String middle = ArrayAlg.getMiddle("JohnM", "Q.", "Public");

3. 类型变量限定

泛型类或方法有时需要对类型变量加以约束,可以通过对类型变量 T 设置限定实现这一点:
例如:<T extends BoundingType>,表示 T 是绑定类型(BoundingType)的子类型。

public static <T extends Comparable> T min (T[] a)

一个类型变量可以有多个限定类型,用“&” 分隔,例如:
T extends Comparable & Serializable, U extends Comparable & Serializable
其中多个类型变量用逗号来分隔。

限定类型中至多有一个类,如果用一个类作为限定,它必须是限定列表中的第一个。

4. 类型擦除

JVM(虚拟机)没有泛型类型对象,所有对象都属于普通类。

无论何时定义一个泛型类型,都自动提供了一个相应的原始类型。原始类型的名字就是删去类型参数后的泛型类名。擦除类型变量, 并替换为限定类型(无限定的变量用 Object )。

例如, Pair<T> 的原始类型如下所示:

public class Pair
{
    private Object first;
    private Object second;
    
    public Pair(Object first, Object second) {
        this.first = first;
        this.second = second;
    }
    public Object getFirst() { return first; }
    public Object getSecond() { return second; }
    
    public void setFirst(Object newValue) { first = newValue; }
    public void setSecond(Object newValue) { second = newValue; }
}

因为 T 是一个无限定的变量, 所以直接用 Object 替换。
原始类型用第一个限定的类型变量来替换, 如果没有给定限定就用 Object 替换。

假定声明了一个不同的类型:

public class Interval <T extends Comparable & Serializable> implements Serializable {
    private T lower;
    private T upper;

    public Interval (T first, T second) { }
}

原始类型 Interval 如下所示:

public class Interval implements Serializable
{
    private Comparable lower;
    private Comparable upper;

    public Interval (Comparable first, Comparable second) { }
}

5. 编译器如何翻译泛型表达式

当程序调用泛型方法时,如果擦除返回类型,编译器插入强制类型转换。
例如:

Pair<Employee> buddies = new Pair<Employee>();
Employee buddy = buddies.getFirst();

擦除 getFirst 的返回类型后将返回 Object 类型。编译器自动插人 Employee 的强制类型转换。
也就是说,编译器把这个方法调用翻译为两条虚拟机指令:

  • 对原始方法 Pair.getFirst 的调用。
  • 将返回的 Object 类型强制转换为 Employee 类型。

当存取一个泛型域时也要插人强制类型转换。假设 Pair 类的 first 域和 second 域都是公有的,表达式:

Employee buddy = buddies.first;

也会在结果字节码中插人强制类型转换。

6. 编译器如何翻译泛型方法

类型擦除也会出现在泛型方法中。程序员通常认为下述的泛型方法

public static <T extends Comparable> T min(T[] a)

是一个完整的方法族,而擦除类型之后,只剩下一个方法:

public static Comparable min(Comparable[] a)

注意,类型参数 T 已经被擦除了, 只留下了限定类型 Comparable。

方法的擦除带来了两个复杂问题。看一看下面这个示例:

class DateInterval extends Pair<LocalDate> {
    public void setSecond(LocalDate second) {
        if (second.compareTo(getFirst()) >= 0) {
            super.setSecond(second);
        }
    }
}

Datelnterval类中用 setSecond 方法来实现 second 的值永远不小 first 的值。
这个类擦除后变成:

class DateInterval extends Pair {
    public void setSecond(LocalDate second) { }
}

令人感到奇怪的是,存在另一个从 Pair 继承的 setSecond方法,即

public void setSecond(Object second) { }

这显然是一个不同的方法,因为它有一个不同类型的参数 Object, 而不是 LocalDate。
然而,不应该不一样。考虑下面的语句序列:

DateInterval interval = new Datelnterval();
Pair<LocalDate> pair = interval; // OK assignment to superclass
pair.setSecond(aDate);

这里,希望对 setSecond 的调用具有多态性,并调用最合适的那个方法。由于 pair 引用 DateInterval 对象,所以应该调用 DateInterval.setSecond。问题在于类型擦除与多态发生了冲突。要解决这个问题, 就需要编译器在 DateInterval 类中生成一个桥方法(bridge method):

public void setSecond(Object second) { setSecond( (LocalDate) second ); }

要想了解它的工作过程,请仔细地跟踪下列语句的执行:

pair.setSecond(aDate)

变量 pair 已经声明为类型 Pair<LocalDate>, 并且这个类型只有一个简单的方法叫setSecond,即 setSecond(Object) 。虚拟机用 pair 引用的对象调用这个方法。这个对象是 DateInterval 类型的,因而将会调用 DateInterval.setSecond(Object) 方法。这个方法是合成的桥方法。它调用 DateInterval.setSecond(LocalDate), 这正是我们所期望的操作效果。
桥方法可能会变得十分奇怪。假设 DateInterval 方法也覆盖了 getSecond 方法:

class DateInterval extends Pair<LocalDate> {
    public LocalDate getSecond() {
        return (LocalDate) super.getSecond();
    }
}

在 DateInterval 类中,有两个 getSecond 方法:

LocalDate getSecond() // defined in DateInterval
Object getSecond() // overrides the method defined in Pair to call the first method

不能这样编写 Java 代码(在这里,具有相同参数类型的两个方法是不合法的),它们都没有参数。
但是,在虚拟机中,用参数类型和返回类型确定一个方法。因此, 编译器可能产生两个仅返回类型不同的方法字节码,虚拟机能够正确地处理这一情况。

总之,需要记住有关 Java 泛型转换的事实:

  • 虚拟机中没有泛型,只有普通的类和方法。
  • 所有的类型参数都用它们的限定类型替换。
  • 桥方法被合成来保持多态。
  • 为保持类型安全性,必要时插人强制类型转换。

7. 通配符

7.1 通配符类型

Pair<? extends Employee>
表示 Pair 类的所有泛型,其类型参数是 Employee 的子类。 如 Pair<Manager>, 但不是 Pair<String>。

假设要编写一个打印雇员对的方法,像这样:

public static void printBuddies(Pair<Employee> p) { }

不能将 Pair<Manager> 传递给这个方法,解决的方法很简单,使用通配符类型:

public static void printBuddies(Pair<? extends Employee> p) { }

类型 Pair<Manager>Pair<? extends Employee> 的子类型。

引入有限定的通配符的关键点,例如:
类型 Pair<? extends Employee> 其方法是
? extends Employee getFirst()
void setFirst(? extends Employee)
其中 setFirst 方法不能被调用,编译器只知道需要某个 Employee 的子类型,但不知道具体是什么类型。它拒绝传递任何特定的类型,毕竟 ?不能用来匹配。
但使用 getFirst 就不存在这个问题:将 getFirst 的返回值赋给一个 Employee 的引用完全合法。

7.2 通配符的超类型限定

通配符限定与类型变量限定十分类似,但是,通配符限定还有一个附加的能力,即可以指定一个超类型限定(supertype bound), 如下所示:
? super Manager
这个通配符限制为 Manager 的所有超类型。
带有超类型限定的通配符可以为方法提供参数, 但不能使用返回值。
例如, Pair<? super Manager> 有方法:
void setFirst(? super Manager)
? super Manager getFirst()
编译器无法知道 setFirst 方法的具体类型,因此调用这个方法时不能接受类型为 Employee 或 Object 的参数。只能传递 Manager 类型的对象,或者某个子类型对象。另外,如果调用 getFirst, 不能保证返回对象的类型,只能把它赋给一个 Object。
注:Employee 为父类,Manager 为子类。

7.3 无限定通配符

还可以使用无限定的通配符,例如,Pair<?>。初看起来,这好像与原始的 Pair 类型一样。 实际上, 有很大的不同。类型 Pair<?> 有以下方法:
? getFirst()
void setFirst(?)
getFirst 的返回值只能赋给一个 Object。setFirst 方法不能被调用, 甚至不能用 Object 调用。
Pair<?> 和 Pair 本质的不同在于: 可以用任意 Object 对象调用原始 Pair 类的 setObject 方法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值