Loading

泛型及类型擦除

泛型是什么?

泛型的英文是 generics,generic的意思是通用的。还有一种较为准确的说法就是为了参数化类型,或者说可以将类型当作参数传递给一个类或者是方法

在Java1.5之前,没有泛型的时候,我们也可以存取任何值,只要我们做到正确的类型转换即可

public class Fanxing {
    public static void main(String[] args) {
        StoreEverything se = new StoreEverything();
        se.setValue(1999);
        int r = (int) se.getValue();
        System.out.println(r);
        se.setValue("StringValue");
        String str = (String) se.getValue();
        System.out.println(str);
    }
}

class StoreEverything {
    Object value;

    public Object getValue() {
        return value;
    }

    public void setValue(Object value) {
        this.value = value;
    }
}

有了泛型之后,我们可以这样写

public class Fanxing {
    public static void main(String[] args) {
        StoreEverything<Integer> se = new StoreEverything<>();
        se.setValue(1999);
        Integer value = se.getValue();
        System.out.println(value);
    }
}

class StoreEverything<T> {
    T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}

这就是泛型,它将 value 这个属性的类型也参数化了,这就是所谓的参数化类型

泛型的定义和使用

泛型按照使用情况可以分为 3 种

  1. 泛型类

  2. 泛型方法

  3. 泛型接口

与开头的例子类似,我们可以这样定义一个泛型类:

public class Test<T> {
	T field;
}

尖括号<>中的 T 被称作是类型参数,用于指代任何类型。T只是我们约定俗成的写法,没有强制要求。但出于规范的目的,Java 还是建议我们用单个大写字母来代表类型参数。常见的如:

  1. T 代表一般的任何类
  2. E 代表 Element 的意思,或者 Exception 异常的意思
  3. K 代表 Key 的意思(常见于Map中)
  4. V 代表 Value 的意思,通常与 K 一起配合使用
  5. S 代表 Subtype 的意思

如果一个类被<T>的形式定义,那么它就被称为是泛型类

泛型类使用

Test<String> test1 = new Test<>();
Test<Integer> test2 = new Test<>();

泛型类不至接受一个类型参数,它还可以这样接受多个类型参数

泛型方法

泛型方法与泛型类稍有不同的地方是,类型参数也就是尖括号那一部分是写在返回值前面的。<T>中的 T 被称为类型参数,而方法中的 T 被称为参数化类型,它不是运行时真正的参数

public  <T> T testMethod1(T t){
	return null;
}

泛型接口

泛型接口和泛型类差不多

public interface Iterable<T> {

}

通配符 ?

除了用 <T>表示泛型外,还有<?>这种形式。? 被称为通配符

通配符的出现是为了指定泛型中的类型范围

通配符有 3 种形式:

<?>被称作无限定的通配符
<? extends T>被称作有上限的通配符
<? super T>被称作有下限的通配符

无限定通配符 <?>

无限定通配符经常与容器类配合使用,它其中的 ? 其实代表的是未知类型,所以涉及到 ? 时的操作,一定与具体类型无关

类型擦除

泛型是 Java 1.5 版本才引进的概念,在这之前是没有泛型的概念的,但显然,泛型代码能够很好地和之前版本的代码很好地兼容。

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

通俗地讲,泛型类和普通类在 java 虚拟机内是没有什么特别的地方

例如代码:

List<String> l1 = new ArrayList<String>();
List<Integer> l2 = new ArrayList<Integer>();
System.out.println(l1.getClass() == l2.getClass());

返回的结果是true

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

泛型信息被擦除了

public class Erase<T> {
    T object;

    public Erase(T object) {
        this.object = object;
    }
}

Erase 是一个泛型类,我们查看它在运行时的状态信息可以通过反射

 Erase<Integer> erase = new Erase<>(1999);
 Class aClass = erase.getClass();
 System.out.println("Erase class is: " + aClass.getName());

结果是

Erase class is: Apr26.Erase
Apr26 为包名

Class 的类型仍然是Erase并不是Erase这种形式,那我们再看看泛型类中 T 的类型在 JVM 中是什么具体类型

Field[] fs = aClass.getDeclaredFields();
for ( Field f:fs) {
	System.out.println("Field name "+f.getName()+" type:"+f.getType().getName());
}

结果还是Object类型

Field name object type:java.lang.Object

也许你会以为泛型类被类型擦除后,相应的类型就被替换成 Object 类型了,其实不然,如下面这段代码所示:

public class Erase<T extends String> {
    T object;

    public Erase(T object) {
        this.object = object;
    }

    public static void main(String[] args) {
        Erase<String> erase = new Erase<>("1999");
        Class aClass = erase.getClass();
        System.out.println("Erase class is: " + aClass.getName());

        Field[] fs = aClass.getDeclaredFields();
        for (Field f : fs) {
            System.out.println("Field name " + f.getName() + " type:" + f.getType().getName());
        }
    }
}

结果为

Erase class is: Apr26.Erase
Field name object type:java.lang.String

由此我们的结论是:泛型类被类型擦除的时候,之前泛型类中的类型参数部分如果没有指定上限,如<T>则会被转译成普通的 Object 类型,如果指定了上限如 <T extends String>则类型参数就被替换成类型上限

泛型中值得注意的地方

  1. 泛型类或者泛型方法中,不接受 8 种基本数据类型
  2. Java 不能创建具体类型的泛型数组
posted @ 2021-04-26 21:26  Xianhao  阅读(180)  评论(0)    收藏  举报