重温Java泛型,带你更深入地理解它,更好的使用它!
1. 引言
jdk5.0中引入了Java泛型,目的是减少错误,并在类型上添加额外的抽象层。
本文将简要介绍Java中的泛型、泛型背后的目标以及如何使用泛型来提高代码的质量。
2. 为什么要用泛型?
设想一个场景,我们希望用Java创建一个列表来存储Integer;代码可能会写成这样:
List list = new LinkedList();
list.add(new Integer(1));
Integer i = list.iterator().next();
令人惊讶的是,编译器会提示最后一行。它不知道返回的数据类型是什么。因此,编译器提示需要进行显式转换:
Integer i = (Integer) list.iterator.next();
没有任何约定可以保证列表的返回类型是整数。定义的列表可以包含任何对象。我们只知道我们是通过检查上下文来检索列表的。在查看类型时,它只能保证它是一个Object,因此需要显式转换来确保类型是安全的。
这种转换可能会令人感到聒噪,我们明明知道这个列表中的数据类型是整数。转换的话,也把我们的代码搞得乱七八糟。如果程序员在显式转换中出错,则可能会导致抛出与 类型相关的运行时错误 。
如果程序员能够表达他们使用特定类型的意图,并且编译器能够确保这种类型的正确性,那么这将更加容易。
这就是泛型背后的核心思想。
我们将前面代码段的第一行修改为:
List<Integer> list = new LinkedList<>();
通过添加包含类型的菱形运算符<>,我们将此列表的特化范围缩小到 Integer 类型,即指定将保存在列表中的类型。编译器可以在编译时强制执行该类型。
在较小的程序中,这看起来像是一个微不足道的添加。但是在较大的程序中,这可以增加显著的健壮性并使程序更易于阅读。
3. 泛型方法
泛型方法是用单个方法声明编写的方法,可以用不同类型的参数调用。编译器将确保所用类型的正确性。以下是泛型方法的一些属性:
泛型方法在方法声明的返回类型之前有一个类型参数(包裹类型的菱形运算符)
类型参数可以有界(边界将在本文后面解释)
泛型方法可以具有不同的类型参数,这些参数在方法签名中用逗号分隔
泛型方法的方法体与普通方法一样
定义将数组转换为列表的泛型方法的示例:
public <T> List<T> fromArrayToList(T[] a) {
return Arrays.stream(a).collect(Collectors.toList());
}
在前面的示例中,方法声明中的 <T>
表示该方法将处理泛型类型 T。即使方法返回的是void,也需要这样做。
如上所述,方法可以处理多个泛型类型,在这种情况下,所有泛型类型都必须添加到方法声明中,例如,如果我们要修改上面的方法来处理类型 T 和类型 G ,应该这样写:
public static <T, G> List<G> fromArrayToList(T[] a, Function<T, G> mapperFunction) {
return Arrays.stream(a)
.map(mapperFunction)
.collect(Collectors.toList());
}
我们正在传递一个函数,该函数将具有T类型元素的数组转换为包含G类型元素的列表。例如,将 Integer 转换为其 String 表示形式:
@Test
public void givenArrayOfIntegers_thanListOfStringReturnedOK() {
Integer[] intArray = {1, 2, 3, 4, 5};
List<String> stringList
= Generics.fromArrayToList(intArray, Object::toString);
assertThat(stringList, hasItems("1", "2", "3", "4", "5"));
}
Oracle建议使用大写字母表示泛型类型,并选择更具描述性的字母来表示形式类型,例如在Java集合中,T 用于类型,K 表示键,V 表示值。
3.1.泛型边界
如前所述,类型参数可以是有界的。有界意味着“限制”,我们可以限制方法可以接受的类型。
例如,可以指定一个方法接受一个类型及其所有子类(上限)或一个类型所有它的超类(下限)。
要声明上界类型,我们在类型后面使用关键字extends,后跟要使用的上限。例如:
public <T extends Number> List<T> fromArrayToList(T[] a) {
...
}
这里使用关键字extends表示类型 T 扩展类的上限,或者实现接口的上限。
3.2. 多个边界
类型还可以有多个上界,如下所示:
<T extends Number & Comparable>
如果 T 扩展的类型之一是类(即Number),则必须将其放在边界列表的第一位。否则,将导致编译时错误。
4. 使用通配符
通配符在Java中用问号“?“ 表示,它们是用来指一种未知的类型。通配符在使用泛型时特别有用,可以用作参数类型,但首先要考虑的是一个重要的注释。
众所周知,Object是所有Java类的超类型,但是,Object的集合不是任何集合的超类型。(可能有点绕,大家好好细品一下)
例如,List<Object>
不是 List<String>
的超类型,将List<Object>
类型的变量赋值给List<String>
类型的变量将导致编译器错误。
这是为了防止在将异构类型添加到同一集合时可能发生的冲突。
相同的规则适用于类型及其子类型的任何集合。看看这个例子:
public static void paintAllBuildings(List<Building> buildings) {
buildings.forEach(Building::paint);
}
如果我们设想一个子类型Building,实例House,那么我们不能将此方法与House列表一起使用,即使House是Building的子类型。如果需要将此方法与类型构建及其所有子类型一起使用,则有界通配符可以实现以下功能:
public static void paintAllBuildings(List<? extends Building> buildings) {
...
}
现在,这个方法可以处理Building类型及其所有子类型。这称为上界通配符,其中类型Building是上界。
通配符也可以使用下限指定,其中未知类型必须是指定类型的超类型。可以使用super关键字后跟特定类型来指定下限,例如,<? super T>
表示未知类型,它是 T(=T及其所有父类)的超类。
5. 类型擦除
泛型被添加到Java中以确保类型安全,并确保泛型不会在运行时造成开销,编译器在编译时对泛型应用一个名为type erasure的进程。
类型擦除删除所有类型参数,并将其替换为它们的边界,如果类型参数是无界的,则替换为Object。因此,编译后的字节码只包含普通的类、接口和方法,从而确保不会生成新的类型。在编译时对Object类型也应用了正确的强制转换。
以下是类型擦除的一个示例:
public <T> List<T> genericMethod(List<T> list) {
return list.stream().collect(Collectors.toList());
}
使用类型擦除,无界类型T将替换为Object,如下所示:
// for illustration
public List<Object> withErasure(List<Object> list) {
return list.stream().collect(Collectors.toList());
}
// which in practice results in
public List withErasure(List list) {
return list.stream().collect(Collectors.toList());
}
如果类型是有界的,则在编译时该类型将替换为绑定:
public <T extends Building> void genericMethod(T t) {
...
}
编译后会发生变化:
public void genericMethod(Building t) {
...
}
6. 泛型和原始数据类型
Java中泛型的一个限制是类型参数不能是基本类型
例如,以下内容无法编译:
List<int> list = new ArrayList<>();
list.add(17);
为了理解原始数据类型为什么不起作用,只需记住 泛型是编译时特性,这意味着类型将会被擦除,所有泛型类型都实现为 Object 类。
举一个例子,让我们看看列表的 add 方法:
List<Integer> list = new ArrayList<>();
list.add(17);
add 方法的声明如下:
boolean add(E e);
并将被编译为:
boolean add(Object e);
因此,类型参数必须可转换为Object。由于基本类型不继承自 Object,所以不能将它们用作类型参数
但是,Java为它们提供了装箱类型,以及自动装箱和自动拆箱:
Integer a = 17;
int b = a;
因此,如果我们想创建一个可以保存整数的列表,我们可以使用包装器:
List<Integer> list = new ArrayList<>();
list.add(17);
int first = list.get(0);
编译后的代码相当于:
List list = new ArrayList<>();
list.add(Integer.valueOf(17));
int first = ((Integer) list.get(0)).intValue();
Java的未来版本可能允许泛型使用原始数据类型。Valhalla 工程旨在改进处理泛型的方式。其思想是实现JEP 218中描述的泛型专门化.
**7. 总结 **
Java泛型是对Java语言的一个强大的补充,因为它使程序员的工作更容易,也更不容易出错。泛型在编译时强制执行类型正确性,并且,最重要的是,能够实现泛型算法,而不会给我们的应用程序带来任何额外的开销。
如果你觉得文章还不错,记得关注公众号: 锅外的大佬
刘一手的博客
重温Java泛型,带你更深入地理解它,更好的使用它!的更多相关文章
- 大白话说Java泛型(二):深入理解通配符
文章首发于[博客园-陈树义],点击跳转到原文<大白话说Java泛型(二):深入理解通配符> 上篇文章<大白话说Java泛型(一):入门.原理.使用>,我们讲了泛型的产生缘由以及 ...
- Java 泛型 <? super T> 中 super 怎么 理解?与 < ? extends T>有何不同?
Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同? 简介 前两篇文章介绍了泛型的基本用法.类型擦除以及泛型数组.在泛型的使用中,还有个重要的 ...
- Java泛型中extends和super的理解(转)
E – Element (在集合中使用,因为集合中存放的是元素) T – Type(Java 类) K – Key(键) V – Value(值) N – Number(数值类型) ? – 表示不确定 ...
- Java泛型中extends和super的理解
作者:zhang siege链接:https://www.zhihu.com/question/20400700/answer/91106397来源:知乎著作权归作者所有.商业转载请联系作者获得授权, ...
- Java泛型之上、下界通配符的理解(适合初学)
泛型的由来 为什么需要泛型 Java的数据类型一般都是在定义时就需要确定,这种强制的好处就是类型安全,不会出现像弄一个ClassCastException的数据给jvm,数据安全那么执行的clas ...
- Java 泛型 <? super T> 中 super 怎么 理解?与 extends 有何不同?
看知乎:https://www.zhihu.com/question/20400700 了解的越多,就会发现,越多不了解.
- Java泛型总结
1. 什么是泛型?泛型(Generic type 或者 generics)是对 Java 语言的类型系统的一种扩展,以支持创建可以按类型进行参数化的类.可以把类型参数看作是使用参数化类型时指定的类型的 ...
- 学习记录 java泛型资料
java泛型资料: 1. 概述在引入范型之前,Java类型分为原始类型.复杂类型,其中复杂类型分为数组和类.引入范型后,一个复杂类型就可以在细分成更多的类型.例如原先的类型List,现在在细分成Lis ...
- Java 泛型(Generics)
Generics, 类似C++中的模版. 允许在定义类和接口的时候使用类型参数(type parameters), 声明的类型参数在使用的时候用具体的类型来替换. 如 ArrayList<Str ...
随机推荐
- 《To B产品经理进阶》
一.沙漏哟:To B产品技术标准化(全网独家) 经济机器是怎样运行的(超级简单模式理解经济运行规律) <俞军产品方法论>(思维模型.交易模型.经济学.心理学) <深度思考六步法> ...
- JVM初步入门(很初级,建议只做大致了解用)
jvm初步入门 本博客中的所有内容都是来自b站狂神说视频教程,如有侵权联系我删除. 下面是视频链接:b站狂神说 关于jvm的几个小问题 1. jvm的位置: jvm是一个运行在操作系统上的用c语言编写 ...
- shell-变量输入内置read命令详解
1. shell变量的输入 shell变量除了可以直接赋值或脚本传参外,还可以使用read命令从标准输入得. [语法格式] read [参数] [变量名] [常用参数] -p:指定读取值时的提示符: ...
- 【手摸手,带你搭建前后端分离商城系统】01 搭建基本代码框架、生成一个基本API
[手摸手,带你搭建前后端分离商城系统]01 搭建基本代码框架.生成一个基本API 通过本教程的学习,将带你从零搭建一个商城系统. 当然,这个商城涵盖了很多流行的知识点和技术核心 我可以学习到什么? S ...
- 彻底根治window弹窗小广告(今日热点)
在一个阴雨蒙蒙的下午,我上完厕所回到工位,输入锁屏密码,解锁,蹦出来三个小广告,我......这还能忍??? 废话不多说,开搞! 一.广告分为两种: 红色字的今日热点 蓝色字的今日热点 二.追溯根源: ...
- 扫描仪扫描文件处理-imagemagick常用参数
-resize 宽x高(缩放,不变形) -extent 宽x高(放大,不变形)之前设置:-gravity center(重心居中) -brightness-contrast 亮度x对比度(设置亮度对比 ...
- pycharm2018.1下载激活(mac平台)
此教程实时更新,请放心使用:如果有新版本出现猪哥都会第一时间尝试激活: pycharm官网下载地址:http://www.jetbrains.com/pycharm/download/ 激活前准备工作 ...
- appium_android-常见的问题
po模型的原则: 用公共方法代表UI所提供的功能 方法应该返回其他的PageObject或者返回用于断言的数据 同样的行为不同的结果可以建模为不同的方法 不要在方法内加断言 字段意义 不要暴露页面内部 ...
- ASP.NET实现进度条效果【转】
原文地址:http://www.jb51.net/article/115310.htm 这篇文章主要为大家详细介绍了ASP.NET实现简单的进度条效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一 ...
- HTML <del> 标签
HTML <del> 标签 什么是<del> 标签? 定义文档中已被删除的文本. 实例 a month is <del>25</del> 30 day ...