Java 泛型

不定类型的公共框架

泛型


没有泛型时,只能用Object作为通用类型,引入泛型实现了类型约束和自动类型转换
比如List可以接受任何对象,但是取出后需要自行转换,而List<E>只能接受E类型的对象,并且取出后直接被转成E类型

泛型类与接口

以类为粒度的泛型,常见用法是实现定制参数类型, 返回值类型

Sample
1
2
3
4
interface Sample<T> {
T getValue();
void setValue(T value);
}

方法泛型

可以脱离泛型类单独存在,常见用法是根据参数类型决定返回类型

Sample
1
2
3
public <T> T method(T t) {
return t;
}

不定类型返回值

特殊需求下,需要返回类型不固定
效果是自适应类型,按照赋值类型进行强制转换

Sample
1
2
3
4
5
6
7
8
9
10
@SuppressWarnings("unchecked")
public static <T> T getValue(Object obj) {
if (obj instanceof Integer) {
return (T)Integer.valueOf(1);
} else if (obj instanceof String) {
return (T)String.valueOf("1");
} else {
return null;
}
}

调用时看不到类型转换
只适合有明确约定确保类型匹配的场景,错误使用会导致强转失败

Sample
1
2
String s = getValue("1");
Integer i = getValue(1);

List<?>,List<Object>List区别


表面上看,三者都可以容纳任何类型

  • List不指定任何类型,称为Raw Type,同时具有读写能力,但是写入时会报Raw Type类型安全的警告
  • List<Object>明确了泛型类型,具备读写能力
  • List<?>通配符限制了写入能力(因为不确定类型,新增无法判定,删除可以),只能读取

不指定参数类型的方式只用于兼容没有泛型时的旧代码
从兼容上看,泛型可以接受老代码Raw类型赋值,编译器不会检查

Sample
1
List<Object> list = new ArrayList();

如果都使用泛型,那么编译器检查类型,值得注意的是泛型赋值没有协变,类型不同就会报错

Sample
1
List<Object> list = new ArrayList<Integer>();  //compile error

使用Raw类型会丧失泛型功能,还有可能造成编译错误

  • 无法调用泛型方法
Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@interface Label {

}

@Label
public class Test {
public static void main(String[] args) throws Exception {
Class<?> c1 = Class.forName("test.Test");
Label label1 = c1.getAnnotation(Label.class);

Class c2 = Class.forName("test.Test");
Label label2 = c2.getAnnotation(Label.class); //Type mismatch: cannot convert from Annotation to Label
}
}
  • 无法类型转换
Sample
1
2
3
4
5
6
7
8
public static List<Integer> method(List<String> param) {
return null;
}

public static void main(String[] args) {
List list = new ArrayList();
Integer i = method(list).get(0); // Type mismatch: cannot convert from Object to Integer
}

泛型约束


不能确定某个类型,但是能限制一些范围
考虑形状问题

1
2
3
Element---Shape
|---Rectangle--Square
|---Triangle

标准的赋值是父类可以容纳子类对象Shape s = square;,不能反过来把父类对象赋给子类

extends

对象相容性
第一行声明表示列表中存的是某种形状,也就是说每个数据都是形状的子类实例,形状是上限
第二行赋值完全合法,因为都是容纳了某种具体形状
第三行报错,因为编译器不知道列表中到底存了啥类型,万一是正方形列表,那么就相当于让子类容纳了父类对象,不合法

Sample
1
2
3
List<? extends Shape> list = new ArrayList<>();
Shape s = list.get(0);
list.add(new Rectangel()); //error

extends方便读取,适合作为方法返回值

容器相容性
根据声明这里链表是某种矩形子类,因此父类是不行的

Sample
1
2
3
4
List<? extends Rectangle> list;
list = new ArrayList<Shape>(); //error
list = new ArrayList<Rectangle>();
list = new ArrayList<Square>();

super

对象相容性
第一行声明表示列表中存的是某种超越形状的存在,形状是下限
第二行赋值显然不合法,超越形状的数据显然不适合塞到形状对象里
之后的写入操作都是合法的,都符合子类加到父类容器

Sample
1
2
3
4
5
List<? super Shape> list = new ArrayList<>();
Shape s = list.get(0); //error
list.add(new Rectangel());
list.add(new Square());
list.add(new Triangel());

super方便写入,适合作为方法参数

容器相容性
根据声明这里链表是某种矩形父类,因此子类是不行的

Sample
1
2
3
4
List<? super Rectangle> list;
list = new ArrayList<Shape>();
list = new ArrayList<Rectangle>();
list = new ArrayList<Square>(); //error

PECS(Producer Extends Consumer Super)原则

  • 生产者输出内容,作为上游,使用extends方便下游读取
  • 消费者写入内容,作为下游,使用super方便处理写入,作为下游的List

Collections类的copy方法使用了两种泛型约束声明

  • 数据来源用于读取,使用extends
  • 数据目的用于写入,使用super
Sample
1
2
public static <T> void copy(List<? super T> dest, List<? extends T> src) {
}

泛型类和泛型方法约束

针对泛型类和泛型方法,即类声明时的尖括号和方法前的尖括号,只能出现上界extends,不能出现下界super
指定上界后,类型擦除后就是上界,可以约束参数类型,可以访问上界中的方法,而下界擦除后就是Object,没什么好处

