欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java开发笔记(六十八)从泛型方法探究泛型的起源

程序员文章站 2022-07-11 11:18:37
前面介绍各种容器之时,通过在容器名称后面添加包裹数据类型的一对尖括号,表示该容器存放的是哪种类型的元素。这样一来总算把Java当中的各类括号都凑齐了,例如包裹一段代码的花括号、指定数组元素下标的方括号、容纳方法输入参数的圆括号,还有最近跟在容器名称之后的尖括号。可是为什么尖括号要加到容器后面呢?它还 ......

前面介绍各种容器之时,通过在容器名称后面添加包裹数据类型的一对尖括号,表示该容器存放的是哪种类型的元素。这样一来总算把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开发笔记(序)章节目录