泛型就是通过编写模板代码来适应任意类型,也可以在使用的时候指定类型。它的好处在于使用时不必对类型进行强制转换,可以通过编译器完成对类型的检查。
注意泛型的继承关系:可以把 ArrayList<Integer>
向上转型为 List<Integer>
(类可以变),
但不能把 ArrayList<Integer>
向上转型为 ArrayList<Number>
(T不能变成父类)。
泛型只在编译阶段有效。
List<Integer> integerList = new ArrayList<>();
List<String> stringList = new ArrayList<>();
System.out.println(integerList.getClass().equals(stringList.getClass())); // true
有上面的例子可以看出,java 的泛型只在编译阶段有效,之后会将相关信息擦拭,不会进入到运行时期。
将泛型类型用于类中,这样定义的类被称为泛型类,如 List
、 Set
和 Map
等。
class Generic<T> {
private T name;
public Generic(T name) {
this.name = name;
}
public T getName() {
return name;
}
}
上面是自定义的一个普通泛型类,在使用泛型类的时候可以传入泛型实参,也可以不传入,这样类型可以为任何类型。
注意:泛型的参数类型只能是类(Integer
等),不能是基础类型(int
等);
泛型接口与泛型类的定义和使用方法类似,常被用在各种类的 generator
中。
interface Generator<T> {
int size();
T add(T element);
interface Generator<T> {
int size();
T add(T element);
T remove(int index);
}
// 未传入泛型实参时,类也要申明泛型,否则编译报错:class MyGenerator implements Generator<T>
class MyGenerator<T> implements Generator<T> {
@Override
public int size() {
return 0;
}
@Override
public T add(T element) {
return null;
}
@Override
public T remove(int index) {
return null;
}
}
// 传入实参时,方法的泛型参数也要申明类型
class StringGenerator implements Generator<String> {
private List<String> stringList = Arrays.asList("a", "b", "c");
@Override
public int size() {
return stringList.size();
}
@Override
public String add(String element) {
stringList.add(element);
return element;
}
@Override
public String remove(int index) {
return stringList.remove(index);
}
}
T remove(int index);
}
WHY?
Java 泛型的泛型没有协变和逆变特性。
数组是可以协变的,比如 Dog extends Animal
,那么 Animal[]
里面可以添加Dog对象。
而集合是不能协变的,也就是说 List<Animal>
不是 List<dog>
的父类,这时候就可以用到通配符了。
List<Number> numbers = new ArrayList<Integer>(); // Incompatible types. Found: 'java.util.ArrayList<java.lang.Integer>', required: 'java.util.List<java.lang.Number>'
List<? extends Number> extendNumbers = new ArrayList<Integer>(); // 使用泛型通配符,OK
a. 无边界的通配符(Unbounded Wildcards), 就是 <?>
, 比如 List<?>
, 它的主要作用就是让泛型能够接受未知类型的数据。
List<?> list = new ArrayList<>();
list.add(1); // 'add(capture<?>)' in 'java.util.List' cannot be applied to '(int)'
list.add("abc");
list.add(null); // 只有null是所有引用数据类型都具有的元素.
int a = list.get(1);
String b = list.get(2); // Incompatible types. Found: 'capture<?>', required: 'java.lang.String'
Object c = list.get(3); // 只能返回Object对象
List list1 = new ArrayList<>();
list1.add(2); // Unchecked call to 'add(E)' as a member of raw type 'java.util.List'
Object d = list1.get(0);
由上例可以看到 List<?>
只能 add null
,get
返回的是 Object
;List
啥都能添加,但会有警告。
b. 固定上边界的通配符(Upper Bounded Wildcards), 使用它的泛型能够接受指定类及其子类类型的数据, 采用 <? extends E>
的形式声明使用该类通配符, 这里的 E 就是该泛型的上边界。
public class Test {
public static void main(String[] args) {
ArrayList<Integer> integerArrayList = new ArrayList<>();
integerArrayList.add(1);
// 'test(java.util.ArrayList<java.lang.Number>)' in 'cn.shawda.Test' cannot be applied to '(java.util.ArrayList<java.lang.Integer>)'
System.out.println(test(integerArrayList));
}
static int test(ArrayList<Number> arrayList) {
return arrayList.get(0).intValue();
}
}
可以看到,由于 ArrayList<Integer>
不是 ArrayList<Number>
的子类,所以 test 方法不接受前者为参数。若前者以参数传到 test 方法中也能得到返回的值,使用 ArrayList<? extend Number>
可以使 test 方法接收所有泛型类型为 Number
或其子类的 ArrayList
类型。
public class Test {
public static void main(String[] args) {
ArrayList<Integer> integerArrayList = new ArrayList<>();
integerArrayList.add(1);
System.out.println(test(integerArrayList)); // OK
}
static int test(ArrayList<? extends Number> arrayList) {
Number number0 = 0;
arrayList.add(number0); // 无法add Number类型
arrayList.add(new Integer(10)); // 'add(capture<? extends java.lang.Number>)' in 'java.util.ArrayList' cannot be applied to '(java.lang.Integer)'
arrayList.add(null); // OK
Number number = arrayList.get(0); // Number类型
return number.intValue();
}
}
可以 get 到 Number
类型的结果,只能add null
。
由上可知,在使用类似 <? extend Number>
通配符作为方法的参数时:
- 方法内部可以调用获取
Number
的方法,如Number number = arrayList.get(0);
;- 方法内部无法调用写
Number
的方法(除null
),如arrayList.add(new Integer(10));
;
也就是:使用 extends
通配符表示可读不可写。
c. 固定下边界的通配符(Lower Bounded Wildcards), 使用它的泛型能够接受指定类及其父类类型的数据, 采用<? super E>的形式明使用该类通配符, 这里的 E 就是该泛型的下边界。
public class Test {
private static void testSuper(List<? super Integer> list) {
list.add(1);
// Incompatible types. Found: 'capture<? super java.lang.Integer>', required: 'java.lang.Integer'
Integer integer = list.get(0);
Object object = list.get(0); // OK
}
}
由上可知,使用 <? super Integer>
通配符表示,允许调用 add 方法传入 Integer 的引用,不允许调用 get 方法获得 Integer 的引用,只能获得 Object 的引用。
可以简单将 extends 看成允许读不允许写,super 看成允许写不允许读。
分别在何时使用它们呢?这就要说到 PECS 原则了:Producer Extends Consumer Super,也就是:若需要返回,那它是生产者,使用 extends 通配符;若需要写入,那它是消费者,使用 super 通配符。
这里以 Collections
的 copy()
方法为例:
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
int srcSize = src.size();
if (srcSize > dest.size())
throw new IndexOutOfBoundsException("Source does not fit in dest");
if (srcSize < COPY_THRESHOLD ||
(src instanceof RandomAccess && dest instanceof RandomAccess)) {
for (int i=0; i<srcSize; i++)
dest.set(i, src.get(i));
} else {
ListIterator<? super T> di=dest.listIterator();
ListIterator<? extends T> si=src.listIterator();
for (int i=0; i<srcSize; i++) {
di.next();
di.set(si.next());
}
}
}
其中 src 的值需要返回,是生产者,用 extends 通配符;dest 的值要写入,是消费者,用 super 通配符。
无限定通配符 <?>
不允许读(只能获取 Object)也不允许写(null 除外),只能做一些 null 判断。