前面介绍各种容器之时,通过在容器名称后面添加包裹数据类型的一对尖括号,表示该容器存放的是哪种类型的元素。这样一来总算把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. Restful levels&HATEOAS

    RESTful: Rest是一种软件架构风格.设计风格,而不是标准,只是提供了一组设计原则和约束条件.它主要用于客户端和服务器交互类的软件.基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等 ...

  2. ps入门学习

    快捷键 打开 ctrl+O 切换显示窗口 ctrl+tab 隐藏工具栏和面板  tab 只隐藏面板不隐藏工具栏  shift+tab 切换屏幕模式  F 文件的新建与格式 1.新建文档Ctrl+N,存 ...

  3. 合适么?现在学ASP.NET Core入门编程……

    现在都快找不到ASP.NET的培训课程了. 知道我要开课做培训,有同学劝我:“憋讲那什么.NET,讲Java,现在这个火!”我说我Java不熟,“唉呀!C#转Java,分分钟的事!以飞哥你的经验,…… ...

  4. 10倍速!一招儿解决因googleapis被墙导致的许多国外网站访问速度慢的问题

    1x.com 是我非常喜欢的一家国外的摄影网站.但,打开它的首页要1分多钟!点击小图看大图的二级页面根本打不开.看着写着“Nude content”的小图却点不开大图的心情你们造吗?!很多国外网站访问 ...

  5. 常见的java设计模式

    单例模式 简单点说,就是一个应用程序中,某个类的实例对象只有一个,你没有办法去new,因为构造器是被private修饰的,一般通过getInstance()的方法来获取它们的实例. getInstan ...

  6. [Swift]LeetCode121. 买卖股票的最佳时机 I | Best Time to Buy and Sell Stock

    Say you have an array for which the ith element is the price of a given stock on day i. If you were ...

  7. [Java]LeetCode141. 环形链表 | Linked List Cycle

    Given a linked list, determine if it has a cycle in it. Follow up:Can you solve it without using ext ...

  8. [Swift]LeetCode654. 最大二叉树 | Maximum Binary Tree

    Given an integer array with no duplicates. A maximum tree building on this array is defined as follo ...

  9. [Swift]LeetCode874. 模拟行走机器人 | Walking Robot Simulation

    A robot on an infinite grid starts at point (0, 0) and faces north.  The robot can receive one of th ...

  10. POI生成EXCEL文件

    POI生成EXCEL文件 一.背景 根据指定格式的JSON文件生成对应的excel文件,需求如下 支持多sheet 支持单元格合并 支持插入图片 支持单元格样式可定制 需要 标题(title),表头( ...