0%

Java泛型

Java 泛型

泛型的好处

  1. 代码更健壮(编译器警告,不会出现ClassCastExceptiion);
  2. 代码更加简洁(不用强转);
  3. 代码更灵活,复用。

泛型有三种使用方式,分别为:泛型类、泛型接口、泛型方法

泛型类
1
2
3
4
5
6
7
8
9
10
11
public class Test<T>{ 
//key这个成员变量的类型为T,T的类型由外部指定
private T key;

public get(T key) { //泛型构造方法形参key的类型也为T,T的类型由外部指定
this.key = key;
}
public T set(){ //泛型方法getKey的返回值类型为T,T的类型由外部指定
return key;
}
}
泛型接口
1
2
3
4
5
    //定义一个泛型接口
public interface Person<T> {
public T next();
}

当实现泛型接口的类,未传入泛型实参时
1
2
3
4
5
6
7

class Student<T> implements Person<T>{
@Override
public T next() {
return null;
}
}
当实现泛型接口的类,传入泛型实参时:
1
2
3
4
5
6
class Student implements Person<String>{
@Override
public T next() {
return null;
}
}

泛型方法

1
2
3
4
5
6
7
8
9
10
/**
* 泛型方法的基本介绍
* @param tClass 传入的泛型实参
* @return T 返回值为T类型
* 说明:
* 1)public 与 返回值中间<T>非常重要,可以理解为声明此方法为泛型方法。
* 2)只有声明了<T>的方法才是泛型方法,泛型类中的使用了泛型的成员方法并不是泛型方法。
* 3)<T>表明该方法将使用泛型类型T,此时才可以在方法中使用泛型类型T。
* 4)与泛型类的定义一样,此处T可以随便写为任意标识,常见的如T、E、K、V等形式的参数常用于表示泛型。
*/
1
2
3
4
public <T> T get(Class<T> tClass){
T instance = tClass.newInstance();
return instance;
}
泛型擦除

泛型信息只存在于代码编译阶段,在进入 JVM 之前,与泛型相关的信息会被擦除掉,专业术语叫做类型擦除。

1
2
3
4
5
6
7
List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();

System.out.println(l1.getClass() == l2.getClass());

打印的结果为 true 是因为 List<String>和 List<Integer>在 jvm 中的 Class 都是 List.class。

1
2
3
4
5
6
7
8
9
puclic   interface Test<T>{
public Test(T object);
}
在编译阶段会被擦除成 object

pulicl interfcae Test<T extends String>{
public Test(T object);
}
在编译阶段会被擦除成 String

我们现在可以下结论了,在泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如 则会被转译成普通的 Object 类型,如果指定了上限如 则类型参数就被替换成类型上限。

//泛型擦除在编译期间,其实常量池里面保留了泛型信息,所哟我们可以通过反射获取泛型信息。常见retrofit中获取泛型类型传入的JavaBean 对象

Java泛型原理?什么是泛型擦除机制?

Java 的泛型是JDK5引入的特性,为了向下兼容,虚拟机其实不支持泛型,所以Java实现的是一种违泛型机制,也就是说Java在编译期擦除了所有的泛型信息,这样Java就不需要产生新的类型到字节码,所有的泛型类型最终都是一种原始类型,在java运行时根本就不存在泛型信息。

Java编译器具体是如何擦除泛型的?

  1. 检查泛型类型,获取目标类型;
  2. 擦除类型变量,并替换为限定类型 ;如果泛型类型的类型没有变量设定,则用>Object作为原始类型,如果限定(T exends Xclass)则用Xclass作为原始类型;如果有多个限定(T extends XClass &XClass2)则使用第一个边界XClas作为原始类。
  3. 在必要时插入类型转换以保持类型安全;
  4. 生成桥方法以在扩展时保持多态性。

使用泛型以及泛型擦除带来的影响(副作用)

  1. 泛型类型不能使用基本类型 ;(类型ArrayList泛型擦除后就是 ArrayList ,但是Object类型不能存放基本类型)
  2. 不能使用instanceof 运算符(类型ArrayList泛型擦除后就是 ArrayList ,泛型信息String 不存在了,所以没法使用instanceof)
  3. 泛型在静态方法静态类中的问题(因为泛型类中的泛型参数的实例化是定义在泛型类对象(比如ArrayList的时候指定的,二静态成员是不需要使用对象来调用的,所以对象都没有创建,如何确定在这个泛型参数是什么。))
  4. 泛型类型中的方法冲突(因为擦除后两个方法变成一样了)
  5. 没法创建泛型实例
  6. 没有泛型数组(因为数字组是协变的) T [] arry =new T[3] //不允许
  7. 通配符类型

    ? extends X 表示类型的上界,类型参数是X的子类
    ? super X 表示类型的下界,类型参数是X的超类(父类或者父类的父类)

    ? extends X

    set方法是不允许被调用的,会出现编译错误
    get方法则没问题,会返回一个X类型的值。
    道理很简单,? extends X 表示类型的上界,类型参数是X的子类,那么可以肯定的说,get方法返回的一定是个X(不管是X或者X的子类)编译器是可以确定知道的。但是set方法只知道传入的是个X,至于具体是X的那个子类,不知道。
    总结:主要用于安全地访问数据,可以访问X及其子类型,并且不能写入非null的数据。

    ? super X

    表示传递给方法的参数,必须是X的超类(父类或者父类的父类 包括X本身)
    但是对泛型类GenericType来说,如果其中提供了get和set类型参数变量的方法的话,set方法可以被调用的,且能传入的参数只能是X或者X的子类;

    get方法只会返回一个Object类型的值。

    ? super X 表示类型的下界,类型参数是X的超类(包括X本身),那么可以肯定的说,get方法返回的一定是个X的超类,那么到底是哪个超类?不知道,但是可以肯定的说,Object一定是它的超类,所以get方法返回Object。编译器是可以确定知道的。对于set方法来说,编译器不知道它需要的确切类型,但是X和X的子类可以安全的转型为X。
    总结:主要用于安全地写入数据,可以写入X及其子类型。

    无限定的通配符 ?

    表示对类型没有什么限制,可以把?看成所有类型的父类,如Pair< ?>;
    比如:
    ArrayList al=new ArrayList(); 指定集合元素只能是T类型
    ArrayList al=new ArrayList();集合元素可以是任意类型,这种没有意义,一般是方法中,只是为了说明用法。
    在使用上:
    ? getFirst() : 返回值只能赋给 Object,;
    void setFirst(?) : setFirst 方法不能被调用, 甚至不能用 Object 调用;