`

Java编程思想(第4版) 之 15.4 泛型方法

 
阅读更多

参考文章:http://qiemengdao.iteye.com/blog/1525624

15.4   泛型方法

到目前为止,我们看到的泛型,都是应用于整个类上。但同样可以在类中包含参数化方法,而这个方法所在的类可以是泛型类,也可以不是泛型类。也就是说,是否拥有泛型方法,与其所在的类是否是泛型没有关系。

泛型方法使得该方法能够独立于类而产生变化。以下是一个基本的指导原则:无论何时,只要你能做到,你就应该尽量使用泛型方法。也就是说,如果使用泛型方法可以取代将整个类泛型化,那么就应该只使用泛型方法,因为它可以使事情更清楚明白。另外,对于一个static的方法而言,无法访问泛型类的类型参数,所以,如果static方法需要使用泛型能力,就必须使其成为泛型方法。

要定义泛型方法,只需将泛型参数列表置于返回值之前,就像下面这样:



 

GenericMethods并不是参数化的,尽管这个类和其内部的方法可以被同时参数化,但是在这个例子中,只有方法f()拥有类型参数。这是由该方法的返回类型前面的类型参数列表指明的。

注意,当使用泛型类时,必须在创建对象的时候指定类型参数的值,而使用泛型方法的时候,通常不必指明参数类型,因为编译器会为我们找出具体的类型。这称为类型参数推断(type argument inference)。因此,我们可以像调用普通方法一样调用f(),而且就好像是f()被无限次地重载过。它甚至可以接受GenericMethods作为其类型参数。

如果调用f()时传入基本类型,自动打包机制就会介入其中,将基本类型的值包装为对应的对象。事实上,泛型方法与自动打包避免了许多以前我们不得不自己编写出来的代码。

练习9:(1) 修改GenericMethods.java类,使f()可以接受三个类型各不相同的参数。

练习10:(1) 修改前一个练习,将方法f()的其中一个参数修改为非参数化的类型。

15.4.1   杠杆利用类型参数推断

人们对泛型有一个抱怨,使用泛型有时候需要向程序中加入更多的代码。考虑第11章中的holding/MapOfList.java类,如果要创建一个持有List的Map,就要像下面这样:



 

(本章稍后会介绍表达式中问号与extends的用法。)看到了吧,你在重复自己做过的事情,编译器本来应该能够从泛型参数列表中的一个参数推断出另一个参数。唉,可惜的是,编译器暂时还做不到。然而,在泛型方法中,类型参数推断可以为我们简化一部分工作。例如,我们可以编写一个工具类,它包含各种各样的static方法,专门用来创建各种常用的容器对象:



 

main()方法演示了如何使用这个工具类,类型参数推断避免了重复的泛型参数列表。它同样可以应用于holding/MapOfList.java:



 

对于类型参数推断而言,这是一个有趣的例子。不过,很难说它为我们带来了多少好处。如果某人阅读以上代码,他必须分析理解工具类New,以及New所隐含的功能。而这似乎与不使用New时(具有重复的类型参数列表的定义)的工作效率差不多。这真够讽刺的,要知道,我们引入New工具类的目的,正是为了使代码简单易读。不过,如果标准Java类库要是能添加类似New.java这样的工具类的话,我们还是应该使用这样的工具类。

类型推断只对赋值操作有效,其他时候并不起作用。如果你将一个泛型方法调用的结果(例如New.map())作为参数,传递给另一个方法,这时编译器并不会执行类型推断。在这种情况下,编译器认为:调用泛型方法后,其返回值被赋给一个Object类型的变量。下面的例子证明了这一点:



 

练习11:(1) 创建自己的若干个类来测试New.java,并确保New可以正确地与它们一起工作。

显式的类型说明

在泛型方法中,可以显式地指明类型,不过这种语法很少使用。要显式地指明类型,必须在点操作符与方法名之间插入尖括号,然后把类型置于尖括号内。如果是在定义该方法的类的内部,必须在点操作符之前使用this关键字,如果是使用static的方法,必须在点操作符之前加上类名。使用这种语法,可以解决LimitsOfInference.java中的问题:



 

当然,这种语法抵消了New类为我们带来的好处(即省去了大量的类型说明),不过,只有在编写非赋值语句时,我们才需要这样的额外说明。

练习12:(1) 使用显式的类型说明来重复前一个练习。

15.4.2   可变参数与泛型方法

泛型方法与可变参数列表能够很好地共存:



 



 

makeList()方法展示了与标准类库中java.util.Arrays.asList()方法相同的功能。

15.4.3   用于Generator的泛型方法

利用生成器,我们可以很方便地填充一个Collection,而泛型化这种操作是具有实际意义的:



 

请注意,fill()方法是如何透明地应用于Coffee和Integer的容器和生成器。

练习13:(4) 重载fill()方法,使其参数与返回值的类型为Collection的导出类:List、Queue和Set。通过这种方式,我们就不会丢失容器的类型。能够在重载时区分List和LinkedList吗?

15.4.4   一个通用的Generator

下面的程序可以为任何类构造一个Generator,只要该类具有默认的构造器。为了减少类型声明,它提供了一个泛型方法,用以生成BasicGenerator:



 



 

这个类提供了一个基本实现,用以生成某个类的对象。这个类必需具备两个特点:(1)它必须声明为public。(因为BasicGenerator与要处理的类在不同的包中,所以该类必须声明为public,并且不只具有包内访问权限。)(2)它必须具备默认的构造器(无参数的构造器)。要创建这样的BasicGenerator对象,只需调用create()方法,并传入想要生成的类型。泛型化的create()方法允许执行BasicGenerator.create(MyType.class),而不必执行麻烦的new Basic-Generator<MyType>(MyType.class)。

例如,下面是一个具有默认构造器的简单的类:



 

CountedObject类能够记录下它创建了多少个CountedObject实例,并通过toString()方法告诉我们其编号。

使用BasicGenerator,你可以很容易地为CountedObject创建一个Generator:



 

可以看到,使用泛型方法创建Generator对象,大大减少了我们要编写的代码。Java泛型要求传入Class对象,以便也可以在create()方法中用它进行类型推断。

练习14:(1) 修改BasicGeneratorDemo.java类,使其显式地构造Generator(也就是不使用create()方法,而是使用显式的构造器)。

15.4.5   简化元组的使用

有了类型参数推断,再加上static方法,我们可以重新编写之前看到的元组工具,使其成为更通用的工具类库。在这个类中,我们通过重载static方法创建元组:



 

下面是修改后的TupleTest.java,用来测试Tuple.java:



 

注意,方法f()返回一个参数化的TwoTuple对象,而f2()返回的是非参数化的TwoTuple对象。在这个例子中,编译器并没有关于f2()的警告信息,因为我们并没有将其返回值作为参数化对象使用。在某种意义上,它被“向上转型”为一个非参数化的TwoTuple。然而,如果试图将f2()的返回值转型为参数化的TwoTuple,编译器就会发出警告。

练习15:(1) 验证前面的陈述是否属实。

练习16:(2) 为Tuple.java添加一个SixTuple,并在TupleTest2.java中进行测试。

15.4.6   一个Set实用工具

作为泛型方法的另一个示例,我们看看如何用Set来表达数学中的关系式。通过使用泛型方法,可以很方便地做到这一点,而且可以应用于多种类型:



 

在前三个方法中,都将第一个参数Set复制了一份,将Set中的所有引用都存入一个新的HashSet对象中,因此,我们并未直接修改参数中的Set。返回的值是一个全新的Set对象。

这四个方法表达了如下的数学集合操作:union()返回一个Set,它将两个参数合并在一起;intersection()返回的Set只包含两个参数共有的部分;difference()方法从superset中移除subset包含的元素;complement()返回的Set包含除了交集之外的所有元素。下面提供了一个enum,它包含各种水彩画的颜色。我们将用它来演示以上这些方法的功能和效果。



 

为了方便起见(可以直接使用enum中的元素名),下面的示例以static的方式引入Watercolors。这个示例使用了EnumSet,这是Java SE5中的新工具,用来从enum直接创建Set。(在第19章中,我们会详细介绍EnumSet。)在这里,我们向static方法EnumSet.range()传入某个范围的第一个元素与最后一个元素,然后它将返回一个Set,其中包含该范围内的所有元素:



 

我们可以从输出中看到各种关系运算的结果。

下面的示例使用Sets.difference()打印出java.util包中各种Collection类与Map类之间的方法差异:



 



 

在第11章的“总结”中,我们使用了这个程序的输出结果。

练习17:(4) 研究JDK文档中有关EnumSet的部分,你会看到它定义了clone()方法。然而,在Sets.java中,你却不能复制Set接口中的引用。请试着修改Sets.java,使其不但能接受一般的Set接口,而且能直接接受EnumSet,并使用clone()而不是创建新的HashSet对象。

<!---->

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics