前面介绍各种容器之时,通过在容器名称后面添加包裹数据类型的一对尖括号,表示该容器存放的是哪种类型的元素。这样一来总算把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. 微信测试号开发入门配置问题java

    这个测试号配置弄了好几天了,入了无数坑,终于解决了...辛苦我了,手动安慰一下自己.. 为了萌新们以后不要再浪费时间绕半天做无用功.看看楼楼的艰苦历程吧. 此教程针对没有云服务器,没有自己的域名的.没 ...

  2. SharePoint 更改管理帐户密码步骤

    // https://wenku.baidu.com/view/0fffab761ed9ad51f01df2df.html \

  3. QEMU KVM Libvirt手册(10): KVM的各种限制

    Overcommits KVM allows for both memory and disk space overcommit. However, hard errors resulting fro ...

  4. Swift 对象内存模型探究(一)

    本文来自于腾讯Bugly公众号(weixinBugly),未经作者同意,请勿转载,原文地址:https://mp.weixin.qq.com/s/zIkB9KnAt1YPWGOOwyqY3Q 作者:王 ...

  5. SQL Server Cast、Convert数据类型转换

    一.概述 本篇文章转载来着官网在线文档,文章主要介绍SQL Server数据类型转换相关语法.隐式转换.Date样式等. 语法 Syntax for CAST: CAST ( expression A ...

  6. 对JS闭包和函数作用域的问题的深入讨论,如何理解JS闭包和函数作用域链?

    首先先引用<JavaScript权威指南>里面的一句话来开始我的博客:函数的执行依赖于变量作用域,这个作用域是在函数定义时决定的,而不是函数调用时决定的. 因此,就出现了如下的几串代码: ...

  7. GT-随身调详细教程

    一.GT介绍 GT(随身调)是APP的随身调测平台,它是直接运行在手机上的“集成调测环境”(IDTE, Integrated Debug Environment).利用GT,仅凭一部手机,无需连接电脑 ...

  8. 【面试篇】资深招聘HR有哪些面试技巧?

    15年资深招聘HR总结的面试技巧     1.做一下自我介绍 了解应聘者的基本信息和工作经历 2.以往工作中您的职责是什么? 了解应聘者的相关工作经验和其系统性全面性 3.请讲一下您以往的工作经历. ...

  9. ubuntu系统界面改变

    主题:https://gitzab.com/Anduin/GNOME-OSX-II-Theme.git图标:https://github.com/keeferrourke/la-capitaine-i ...

  10. [Swift]LeetCode501. 二叉搜索树中的众数 | Find Mode in Binary Search Tree

    Given a binary search tree (BST) with duplicates, find all the mode(s) (the most frequently occurred ...