前面介绍各种容器之时,通过在容器名称后面添加包裹数据类型的一对尖括号,表示该容器存放的是哪种类型的元素。这样一来总算把Java当中的各类括号都凑齐了,例如包裹一段代码的花括号、指定数组元素下标的方括号、容纳方法输入参数的圆括号,还有最近跟在容器名称之后的尖括号。可是为什么尖括号要加到容器后面呢?它还能不能用于其它场合?若想对尖括号的来龙去脉究根问底,就得从泛型的概念说起了。
不管是方法还是类,都支持输入指定类型的参数,其中方法的输入参数在调用方法时填写,而类的输入参数可通过构造方法传递。在这两种参数输入的情况中,参数类型是早就确定好的,只有参数值才会动态变化,那要是连参数类型都不确定,得等到方法调用或者创建实例的时候才能确定参数类型,这可如何是好?为解决此种需求,各类编程语言纷纷祭出泛型的绝招,所谓“泛型”它的表面意思是空泛的类型,也就是不明确的类型,既然类型在方法定义或者类定义时仍不明确,只好留待要用的时候再指定了。
为了更好地理解泛型的根源,接下来先看个简单的例子。如今有两个数字,一个是整数1,另一个是带小数点的1.0,光光从算术方面比较的话,1与1.0肯定相等。但是到了Java语言这里,使用包装整型变量保存整数1,使用包装浮点型变量保存小数1.0f,然后二者通过equals方法进行校验,判断结果却是不等的。此处对整数与小数开展比较的代码如下所示:

		Integer oneInt = 1;
Float oneFloat = 1.0f;
boolean equalsSimple = oneInt.equals(oneFloat);
System.out.println("equalsSimple="+equalsSimple);

运行以上的测试代码,发现日志输出信息为“equalsSimple=false”,该结果看似咄咄怪事,其实是必然的,因为它们的变量类型都不一样,导致编译器认为二者的类型尚不吻合,遑论其它。若想进行包装数值变量之间的相等判断,就必须把有关变量转换为相同类型,再作指定精度的数值一致性检验。考虑到Integer和Float都继承自Number类型,系出同源的还有Long、Double等类型,于是可将这些包装变量统统转为Number类型,然后从Number变量获取双精度数值加以比较。据此编写的方法代码示例如下:

	// 通过Number基类比较两个数值变量是否相等
public static boolean equalsNumber(Number n1, Number n2) {
return n1.doubleValue() == n2.doubleValue();
}

从上面的equalsNumber方法可见,它的输入参数为Number型,同时涵盖了Number及其派生出来的所有子类。对于这种情况,Java允许泛化参数类型,即先声明一个由Number扩展而来的类型T,再把T作为输入参数的变量类型。下面是具体的类型泛化代码:

	// 通过泛型变量比较两个数值变量是否相等。利用尖括号包裹泛型的派生操作
public static <T extends Number> boolean equalsGeneric(T t1, T t2) {
return t1.doubleValue() == t2.doubleValue();
}

虽然equalsNumber与equalsGeneric的参数格式有所不同,但实际上两个方法是等价的,它们支持的入参类型都属于Number及其子类。

紧接着再来看个数组元素拼接成字符串的例子,编码调试的过程中,程序员常常想知道某个数组里面究竟放了哪些元素,这时便需要将数组的所有元素都打印出来。然而数组变量自身不能自动转成字符串,只能通过Arrays工具的toString方法输出拼接好的字符串,倘若由程序员自己编码去拼接数组元素,那又该如何处理?因为普通的数据类型也同时支持数组形式,所以要想整个通用的字符串拼接方法,必须找到这些数据类型的共同基类,恰好Java也提供了这个基类名叫Object,那末把“Object[]”当作通用的数组类型真是再合适不过了。如此一来,数组各元素的字符串拼接代码就变成了下面这般:

	// 把对象数组里的各个元素拼接成字符串
public static String objectsToString(Object[] array) {
String result = "";
if (array!=null && array.length>0) {
for (int i=0; i<array.length; i++) {
if (i > 0) {
result = result + " | ";
}
result = result + array[i].toString();
}
}
return result;
}

