本文接上一篇 Java中的泛型generics从0到1(上)
泛型的方法
泛型方法就是引入了类型参数的方法,这和定义泛型类一样。但类型参数只能用于所声明的方法中。
泛型方法可以是静态的,也可以是非静态的,也可以是类的构造函数。
泛型方法的语法是,在返回值前面,出现类型参数列表。对于静态方法,类型参数必须出现在返回值的类型前。
下面的实例代码 类Util包含一个泛型方法compare,代码如下
public class Util { public static boolean compare(Pair p1, Pair p2) { return p1.getKey().equals(p2.getKey()) && p1.getValue().equals(p2.getValue()); }}public class Pair { private K key; private V value; public Pair(K key, V value) { this.key = key; this.value = value; } public void setKey(K key) { this.key = key; } public void setValue(V value) { this.value = value; } public K getKey() { return key; } public V getValue() { return value; }}
调用这个compare方法的完整语法如下
Pair p1 = new Pair<>(1, "apple");Pair p2 = new Pair<>(2, "pear");boolean same = Util.compare(p1, p2);
在上面的例子中,显示的提供了类型,但一般来说,也可以不指定类型,由编译器来自动推断。
Pair p1 = new Pair<>(1, "apple");Pair p2 = new Pair<>(2, "pear");boolean same = Util.compare(p1, p2);
这个特性叫类型推断 ,将在后面介绍到。它让你在调用泛型方法时就像调用普通方法一样。
上面介绍的泛型是任意类型都可以,下面介绍的有界泛型就可以把类型限制在一个小范围内,这样就可以利用到该类型的方法。
有界的类型参数
有时候,你想限制一下实际传入的类型参数,这时有界的类型参数就派上用场了。
比如有个类,只想处理Number和它的子类,这时就需要限制一下传入的类型。
申明一个有界的类型参数的语法是,先列出类型参数,后面跟上关键字 extends,然后再跟上它的上界。下面这个例子中的上界是Number 。注意这里的extends其实有两个含义, 既可以是继承自某个类,也可以是实现了某个接口。
public class Box { private T t; public void set(T t) { this.t = t; } public T get() { return t; } public void inspect(U u){ System.out.println("T: " + t.getClass().getName()); System.out.println("U: " + u.getClass().getName()); } public static void main(String[] args) { Box integerBox = new Box(); integerBox.set(new Integer(10)); integerBox.inspect("some text"); // error: this is still String! }}
由于在泛型方法的类型参数上增加了上界,main方法中的第三行这时编译就出错了。因为inspect的参数类型需要Number及其子类,String并不是。
有界参数不仅可以用于泛型方法,还可以将其用在泛型类上,这样你就可以调用该上界的方法了。
因此有界类型有两个作用
1,可以限制初始化时的实际类型
2,在定义了有界的类型的类中,可以使用该有界类型的类的方法。
比如下面的例子,由于T是Integer的子类,因此在isEven方法中,n可以调用Number的方法intValue,
这在实现泛型的算法上很关键。
public class NaturalNumber { private T n; public NaturalNumber(T n) { this.n = n; } public boolean isEven() { return n.intValue() % 2 == 0; } public static void main(String[] args) { NaturalNumber naturalNumber = new NaturalNumber<>(4); boolean even = naturalNumber.isEven(); System.out.println(even); }}
类型参数可以有多个界限
上面的类型展示了类型参数有一个界限,实际上,类型参数可以有多个界限,语法如下
它的含义是T类型是界限类型列表中全部类的子类。换句话说,T类实现了这些接口。
而且如果在这些上界中有一个是class,必须放在第一位置,把接口放在后面,不然编译会报错。
由于Java单继承只有一个父类,所以只有第一个类型可能是class,后面的都是接口类型。表示T实现了这些接口。
Class A { /* ... */ }interface B { /* ... */ }interface C { /* ... */ }class D { /* ... */ } // 合法,class在第一个位置class D { /* ... */ } // compile-time error
泛型方法中的有界的类型参数
有界的类型参数是实现泛型算法的关键。
比如下面的代码是一个算法,用于计算一个数组中,比给定的元素elem大的个数,实现的方法很容易理解,但却无法编译,因为大于号>只能对原始类型进行比较。不能用于比较对象。 为了解决这个问题,需要用到有界限的类型参数。
public static int countGreaterThan(T[] anArray, T elem) { int count = 0; for (T e : anArray) if (e > elem) // compiler error 无法编译 ++count; return count;}
将上面的方法改成有界限的类型参数,指明了这些对象都需要实现这个比较接口Comparable,新代码如下
public interface Comparable { public int compareTo(T o);}public static > int countGreaterThan(T[] anArray, T elem) { int count = 0; for (T e : anArray) if (e.compareTo(elem) > 0) ++count; return count;}
这样这个算法就可以编译了。
在集合框架中有许多要用到的比较的方法,其元素都要求实现这个Comparable接口。如Map中的方法
public static , V> Comparator> comparingByKey() { return (Comparator> & Serializable) (c1, c2) -> c1.getKey().compareTo(c2.getKey()); }
泛型有关的继承和子类型
在Java中,在类型合理的情况下,可以把一种类型的对象赋给另一种类型。如下面的代码,可以把Integer的对象赋给Object类型的变量,因为Object是Integer的超类型supertype
Object someObject = new Object();Integer someInteger = new Integer(10);someObject = someInteger; // OK
在面向对象的术语中,这是一个“is a“关系。Integer是一种Object,所以这种赋值合法。另外Integer也是一种 Number,所以下面的代码也合法。
public void someMethod(Number n) { /* ... */ }someMethod(new Integer(10)); // OKsomeMethod(new Double(10.1)); // OK
这种逻辑在泛型中也是OK的,下面在实例化Box时传入的类型是Number,调用box的方法时,传入Number的子类型都是可以的。
Box box = new Box();box.add(new Integer(10)); // OKbox.add(new Double(10.1)); // OK
下面考虑这个方法
public void boxTest(Box n) { /* ... */ }
这个方法,可以传什么实参进来,可以是Box或Box吗?答案是No。
为什么呢?
虽然Integer是Number的子类,但是Box并不是Box的子类。
在泛型中这非常容易搞混,所以需要重点的学一下。
Box和Box的共同父对象是Object
尽管Integer是Number的子类型,Box不是Box的子类型
如果两个泛型类的类型参数是相关的,那这两个泛型类有类似子类型的关系吗?有,等到后面了解到通配符的知识了,就可以了解这部分内容了。
泛型类的子类型
创造一个泛型类的子类型可以通过继承或实现接口的方式 。
这里用Collections中的类来举例,下面是ArrayList,List的定义。
public class ArrayList extends AbstractList implements List {}public interface List extends Collection {}
可以看出ArrayList实现了List,List又继承了Collection。
所以 ArrayList是List的子类型,
List是Collection的子类型。
只要他们的实参类型是相同的,他们之间就有子类型的关系。
现在假设你自定义了一个List叫 PayloadList,关联了一个可选的泛型类型P到每个元素上,这个接口的声明如下
interface PayloadList extends List { void setPayload(int index, P val); ...}
下面这几个参数化的类型都是List的子类型
- PayloadList
- PayloadList
- PayloadList
本文就介绍到这里,下一篇的内容包括 泛型通配符,有上界的通配符,类型擦除等。
本文为《Java中的泛型系列》第二篇,其他的还有
Java中的泛型generics从0到1(上)
Java中的泛型generics从0到1(中)
Java中的泛型generics从0到1(下)
Java中的泛型generics从0到1(甲)
Java中的泛型generics从0到1(乙)
Java中的泛型generics从0到1(丙)
Java中的泛型generics从0到1(丁)