1.什么是泛型
概括地讲,泛型就是我们在定义类、接口或方法的时候将某些引用到的类或接口参数化,这样定义的类、接口、方法就叫做泛型。这类似定义一般方法时,将传入的值参数化,不同的是泛型传的是类型,而方法传是具体的值。
如,public interface List<E>
就是将集合存放元素的类型参数化,即泛型。
2.泛型的定义
泛型类
定义
class Name<T1,T2,...,Tn>{/*...*/}
泛型接口的定义和泛型类定义相似,将class换成interface即可。
T1,T2,...,Tn 就是泛型的形参。例如:
/**
* @param <T> Box中存放至的类型,即形参
*/
class Box<T> {
// T stands for "Type"
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
泛型形参命名习惯
- E-Element
- K-Key
- V-Value
- T-Type
- N-Number
声明和实例化
声明和实例化泛型类时,必须传入具体的类型。
如:Box<Integer> intBox = new Box<Integer>();
在Java SE 7及以后的版本,new后面<>内的类型可以省略,因为编译器从前面的声明,就已经能确定类型。这样我们可以用如下的写法,创建泛型类的实例Box<Integer> intBox = new Box<>();
,这种写法叫做钻石写法,<>是不是像一颗钻石啊。
泛型的原型
泛型类省略了<T>,就叫做原型,如Box就是Box<T>的原型。只有泛型类(接口)有原型,非泛型类(接口)是没有原型的。
原型之所以出现,是因为在JDK5以前,很多的类都没有泛型,为了实现泛型对原型的兼容。这样我们可以创建List的原型对象List list = new ArrayList()
,使用原型就会丧失泛型的安全性。
泛型方法
定义
<T1,T2,...,Tn> ReturnClassType methodName(T1 t1,T2 t2,...,Tn tn){/*...*/}
注意以上定义省略了方法的修饰符,在实际定义泛型方法时根据情况补充修饰符,另外方法参数中也可以用非泛型类的参数。
泛型方法的类型参数的有效范围仅为本方法内。静态方法、非静态方法以及构造方法都可以是泛型方法。
如:
public static <K,V> V get(Map<K, V> map, K key, V defaultValue) {
if (key != null) {
if (map != null && !map.isEmpty()) {
V value = map.get(key);
return (value == null) ? defaultValue : value;
}
return defaultValue;
}
return null;
}
有界类型参数
有界类型参数,就是通过extends关键字限制类型参数只能为某一类型的子类,这是实现泛型算法的关键。如:
public static <T extends Comparable<T>> int countGreaterThan(T[] anArray, T elem) {
int count = 0;
for (T e : anArray)
if (e.compareTo(elem) > 0)
++count;
return count;
}
除了通过类来限制外,还可以使用接口来限制泛型,另外也可以通过类和接口共同来限制泛型。结构如下:
class GenericTest<T extends TestClass & TestInterface1&...&TestInterfacen>{/*...*/}
如果用类和接口共同来界定是,类必须写在前面。
3.通配符?
上界通配符<?extends ParentClass>
上界通配符用于限制参数类型为某一明确的类及其子类。
如:
public static double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
无界通配符<?>
使用无界通配符,要求方法的逻辑只需要使用Object类的方法就可以实现,如:
public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}
下界通配符<?super ChildClass>
下界通配符用于限制参数类型为某一明确的类及其父类,如。
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}
通配符选择指南
- 如果是输入参数变量,选用上界通配符。
- 如果是输出参数变量,选用下界通配符。
- 如果即是入参又是出参,不使用通配符
子类、泛型与通配符的关系
两个类存在父子关系,它们的泛型类之间是没有父子关系的,如Integer是Number的子类,但是Box<Integer>并不是Box<Number>的子类。但是可以通过通配符来建立关系,如List<? extends Integer>是List<? extends Number>的子类。
4.泛型的好处
- 增加编译时的类型检查,避免运行时的类型转换异常。
- 提高代码的复用性,并能够复用算法。
- 无须再写强制类型转换的代码。
https://docs.oracle.com/javase/tutorial/
java/generics/types.html