接着让外部运行一段测试代码,检查看看字符串拼接是否正常运行,测试代码如下:

		Double[] doubleArray = new Double[] { 1.1, 2D, 3.1415926, 11.11 };
System.out.println("objectsToString=" + objectsToString(doubleArray));

运行上述的测试代码,观察输出的日志发现拼接功能完全正常:

objectsToString=1.1 | 2.0 | 3.1415926 | 11.11

Object作为普通数据类型的基类,自然它也支持泛化的写法,即先声明一个由Object扩展而来的类型T,再把T作为输入参数的变量类型,于是类型泛化的代码格式形如“<T extends Object>”。由于Object是Java默认的原始基类,如同大家自定义新类时都没写“extends Object”那样,类型泛化也不必显式写明“extends Object”,因此“<T extends Object>”完成可以简写为“<T>”。这样采取泛化简写的字符串拼接泛型代码如下所示:

	// 把泛型数组里的各个元素拼接成字符串。<T> 等同于 <T extends Object>
//public static <T extends Object> String arraysToString(T[] array) {
public static <T> String arraysToString(T[] array) {
String result = "";
if (array!=null && array.length>0) {
for (int i=0; i<array.length; i++) {
if (i > 0) {
result = result + " | ";
}
result = result + array[i].toString();
}
}
return result;
}

现在给出了数组类型的泛型写法,容器类型也能依样画葫芦,对应于泛型数组的“T[]”,原先通用的清单数据就变成了类型“List<T>”。改写之后的清单元素拼接代码示例如下:

	// 把List清单里的各个元素拼接成字符串,此处使用了泛型
public static <T> String listToString(List<T> list) {
String result = "";
if (list!=null && list.size()>0) {
for (int i=0; i<list.size(); i++) {
if (i > 0) {
result = result + " | ";
}
result = result + list.get(i).toString();
}
}
return result;
}

对于包括清单在内的容器类型来说,还能在尖括号内部填上问号,同样表示里面的数据类型是不确定的,就像下列代码演示的那样:

	// 把List清单里的各个元素拼接成字符串,此处使用了问号表示不确定类型
public static String listToStringByQuestion(List<?> list) {
String result = "";
if (list!=null && list.size()>0) {
for (int i=0; i<list.size(); i++) {
if (i > 0) {
result = result + " | ";
}
result = result + list.get(i).toString();
}
}
return result;
}

不过带有问号的“<?>”写法有很大的局限性,它既不如泛型灵活,也不如Object通用。问号写法仅仅适用于个别场合,并不推荐在一般方法中运用。单单拿问号跟泛型比较的话,主要有以下几点区别:
1、问号只能用于给泛型类创建实例,本身不能创建实例。而泛型T既可用于泛型类创建实例,也可用于给自身创建实例,如“T t;”
2、问号只可用作输入参数,不可用作输出参数。而泛型T用于二者皆可。
3、使用了问号的容器实例,只允许调用get方法,不允许调用add方法。而泛型容器不存在方法调用的限制。

更多Java技术文章参见《Java开发笔记(序)章节目录

