《Java核心技术(卷1)》笔记:第8章 泛型程序设计
(P 327)“菱形”语法:
ArrayList<String> files = new ArrayList<>();
// Java 9 扩展了菱形语法的使用范围,例如:现在可以对匿名子类使用菱形语法
ArrayList<String> passwords = new ArrayList<>() {
public String get(int n) {
return super.get(n).replaceAll(".", "*");
}
}
(P 328)定义泛型类:
public class Pair<T, U> {
...
}
常见的做法是类型变量使用大写字母,而且很简短:
E
表示集合的元素类型K
、V
分别表示表的键和值的类型T
、U
、S
表示任意类型
(P 330)定义泛型方法:类型变量放在修饰符的后面,并在返回类型的前面
class ArrayAlg {
public static <T> T getMiddle(T... a) {
...
}
}
调用泛型方法:
String middle = ArrayAlg.<String>getMiddle("John", "Q.", "Public");
// 大多数情况下,可以省略类型参数
String middle = ArrayAlg.getMiddle("John", "Q.", "Public"); // 编译器将参数的类型与泛型类型T进行匹配,推断出T一定是String
(P 332)类型变量的限定
T
是限定类型(bounding type)的子类型:<T extends BoundingType>
一个类型变量或通配符可以有多个限定,限定类型用“&”分隔,而逗号用来分隔类型变量
<T extends BoundingType1 & BoundingType2>
可以拥有多个接口超类型,但最多有一个限定可以是类。如果有一个类作为限定,它必须是限定列表中的第一个限定
(P 333)类型擦除:无论何时定义一个泛型类型,都会自动提供一个相应的原始类型。这个原始类型的名字就是去掉类型参数后的泛型类型名。类型变量会被擦除,并替换为其限定类型(或者,对于无限定的变量则替换为Object)
(P 334)为了提高效率,应该将标签接口(即没有方法的接口)放在限定列表的末尾
(P 335)调用一个泛型方法时,编译器会擦除返回类型,并插入强制类型转换。当访问一个泛型字段时,也会插入强制类型转换。
Pair<Employee> buddies = ...;
Employee buddy = buddies.getFirst();
// 编译器会做如下类似的处理
Pair buddies = ...; // 擦除类型参数,Pair中的所有泛型被替换为Object
Employee buddy = (Employee) buddies.getFirst(); // 插入强制类型转换(方法原来的返回类型被擦除变成了Object)
(P 335)桥方法:用来解决多态调用和类型擦除的冲突
方法的擦除会带来两个问题,考虑如下代码:
class DateInterval extends Pair<LocalDate> {
// (伪)重写父类中的方法
// 之所以这里加个“伪”字,是因为父类的类型参数会被编译器擦除,变成Object,所以这里实际上是重载了父类中的方法,只是看起来像重写
public void setSecond(LocalDate second) {
...
}
// 这个类中,除了上面的那个外,还存在一个从父类继承的方法
public void setSecond(Object second);
}
这样在多态调用时会产生问题:
DateInterval interval = ...;
Pair<LocalDate> pair = interval;
pair.setSecond(aDate); // 这里调用的是哪个方法呢?类型擦除和多态发生了冲突
// 如果编译器什么都不做,将调用Pair.setSecond(Object),因为Pair中只存在这一个setSecond方法
// 而我们希望进行多态调用,即调用DateInterval.setSecond(LocalDate)
为了解决这个问题,编译器会在子类中生成一个桥方法:
class DateInterval extends Pair<LocalDate> {
// (伪)重写父类中的方法
public void setSecond(LocalDate second) {
...
}
// 编译器生成的桥方法,重写了父类的setSecond方法
public void setSecond(Object second) {
setSecond((LocalDate) second); // 调用上面的那个setSecond方法
}
}
另外,还有一个问题,考虑如下代码:
class DateInterval extends Pair<LocalDate> {
// (伪)重写父类中的方法
public LocalDate getSecond() {
...
}
// 同理,编译器会生成桥方法,以便进行多态调用
public Object getSecond() {
return (LocalDate) getSecond(); // 这里调用的是哪个方法呢?方法重载时要求参数类型不同,但是这里两个getSecond方法都没有参数,似乎不合法
}
}
程序员是不能这样编写Java代码的,但是在虚拟机中,会由参数类型和返回类型共同指定一个方法。因此,编译器可以为两个仅返回类型不同的方法生成字节码,虚拟机能够正确地处理这种情况
(P 337)对于Java泛型的转换,有如下几个事实:
- 虚拟机中没有泛型,只有普通的类和方法
- 所有的类型参数都会替换为它们的限定类型
- 会合成桥方法来保持多态
- 为保持类型安全性,必要时会插入强制类型转换
(P 337)在泛型代码和遗留代码之间进行互操作时,编译器会发出一个警告,可以通过加注解
@SuppressWarnings("unchecked")
使之消失// 将泛型对象赋给原始类型对象
Dictionary<Integer, Component> labelTable = ...;
@SuppressWarnings("unchecked") // 抑制编译器的警告
slider.setLabelTabel(labelTable); // warning // 将原始类型对象赋给泛型对象
@SuppressWarnings("unchecked") // 抑制编译器的警告
Dictionary<Integer, Component> labelTable = slider.getLabelTable(); // warning
(P 338)限制与局限性:
不能用基本类型实例化类型参数
Pair<double> pair = ...; // 不合法,double是基本类型
运行时类型查询只适用于原始类型
if (a instanceof Pair<String>) // 错误
if (a instanceof Pair<T>) // 错误
if (a instanceof Pair) // 正确 Pair<String> pair = (Pair<String>) a; // 错误
getClass
方法总是返回原始类型Pair<String> stringPair = ...;
Pair<Employee> employeePair = ...;
if (stringPair.getClass() == employeePair.getClass()) // 比较结果为true,两个getClass调用都返回Pair.class
不能创建参数化类型的数组(可以声明,但不能创建)
var table = new Pair<String>[10]; // 错误
var table = (Pair<String>[]) new Pair<?>[10]; // 可以,但是结果将是不安全的
如果需要收集参数化类型对象,简单地使用
ArrayList
更安全、有效var table = new ArrayList<Pair<String>>(); // 合法
Varargs警告:向参数个数可变的方法传递一个泛型类型的实例,编译器会发出一个警告,可以使用
@SuppressWarnings("unchecked")
或者@SafeVarargs
注解来抑制这个警告@SafeVarargs
public static <T> void addAll(Collection<T> coll, T... ts) // 调用这个方法时,虚拟机必须要创建T类型的数组ts
// 这违反了前面的规则,但此时编译器只会发出一个警告
- 对于任何只需要读取参数数组元素的方法,都可以使用
@SafeVarargs
注解 @SafeVarargs
只能用于声明为static
、final
或private
的构造器和方法。
- 对于任何只需要读取参数数组元素的方法,都可以使用
不能实例化类型变量
public Pair() {
first = new T(); // 错误
second = new T(); // 错误
}
Java 8之后,最好的解决办法:让调用者提供一个构造器表达式
public static <T> Pair<T> makePair(Supplier<T> constr) {
return new Pair<>(constr.get(), constr.get());
} Pair<String> p = Pair.makePair(String::new);
传统的解决方法:通过反射调用
Constructor.newInstance
方法来构造泛型对象first = T.class.getConstructor().newInstance(); // 错误,T被擦除为Object public static <T> Pair<T> makePair(Class<T> cl) {
try {
return new Pair<>(cl.getConstructor().newInstance(), cl.getConstructor().newInstance());
} catch (Exception e) {
return null;
}
} Pair<String> p = Pair.makePair(String.class);
不能构造泛型数组
public static <T extends Comparable> T[] minmax(T... a) {
T[] mm = new T[2]; // 错误
...
}
泛型类的静态上下文中类型变量无效:不能在静态字段或方法中引用类型变量
public class Singleton<T> {
private static T singleInstance; // 错误
public static T getSingleInstance() { // 错误
...
}
}
不能抛出或捕获泛型类的实例
public class Problem<T> extends Exception { ... } // 错误,泛型类不能扩展Throwable
try { ... } catch (T e) { ... } // 错误,catch子句中不能使用类型变量
可以取消对检查型异常的检查
通过使用泛型类、擦除和
@SuppressWarnings
注解,我们就能消除Java类型系统的部分基本限制(详见P 343 ~ P 345)注意擦除后的冲突:例如在类中增加一个
equals
方法就可能和从Object
中继承的equals
方法冲突倘若两个接口类型是同一接口的不同参数化,一个类或类型变量就不能同时作为这两个接口类型的子类
class Employee implements Comparable { ... }
class Manager extends Employee implements Comparable { ... } // 错误
(P 346)具有继承关系的类如果作为泛型类的类型参数,则这些泛型类之间没有继承关系(通配符类型可以解决这个问题),例如
Employee
和Manager
具有继承关系,但是Pair<Employee>
和Pair<Manager>
之间没有继承关系。注意:数组类型Employee[]
和Manager[]
之间具有继承关系(P 347)总是可以将参数化类型转换为一个原始类型
var managerBuddies = new Pair<Manager>(...);
Pair rawBuddies = managerBuddies; // 合法
(P 347)泛型类可以扩展或实现其他的泛型类。如:
ArrayList<T>
实现了List<T>
接口,这意味着ArrayList<Manager>
实现了List<Manager>
接口(P 348)通配符:在通配符类型中,允许类型参数发生变化
Pair<? extends Employee> // 表示任何泛型Pair类型,它的类型参数是Employee的子类
// 如Pair<Manager>是Pair<? extends Employee>的子类
其中的方法如下:
? extends Employee getFirst() // 合法,可以将返回值赋给一个Employee
void setFirst(? extends Employee) // 这样不可能调用这个方法,它拒绝传递任何特定的类型
(P 349)超类型限定:
? super Manager
,这个通配符限制为Manager的所有超类型void setFirst(? super Manager) // 合法,可以向方法传递一个Manager对象,或者其子类型的对象
? super Manager getFirst() // 不能调用这个方法,它无法确定返回值的类型,只能赋给Object
(P 350)直观地讲,带有超类型限定的通配符允许你写入一个泛型对象,而带有子类型限定的通配符允许你读取一个泛型对象
(P 351)无限定通配符:在编写不需要实际类型的方法时很有用,可读性更好
? getFirst() // 返回值只能赋给Object
void setFirst(?) // 不能被调用,甚至不能传递Object(原始的Pair类型可以,这是Pair<T>和Pair主要的不同),可以传递null
(P 352)不能在编写代码中使用“
?
”作为一种类型,必须保存?
类型的变量时,可以通过编写辅助方法(泛型方法)解决(P 353)通配符捕获只有在非常限定的情况下才是合法的,编译器必须能够保证通配符表示单个确定的类型
(P 356)可以使用
java.lang.reflect
包中的接口Type
表述泛型类型的声明,其包含以下子类:Class
类,描述具体类型TypeVariable
接口,描述类型变量WildcardType
接口,描述通配符ParameterizedType
接口,描述泛型类或接口类型GenericArrayType
接口,描述泛型数组
《Java核心技术(卷1)》笔记:第8章 泛型程序设计的更多相关文章
- Java核心技术卷一基础知识-第12章-泛型程序设计-读书笔记
第12章 泛型程序设计 本章内容: * 为什么要使用泛型程序设计 * 定义简单泛型类 * 泛型方法 * 类型变量的限定 * 泛型代码和虚拟机 * 约束与局限性 * 泛型类型的继承规则 * 通配符类型 ...
- java中的数据类型,运算符,字符串,输入输出,控制流,大数值,数组; 《java核心技术卷i》 第三章:java基本程序结构;
<java核心技术卷i> 第三章:java基本程序结构: 每次看书,去总结的时候,总会发现一些新的东西,这次对于java的数组有了更深的了解: java中的数据类型,运算符,字符串,输入输 ...
- Java核心技术卷阅读随笔--第3章【Java 的基本程序设计结构】
Java 的基本程序设计结构 现在, 假定已经成功地安装了 JDK,并且能够运行第 2 章中给出的示例程序.我们从现在开始将介绍 Java 应用程序设计.本章主要介绍程序设计的基本概念(如数据类型.分 ...
- Java核心技术卷一基础知识-第7章-图形程序设计-读书笔记
第7章 图形程序设计 本章内容: * Swing概述 * 创建框架 * 框架定位 * 在组件中显示信息 * 处理2D图形 * 使用颜色 * 文本使用特殊字体 * 显示图像 本章主要讲述如何编写定义屏幕 ...
- Java核心技术卷阅读随笔--第4章【对象与类】
对 象 与 类 4.1 面向对象程序设计概述 面向对象程序设计(简称 OOP) 是当今主流的程序设计范型, 它已经取代了 20 世纪 70 年代的" 结构化" 过程化程序设计开发技 ...
- Java核心技术卷阅读随笔--第2章【Java 程序设计环境】
Java 程序设计环境 本章主要介绍如何安装 Java 开发工具包( JDK ) 以及如何编译和运行不同类型的程序: 控制台程序. 图形化应用程序以及 applet.运行 JDK 工具的方法是在终端窗 ...
- Java核心技术卷1Chapter7笔记 图形程序设计
Swing是指被绘制的用户界面类,AWT是指像事件处理这样的窗口工具箱的底层机制. SWT,JavaFX是可能的代替技术. 创建框架 在Java中,顶层窗口(就是没有包含在其他窗口中的窗口)被称为框架 ...
- 《Java核心技术卷I》——第5章 继承
在C++中,没有提供用于表示抽象类的特殊关键字.只要有一个纯虚函数,这个类就是抽象类. hashCode()方法是定义在Object类中,因此每个对象都有一个默认的散列码,其值为对象的存储地址. 绝大 ...
- 《Java核心技术卷I》——第3章 Java的基本程序设计结构
byte和short类型主要用于特定的应用场合,例如,底层的文件处理或者需要控制占用存储空间量的大数组. 十六进制数值有一个前缀0x(如0xCAFE),八进制有一个前缀0,如010对应八进制中的8.很 ...
- 《Java核心技术·卷Ⅰ:基础知识(原版10》学习笔记 第5章 继承
<Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 目录 <Java核心技术·卷Ⅰ:基础知识(原版10>学习笔记 第5章 继承 5.1 类.超类和子类 5.1 ...
随机推荐
- Physic Design:Floorplan算法概览
仅用于学习交流,转载请联系本人. 1 floorplan是什么 floorplan常被翻译成布图规划,是指在芯片级别上对模块进行布局,也就是哪个单元放在什么地方,但是单元内部的具体布局并不关心.该步骤 ...
- Java实现 第十一届 蓝桥杯 (高职专科组)省内模拟赛
有错误的或者有问题的欢迎评论 十六进制数1949对应的十进制数 19000互质的数的个数 70044与113148的最大公约数 第十层的二叉树 洁净数 递增序列 最大的元素距离 元音字母辅音字母的数量 ...
- java实现基因牛的繁殖
基因牛的繁殖 基因牛 张教授采用基因干预技术成功培养出一头母牛,三年后,这头母牛每年会生出1头母牛, 生出来的母牛三年后,又可以每年生出一头母牛.如此循环下去,请问张教授n年后有多少头母牛? 以下程序 ...
- java实现股票的风险
股票的风险 股票风险 股票交易上的投机行为往往十分危险.假设某股票行为十分怪异,每天不是涨停(上涨10%)就是跌停(下跌10%).假设上涨和下跌的概率均等(都是50%).再假设交易过程没有任何手续费. ...
- docker-compose mysql和node连接认证mongo问题
前言 最近,想部署一个自己的项目,鉴于自己的服务器是VPS(虚拟主机),配置也不够,就想到了用 docker 直接部署好了,这样既方便部署也方便不用的时候卸载或更新 然后本地搭建了环境,踩了一些坑,在 ...
- java启动RabbitMQ消息报异常解决办法
启动SpringCloud微服务,RabbitMQ报如下异常: 2019-08-12 18:15:49.543 ERROR 53096 --- [68.252.131:5672] o.s.a.r.c. ...
- 算法讲堂二:组合数学 & 概率期望DP
组合数学 1. 排列组合 1. 加法原理 完成一列事的方法有 n 类,其中第 i 类方法包括\(a_i\)种不同的方法,且这些方法互不重合,则完成这件事共有 \(a_1 + a_2 + \cdots ...
- 学习Redis好一阵了,我对它有了一些新的看法
前言 本篇文章不是一篇具体的教程,我打算记录一下自己对Redis的一些思考.说来惭愧,我刚接触Redis的时候只是简单地使用了一下,背了一些面试题,就在简历上写下了Redis这个技能点. 我们能在网络 ...
- Jenkins job docker 没有权限
问题描述 基于docker使用jenkins 构建cicd,在执行docker build 的时候出现了权限的问题.具体报错如下 + REPOSITORY=10.0.0.100/library/wen ...
- Python + MySQL 批量查询百度收录
做SEO的同学,经常会遇到几百或几千个站点,然后对于收录情况去做分析的情况 那么多余常用的一些工具在面对几千个站点需要去做收录分析的时候,那么就显得不是很合适. 在此特意分享给大家一个批量查询百度收录 ...