多个约束

使用&可以加多个约束

Sample
1
2
3
4
interface Sample<T extends Serializable & Comparable> {
T getValue();
void setValue(T value);
}

类型擦除


泛型在编译期进行约束,可以静态检查,但是考虑到老代码兼容性,类型信息在真正运行时会被抹去

虽然两个列表看上去是不同类型的,但是运行时都是加载同一个类java.util.ArrayList,没有类型信息

Sample
1
2
3
List<Integer> intList = new ArrayList<>();
List<String> stringList = new ArrayList<>();
System.out.println(intList.getClass() == stringList.getClass()); //true

重载失败,因为类型抹掉后参数类型是一样的

Sample
1
2
3
4
5
public void method(List<Integer> list) {
}

public void method(List<String> list) {
}

类型擦除后,如果泛型参数没有上界,就用Object替代,如果有,就用第一个上界参数替代

运行时获取泛型类型


由于类型擦除,泛型类的方法是不能直接拿到泛型类型的,泛型类型只是一个类型参数,并不是真正的类

Sample
1
2
3
4
5
class Sample<T> {
public T newInstance() throws Exception {
return T.class.newInstance(); //error
}
}

方法1,既然拿不到,那就直接构造传入

Sample
1
2
3
4
5
6
7
8
9
class Sample<T> {
private Class<T> generic;
public Sample(Class<T> generic){
this.generic = generic;
}
public T newInstance() throws Exception {
return generic.newInstance();
}
}

调用时有点冗余的感觉

Sample
1
String s = new Sample<String>(String.class).newInstance();

方法2,如果有子类,可以利用抽象类提供额外方法,返回泛型类型
代价是每个子类实现额外的方法

Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
abstract class Sample<T> {
public T newInstance() throws Exception {
return getGeneric().newInstance();
}

public abstract Class<T> getGeneric();
}

class Child extends Sample<String> {
@Override
public Class<String> getGeneric() {
return String.class;
}
}

方法3, 如果有子类,可以利用获取父类泛型参数的方法
原理是父类调用this实际上是子类对象

Sample
1
2
3
4
5
6
7
8
9
10
11
abstract class Sample<T> {
public T newInstance() throws Exception {
Type type = ((ParameterizedType)this.getClass().getGenericSuperclass())
.getActualTypeArguments()[0];
return (T)Class.class.cast(type).newInstance();
}
}

class Child<T> extends Sample<T> {

}

实现根据参数创建实例

Sample
1
String s = new Child<String>().newInstance();

如果根本没有显式子类,还可以直接以匿名子类的形式,调用时有个空的实现

Sample
1
2
Sample<String> p = new Sample<String>(){};
String s = p.newInstance();

解析泛型参数,还可以采用Spring的工具类GenericTypeResolver

Sample
1
2
3
4
5
6
abstract class Sample<T> {
public T newInstance() throws Exception {
Class<?> genericClass = GenericTypeResolver.resolveTypeArgument(this.getClass(), Sample.class);
return (T)genericClass.newInstance();
}
}

集合间继承关系


虽然String是Object的子类,Object列表也显然能容纳String对象,但是直接把String列表进行赋值会报错
也就是说类型不同的集合不能直接赋值,因为两个列表之间没有直接的关系,传参时候要注意

Sample
1
2
3
4
List<Object> objList = new ArrayList<>();
List<String> stringList = new ArrayList<>();
objList.add(new String());
objList = stringList; //error

如果想赋值,需要建立容器间关系。使用通配符描述类型,字符串列表相当于类型具体化,在逻辑上等同子类

Sample
1
2
3
List<? extends Object> someKindList = new ArrayList<>();  //等同于List<?>
List<String> stringList = new ArrayList<>();
someKindList = stringList;

泛型数组


不能创建出泛型数组

Sample
1
List<String>[] listArray =  new ArrayList<String>[10]; //error

可以使用无限定类型方式,但是牺牲了写入能力

Sample
1
List<?>[] listArray = new ArrayList<?>[10];

枚举


描述enum类型的类定义为Class<? extends Enum<?>>

Enum.java
1
2
public abstract class Enum<E extends Enum<E>> {
}

定义枚举类型Color,内部会编译成

Sample
1
2
class Color extends Enum<Color> {
}

这种内部约束规定了泛型类型必须是枚举,不能用随便一个类作为泛型类型

泛型与静态


泛型类型擦除,相同类的不同泛型在底层是同一个类,因此静态变量只有一份

Sample
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
class Sample<T> {
public static int count = 0;

private Object value;

public Sample(T value) {
this.value = value;
count++;
}

public T getValue() {
return (T) this.value;
}
}

public class Test {
public static void main(String[] args) throws Exception {
Sample<Integer> s1 = new Sample<>(1);
Sample<String> s2 = new Sample<>("foo");
System.out.println(Sample.count); // 2
}
}

静态只跟类有关,而类的不同实例可能采用不同泛型,因此泛型类的静态成员不能出现泛型

Sample
1
2
3
class Sample<T> {
public static T value; // error
}

静态内类和外围无关,因此外围的泛型无法作用于内类

Sample
1
2
3
4
5
6
7
8
9
class Sample<T> {
private static class InnerStatic {
T value; // error
}

private class Inner {
T value;
}
}