Java8基础知识泛型

Posted Aries99C

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8基础知识泛型相关的知识,希望对你有一定的参考价值。

泛型

在增加泛型类前,泛型程序设计是用继承实现的,要将方法参数和域的类型设计为Object,通过强制类型转换实现设计。由于Object在编译阶段几乎不会报错,所以很难通过静态类型检查发现这种设计下隐藏的错误。

使用类型参数后,通过编译器就可以检测提供的参数类型是否错误,使程序具有更好的可读性和安全性。

但实现泛型类也存在一定的困难,因为设计的方法同样要对所有的类型都能够编译且正确运行。

// 已知在ArrayList中设计addAll方法来向当前list中添加另一个list的所有元素
ArrayList<Manager> managerList = new ArrayList<>();
ArrayList<Employee> employeeList = new ArrayList<>();
// 可以将所有的Manager添加到Employee中
// 但未必能将所有的Employee添加到Manager中

因此需要通配符的存在,来允许将泛型向上转型

类型参数

类型参数可以用于定义泛型类,也可以定义泛型方法。

当定义泛型类时,类型参数放在类名后;当定义泛型方法时,类型参数放在方法的修饰符后。

// 泛型类
public class TClass<T> {
	// 泛型方法
    // 这里E表示该方法的返回类型或参数类型可以是T以外的其他类型
    public static <E> E EMethod(E... e) {
        // ...
    }
    // 这里T指泛型类定义中的T
    public static T TMethod(T... t) {
        // ...
    }
    // 可以发现上述两个方法的区别实际在于是否有<E>定义
}

当调用泛型方法时,应当在方法名前加添加<E>,避免多种类型对于方法的调用都是合法的,从而导致错误。

类型变量的限定

当某个类型变量调用了某种方法,但无法确定是否所有了类都实现这种方法时,可以在方法名前添加<T extends SomeInterface>来限定其参数的类型,避免没有实现特定方法的类作为参数被传递。当同时存在多个限定时,用&来进行分割。

泛型代码和虚拟机

所有的对象对于虚拟机来说都是普通类,编译器会将泛型类通过一系列操作(擦除类型+强制转换)转换为普通类,然后交由虚拟机执行。

具体的转换机制如下:

1.类型擦除

对于定义的泛型类,编译器会提供相应的原始类(删除类定义上的类型参数,方法中的类型变量用提供的限定类型代替,若无限定则用Object代替;若限定类型有多个,则选用第一个限定类型)。

注:如果调用了未被选用的限定类型中的方法,编译器在必要时会插入强制类型转换来确保方法能够被调用。因此,应该将标签接口(没有方法的接口)放在限定边界列表的末尾。

2.翻译泛型表达式

由于擦除类型后变量为Object类型,编译器将自动插入强制类型转换来改变泛型表达式的返回类型或者泛型域的类型。

3.翻译泛型方法

如果单纯地使用类型擦除和和强制转换,可能导致多态冲突

// 定义泛型类Pair的子类,使用特定的LocalDate类实现
class DateInterval extends Pair<LocalDate> {
    pubic void setSecond(LocalDate second) {
        if (second.compareTo(getFirst()) >= 0)
            super.setSecond(second);
    }
}

由于上述类已经用特定的类实现,所以擦除后并不会擦去方法中的LocalDate

// 擦除后原始类
class DateInterval extends Pair {
    // 覆盖Pair中的setSecond方法
    public void setSecond(LocalDate second) {
        // ...
    }
}

同时,存在另一个从Pair中继承的setSecond方法。

public void setSecond(Object second)

考虑如下代码:

DateInterval interval = new DateInterval(...);
// 将interval的引用传递给pair
Pair<LocalDate> pair = interval;
// 类型擦除后,即使插入强制类型转换,aDate仍然同时满足Object类型和DateInterval类型
pair.setSecond(aDate);

对于上述问题,编译器在DateInterval类中生成一个桥方法

// 用该方法覆盖原始方法中的setSecond,避免多态冲突
public void setSecond(Object second) {setSeond((Date) second);}

利用桥方法,就可以正确地调用setSecond(LocalDate second)而不是setSecond(Object second)。然而,假设DateInterval也覆盖了getSecond方法,那么在DateInterval类中就会同时包含两个无参但返回类型不同的getSecond方法(其中一个是桥方法)。对于这种情况,编译器可能产生两个仅返回类型不同的方法的字节码,以使虚拟机能够正确地分辨两个方法。

调用遗留代码

当调用某些遗留代码时,由于遗留代码没有使用泛型设计,可能导致编译器无法确定遗留代码的行为而发出警告。在查看警告确保该调用不存在问题后。可以利用注解使其消失。注解应放在生成警告的代码所在的方法前。

// 确定代码不会产生错误,利用注解取消警告
@SuppressWarnings("unchecked")
Dictionary<Integer, Components> labelTable = slider.getLabelTable();
// 也可以标注包含这行代码的方法,但会关闭该方法中所有代码的检查

以上是关于Java8基础知识泛型的主要内容,如果未能解决你的问题,请参考以下文章

Java8基础知识泛型的约束与局限性

java8泛型

201621123037 《Java程序设计》第9周学习总结

具有泛型类型的 Java 8 方法参考

操作 Java 泛型:泛型在继承方面体现与通配符使用

java8 泛型声明 The diamond operator ("<>") should be used