说明:CSDN 平台上 Markdown 编辑器识别 < 和 > 为非法字符,导致文章无法发布,于是使用 [ 和 ] 来替代。说实话,转载文章的排版真是能瞅死个眼啊。
Java 中类似
这样的类型参数 (Type Parameter)
在 JDK 中或工具类方法中经常能看到。比如 java.util.Collections
类中的这个方法声明:
public static
void sort (List
> list)
我知道 extends
和 super
这样的关键字在泛型中是干什么的,但对上面这样复杂的类型参数声明着实有点看不懂。
我觉得类型参数T
写成这样就足够了:
>
可T
偏偏被声明成这样:
>
搞这么复杂图啥呢?难道 Java 只是高智商人士的玩具?
终于有一天,我觉得有点对不起 Java Developer 这个头衔了,于是认真看了看书,认真 Google 了一下,终于搞明白了这样的类型参数是怎么回事儿。
1、>
和 >
有什么不同
中类型 T
必须实现 Comparable
接口,并且这个接口的 参数类型 是 T
。只有这样,T
的实例之间才能相互比较大小。例如,在实际调用时若使用的具体类是 Dog
,那么 Dog
必须 implements Comparable
。
中类型 T
必须实现 Comparable
接口,并且这个接口的 参数类型 是T
或T 的任一父类
。这样声明后,T
的实例之间、T
的实例和它的父类的实例之间,可以相互比较大小。例如,在实际调用时若使用的具体类是 Dog
(假设 Dog 有一个父类 Animal),Dog
可以从 Animal
那里继承 Comparable
,或者自己 implements Comparable
。
2、 我对 >
类型参数的理解
光看上面的定义除了摸不着头脑,不会有其它感觉。下面用代码来说明为什么要这样声明。
2.1 代码运行环境
我使用的 JDK 版本是:1.8.0_60 ,在 Eclipse 中编译运行。因为注释用了中文,编码采用 UTF-8。如果你要在命令行下编译、运行,编译时要使用 -encoding UTF-8 选项:
javac -encoding UTF-8 TypeParameterTest.java
另外,Eclipse 中的警告、错误信息跟命令行中的不一样(个人感觉 Eclipse 中的信息要好懂一些)。以下的示例以 Eclipse 中的信息为准。
2.2 示例代码
package generics3;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class TypeParameterTest {
//第一种声明:简单,灵活性低
public static > void mySort1(List list) {
Collections.sort(list);
}
//第二种声明:复杂,灵活性高
public static > void mySort2(List list) {
Collections.sort(list);
}
public static void main(String[] args) {
//在这个方法中要创建一个 Animal List 和一个 Dog List,然后分别调用两个排序方法。
}
}
class Animal implements Comparable {
protected int age;
public Animal(int age) {
this.age = age;
}
//使用年龄与另一实例比较大小
@Override
public int compareTo(Animal other) {
return this.age - other.age;
}
}
class Dog extends Animal {
public Dog(int age) {
super(age);
}
}
上面的代码包括三个类:
Animal
实现了Comparable
接口,通过年龄来比较实例的大小。Dog
继承自 Animal
。TypeParameterTest 类中提供了两个排序方法和测试用的 main() 方法:
-
mySort1()
使用
类型参数> -
mySort2()
使用
类型参数> -
main()
测试方法。在这个方法中要创建一个Animal List
和一个Dog List
,然后分别调用两个排序方法
2.3 测试 mySort1() 方法
// 创建一个 Animal List
List animals = new ArrayList();
animals.add(new Animal(25));
animals.add(new Dog(35));
// 创建一个 Dog List
List dogs = new ArrayList();
dogs.add(new Dog(5));
dogs.add(new Dog(18));
// 测试 mySort1() 方法
mySort1(animals);
mySort1(dogs);
Line 13 出编译错误了。Eclipse 说:
The method mySort1(List
) in the type TypeParameterTest is not applicable for the arguments (List
)
为什么会出错误呢? mySort1() 方法的类型参数是
,它要求的类型参数是类型为 T
的 Comparable
。如果传入的是List
,没问题,因为Animal implements Comparable
。但是,如果传入的参数是 List
时会有问题,**因为 Dog 没有 implements Comparable
**,它只是从 Animal 继承了一个 Comparable
。
不知道大家注意到没有,那个 animals list 中实际上是包含一个 Dog 实例的 。如果你碰上类似的情况(子类 list 不能传入到一个方法中),可以考虑把子类实例放到一个父类 List 中,避免编译错误。
2.4 测试 mySort2() 方法
public static void main(String[] args) {
// 创建一个 Animal List
List animals = new ArrayList();
animals.add(new Animal(25));
animals.add(new Dog(35));
// 创建一个 Dog List
List dogs = new ArrayList();
dogs.add(new Dog(5));
dogs.add(new Dog(18));
// 测试 mySort2() 方法
mySort2(animals);
mySort2(dogs);
}
两个方法调用都没有问题。 第二个方法不但可以接受 Animal implements Comparable
这样的参数,也可以接收:Dog implements Comparable
这样的参数。
2.5 Dog
可以 implements Comparable
吗?
如果让 Dog implements Comparable
class Dog extends Animal implements Comparable
{
public Dog(int age)
{
super(age);
}
}
很不幸,出错了。Eclipse 说:
The interface Comparable cannot be implemented more than once with different arguments:
Comparable
andComparable
就是说,Dog
已经从父类 Animal
那里继承了一个Comparable
,它不能再实现一个 Comparable
。
如果子类不喜欢父类的实现怎么办? Override 父类的 public int compareTo(Animal other) 方法。
2.6 >
类型参数声明的好处
对 Animal/Dog 这两个有父子关系的类来说:
可以接受 List
,也可以接收List
。 而
只可以接收List
。所以,
这样的类型参数对所传入的参数限制更少,提高了 API 的灵活性。总的来说,在保证类型安全的前提下,要使用限制最少的类型参数。
3 其他
3.1 JDK 中的例子
JDK 中这样的例子很多,比如 java.util.Date 和 java.sql.Date 这两个类:
public class Date implements java.io.Serializable, Cloneable,
Comparable
public class Date extends java.util.Date
- java.sql.Date 是 java.util.Date 的子类。
- java.util.Date 实现了 Comparable
,所以 ~java.sql.Date 也拥有了 Comparable
类型。 - java.sql.Date 不能再 implements
Comparable
。 - 如果你有一个
List
并对它排序的话,只能传给拥有
这种类型参数的方法。>
3.2 《Effective Java》 一书对 >
这种类型参数的解释
这本书使用 Produce-Extends, Consume-Super (PESC) 原则来解释。这个原则不但可以帮你理解复杂的声明,而且可以指导你在定义类型参数时,何时使用 extends ,何时使用 super,有助于你写出复杂的、适应性强的类型参数来。
有兴趣的同学可以看看这本书的 Item 28: Use bounded wildcards to increase API flexibility
3.3 泛型是个脑力活
简单的泛型很好理解很好用,但稍微复杂一点,就变得很难理解。
3.3.1 脑子开窍开大了
在琢磨这个问题时,我脑洞一开,心想,T 这样的东西太一般化,有点摸不着头脑,不好理解。如果把 T 换成一个具体类,应该会好理解。于是我就想出了这样两个声明:
>
Dog extends Comparable super Dog>>
我挺得意,觉得这样先用具体的类理解,然后再换成一般的类型,由具体到一般,多符合逻辑啊!后来发现这样的声明有个大问题,Eclipse 给了个黄色警告:
The type parameter Dog is hiding the type Dog
上面这句话翻译过来就是: 类型参数 Dog 掩盖了 类型 Dog 。
在
这个声明中,extends 前面的部分必须是类型参数。类型参数一般用T
,E
这样的大写字母,但也可以是小写或者一个单词(只要是个标识符就行)。所以,Dog
在这里是一个类型参数,不是一个具体类。但我已经创建过一个具体的Dog
类了。怎么办?类型参数 Dog 赢了,具体类 Dog 暂时靠边站。类似于你有一个实例变量x
。然后你在一个方法中又声明了一个局部变量也叫x
。在执行这个方法时,方法中的这个局部变量 x 就暂时掩盖了(shadow) 实例变量x
。
3.3.2 脑子一点也不开窍
有时候想得多了,脑子就糊涂了,一点儿也不开窍,连简单问题也不明白了。 比如,我可以这样定义一个方法:
public static void mySort3(List list)
{
Collections.sort(list);
}
也可以这样定义一个方法:
public static void mySort4(List extends Animal> list)
{
Collections.sort(list);
}
第二个方法没有T
,也能实现跟第一个方法同样的功能,我为什么非得要一个T
呢?在脑子思虑过度的情况下,进死胡同了。在我准备放狗搜之前,总算想明白了。
第二个方法中,参数是: List extends Animal> list
。 这个方法可以接收 List
,也可以接收 List
。这里没有使用类型参数,只是使用泛型的限定符对所传入的 List 的类型做了一个限定。
而在第一个方法中,使用了一个类型参数 T
。这个 T
可以是 Animal
,也可以是 Animal
的子类 Dog
。
在第一个方法中,看不出定义一个类型参数有什么作用。但是,类型参数不但可以在方法参数中使用,也可以在方法返回值和方法体内使用。比如下面这个方法:
public > T test1(T t, List list)
{
for (T element : list)
{
if (element.equals(t))
return t;
}
return null;
}
你定义了一个类型参数 T ,这个 T 定义成 :
。定义好之后,你就可以在参数中,返回值中,以及方法体内使用这个 T 了。如果不使用类型参数,是达不到这种效果的。
你也可以定义多个类型参数,并让这些参数之间有关联:
public T test2(T t, S s)
{
return s;
}
3.3.3 多练习练习
我从 JDK 中找了两段程序,看看能不能看明白。
程序一
TreeSet 类是这样声明的:
public class TreeSet extends AbstractSet
implements NavigableSet, Cloneable, java.io.Serializable
它有个 constructor 是这样定义的:
public TreeSet(Comparator super E> comparator) {
this(new TreeMap<>(comparator));
}
程序二
Collections 类的 max() 方法:
public static T max(Collection extends T> coll, Comparator super T> comp) {
if (comp==null) return (T)max((Collection) coll);
Iterator extends T> i = coll.iterator();
T candidate = i.next();
while (i.hasNext()) {
T next = i.next();
if (comp.compare(next, candidate) > 0)
candidate = next;
}
return candidate;
}
这段程序也展示了类型参数的另一种用法:类型参数本身只是简简单单的 super T>
, extends T>
4 推荐一本书:《SCJP Sun Certified Programmer for Java 6 Exam 310-065》
这本书的作者是:Kathy Sierra 和 Bert Bates,所以此书也被称为“KB书”。从书名上就可以看出这本书是为 SCJP 6 认证考试而写的。因为是认证考试教材,这本书讲的不是很深,有很多方面也没有涉及到。但这本书对 Java 基本概念的讲解非常透彻而又不啰嗦。它不但是认证宝典中的宝典,而且也非常适合已经入门的 Java 程序员阅读。这本书现在有 Java 7 的版本,不知道以后会不会出 Java 8 版本。如果出的话,我会去买一本。
5、参考书目和链接
本文原链接
What is super T>
syntax?
What is the difference between these class declarations with Comparable?
Java:泛型
Effective Java (2nd Edition)
SCJP Sun Certified Programmer for Java 6 Exam 310-065