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();
// 也可以标注包含这行代码的方法,但会关闭该方法中所有代码的检查

原文地址:https://www.cnblogs.com/aries99c/p/12614624.html

时间: 2024-08-30 00:40:34

Java8基础知识(九)泛型的相关文章

黑马程序员——集合基础知识(泛型)

集合:泛型基础知识 泛型.(泛型就是类型参数化,默认的时object,虽然不用强制类型转换,这个就要你自己去写特性方法,比如compareto是string的特有方法吧,你可以写但是父类肯定调用不了) itnex t对象都是obj要使用对象的特性功能必须强.编译的时候没问题,因为都不知道你会传什么对象,你橙子也可以当作apple来传,设计的时候并不知道! 泛型作用.1.用于解决安全问题.运行时期出现的问题classcastexception转移到编译时期.2.迭代器里面的itnext()不用强转

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

泛型的约束与局限性 由于泛型是通过类型擦除.强制类型转换和桥方法来实现的,所以存在某些局限(大多来自于擦除). 不能使用基本类型实例化类型参数 类型参数都是类,要用包装器将基本类型包装才可以作为类型参数(原因在于擦除类型后Object类不能存储基本类型的值).当包装器类不能接受类型参数替换时,可以使用独立的类和方法进行处理. 运行时类型查询只适用于原始类型 由于虚拟机中的对象都有特定的原始类型,所以类型查询只能查询原始类型. // 只能测试a是否为某种Pair类型 if (a instanceo

C#基础知识之泛型

泛型在c#中有很重要的位置,对于写出高可读性,高性能的代码有着关键的作用. 其实官方文档说明的很详细,我这边算是做个记录吧 一.什么是泛型? 泛型是 2.0 版 C# 语言和公共语言运行库 (CLR) 中的一个非常重要的新功能. 泛型(Generic) 允许您延迟编写类或方法中的编程元素的数据类型的规范,直到实际在程序中使用它的时候.换句话说,泛型允许您编写一个可以与任何数据类型一起工作的类或方法.您可以通过数据类型的替代参数编写类或方法的规范.当编译器遇到类的构造函数或方法的函数调用时,它会生

(整理)C#基础知识_泛型的实现

本文是截取自MSDN的文章部分,方便自己查看,原文地址:https://msdn.microsoft.com/zh-cn/library/ms379564(VS.80).aspx 泛型实现 表面上,C# 泛型的语法看起来与 C++ 模板类似,但是编译器实现和支持它们的方式存在重要差异.正如您将在后文中看到的那样,这对于泛型的使用方式具有重大意义. 注 在本文中,当提到 C++ 时,指的是传统 C++,而不是带有托管扩展的 Microsoft C++. 与 C++ 模板相比,C# 泛型可以提供增强

Java基础知识(九)

一.计算机在Eclipse中存储中文方式(1)平台默认编码集:GBK,一个中文对应两个字节.(2)第一个字节:一定是负数:第二个字节:一般是负数,可能也会是正数,不影响结果.(3)例如: public class Demo { public static void main(String[] args) { //定义一个字符串 String str = "大脑袋" ; //转成字节数组 byte[] bys = str.getBytes() ; System.out.println(A

Android学习之基础知识九 — 数据存储(持久化技术)之使用LitePal操作数据库

上一节学习了使用SQLiteDatabase来操作SQLite数据库的方法,接下来我们开始接触第一个开源库:LitePal.LitePal是一款开源的Android数据库框架,它采用了对象关系映射(ORM)的模式,并将我们平时开发最常用到的一些数据库功能进行了封装,使得不用编写一行SQL语句就可以完成各种建表和增删改查的操作,LitePal的项目主页上也有详细的使用文档,地址是: https://github.com/LitePalFramework/LitePal 一.配置LitePal 要在

ASP.NET Core 2.2 基础知识(九) 使用托管服务实现后台任务

在 ASP.NET Core 中,后台任务作为托管服务实现.托管服务是一个类,而且必须实现 IHostedService 接口,该接口定义了两个方法: StartAsync(CancellationToken cancellationToken)  该方法包含启动后台任务的逻辑,当启动服务器并触发 IApplicationLifetime.ApplicationStarted 后调用该方法. StopAsync(CancellationToken cancellationToken)主机正常关闭

Linux 基础知识(九)

一.TLS链路的通信图 第一阶段:ClientHello: 支持的协议版本,比如tls 1.2: 客户端生成一个随机数,稍后用户生成"会话密钥" 支持的加密算法,比如AES.3DES.RSA: 支持的压缩算法: 第二阶段:ServerHello 确认使用的加密通信协议版本,比如tls 1.2: 服务器端生成一个随机数,稍后用于生成"会话密钥" 确认使用的加密方法: 服务器证书: 第三阶段: 验正服务器证书,在确认无误后取出其公钥:(发证机构.证书完整性.证书持有者.

java基础知识回顾之java Thread类学习(九)--wait和notify区别

wait和sleep区别:  相同点:调用wait,sleep方法都可以是线程进入阻塞状态,让出cpu的执行权. 不同点:1.sleep必须指定时间,但是wait方法可以指定时间,也可以不指定时间. 2.wait方法必须在同步中使用,但是sleep不一定在同步中使用. 3.在同步中,调用sleep方法释放CPU执行权,但是不会释放锁,即使让出了CPU执行权,其它线程也无法进入同步锁,不能得到执行.但是wait  方法不仅释放CPU执行权,而且释放同步锁,进入阻塞状态.也就是说其它等待此锁的线程可