Java开发笔记(六十八)从泛型方法探究泛型的起源的更多相关文章

  1. Java开发笔记(十八)上下求索的while循环

    循环是流程控制的又一重要结构,“白天-黑夜-白天-黑夜”属于时间上的循环,古人“年复一年.日复一日”的“日出而作.日落而息”便是每天周而复始的生活.计算机程序处理循环结构时,给定一段每次都要执行的代码 ...

  2. Java开发笔记(九十八)利用Callable启动线程

    前面介绍了如何利用Runnable接口构建线程任务,该方式确实方便了线程代码的复用与共享,然而Runnable不像公共方法那样有返回值,也就无法将线程代码的处理结果传给外部,造成外部既不知晓该线程是否 ...

  3. Java开发学习(二十八)----拦截器(Interceptor)详细解析

    一.拦截器概念 讲解拦截器的概念之前,我们先看一张图: (1)浏览器发送一个请求会先到Tomcat的web服务器 (2)Tomcat服务器接收到请求以后,会去判断请求的是静态资源还是动态资源 (3)如 ...

  4. Java开发笔记(十六)非此即彼的条件分支

    前面花了大量篇幅介绍布尔类型及相应的关系运算和逻辑运算,那可不仅仅是为了求真值或假值,更是为了通过布尔值控制流程的走向.在现实生活中,常常需要在岔路口抉择走去何方,往南还是往北,向东还是向西?在Jav ...

  5. Java学习笔记二十八:Java中的接口

    Java中的接口 一:Java的接口: 接口(英文:Interface),在JAVA编程语言中是一个抽象类型,是抽象方法的集合,接口通常以interface来声明.一个类通过继承接口的方式,从而来继承 ...

  6. Java学习笔记(十八)——Java DTO

    [前面的话] 在和技术人员的交流中,各种专业术语会出现,每次都是默默的记录下出现的术语,然后再去网上查看是什么意思.最近做项目,需要使用到DTO,然后学习一下吧. 这篇文章是关于Java DTO的,选 ...

  7. 【Java学习笔记之十八】Javadoc注释的用法

    Javadoc注释的用法 Java 文档 // 注释一行/* ...... */ 注释若干行/** ...... */ 注释若干行,并写入 javadoc 文档 通常这种注释的多行写法如下: /*** ...

  8. Java开发笔记(十九)规律变化的for循环

    前面介绍while循环时,有个名叫year的整型变量频繁出现,并且它是控制循环进出的关键要素.不管哪一种while写法,都存在三处与year有关的操作,分别是“year = 0”.“year<l ...

  9. 安卓开发笔记(十八):实现button按钮事件的三种方法

    Android开发中有三种主要的方式用于设置View的点击事件,1.创建内部类:2.主类中实现OnClickListener接口:3.使用匿名内部类.这三种方式都用到了OnClickListener接 ...

随机推荐

  1. koa2学习(一)

    前期准备: node环境 npm包管理工具 安装Koa npm install --save koa 第一个程序 创建index.js const Koa = require('koa'); cons ...

  2. Ooui.Wasm:浏览器中的.NET

    小型.NET Web框架Ooui作为Web程序集(WASM)在浏览器中完全运行 – 不需要服务器端的交互,涉及的所有文件都直接从浏览器执行,.NET Web框架Ooui可以作为其开发人员Frank A ...

  3. python爬虫学习视频资料免费送,用起来非常666

    当我们浏览网页的时候,经常会看到像下面这些好看的图片,你是否想把这些图片保存下载下来. 我们最常规的做法就是通过鼠标右键,选择另存为.但有些图片点击鼠标右键的时候并没有另存为选项,或者你可以通过截图工 ...

  4. [Swift]LeetCode328. 奇偶链表 | Odd Even Linked List

    Given a singly linked list, group all odd nodes together followed by the even nodes. Please note her ...

  5. [Swift]LeetCode543. 二叉树的直径 | Diameter of Binary Tree

    Given a binary tree, you need to compute the length of the diameter of the tree. The diameter of a b ...

  6. [Swift]LeetCode712. 两个字符串的最小ASCII删除和 | Minimum ASCII Delete Sum for Two Strings

    Given two strings s1, s2, find the lowest ASCII sum of deleted characters to make two strings equal. ...

  7. 接口平台经常报server internal error(500)错误

    查询日志,发现连接mysql报错,web页面显示server internal error(500) 解决方法:重启mysql服务器 systemctl start mysqld #安装mysql # ...

  8. Unable to preventDefault inside passive event listener due to target being treated as passive

    Unable to preventDefault inside passive event listener due to target being treated as passive 今天在做项目 ...

  9. mac连接windows远程桌面及文件复制

    最近更换mac办公,但由于之前是用windows,所以很多文件项目之类的东西都还在windows电脑中,一次都传到mac上又会比较占内存,并且使用率也不高,感觉不划算.但每次想用的时候,在从windo ...

  10. Lunix服务器上项目迁移命令

    scp就是security copy,用于将文件或者目录从一个Linux系统拷贝到另一个Linux系统下. scp传输数据用的是SSH协议,保证了数据传输的安全,其格式如下: scp 远程用户名@IP ...