先和大家来看一个问题:ArrayList<String>
和ArrayList<Integer>
是否为不同的类型?
不同的类型的具有不同的行为,如我们可以将一个 String 对象放入 ArrayList<String>
,但却不能将 Integer 对象放入,故我们会认为上述两个 ArrayList 是不同的类型。
但实际真的如此嘛?通过代码告诉我们答案。
public class ErasedTypeTest {
public static void main(String[] args) {
Class c1 = new ArrayList<String>().getClass();
Class c2 = new ArrayList<Integer>().getClass();
System.out.println(c1 == c2);
}
}
true
我们看到即使指定了不同的类型参数,使用的仍然是相同的类型。因为在泛型代码内部,无法获得任何有关泛型参数类型的信息。
ArrayList<String>
和ArrayList<Integer>
都会被擦除成 ArrayList
。
JAVA 语言为什么要这么设计呢?
因为泛型并不是在 JAVA 语言出现时就存在,为了兼容 JAVA SE5 之前编写的非泛型类库,泛型类型只能被当做第二类型处理。泛型类型只在静态类型检查期间出现,在此之后,程序中的所有泛型类型都将被擦除,替换为非泛型上界。如 ArrayList<T> 会被擦除为 ArrayList,非泛型上界为 Object 。这就解释了 ArrayList<String>
和ArrayList<Integer>
为何会被认定为相同的类型。
看到这里可能有同学会有疑问,既然泛型擦除了具体类型,但我们在往 ArrayList 里面存元素时只能存放指定类型,且取元素的时候为什么能返回指定类型呢?这是因为编译器会对传递进来的值进行额外的编译期检查,并对传递出去的值进行转型,但在泛型代码内部类型参数一直都会被当做 Object 进行处理。
泛型擦除使得任何在运行时需要知道确切类型信息的操作无法正常运行,这会给我们带来一些不便,例如下面这个例子。
我们想在 PetStore 类中调用传入对象的 getType() 方法,但是因为泛型擦除使得我们失去了具体类型,被当成了 Object 类,故哪怕传入的对象具备 getType() 方法,我们也无法直接调用。
public abstract class Pet {
abstract void f();
}
public class Dog extends Pet {
@Override
void f() {
System.out.println("This is a dog");
}
}
public class PetStore<T> {
private T pet;
public PetStore(T pet) {
this.pet = pet;
}
public void getType() {
//编译错误
//pet.getType();
}
}
想要解决上述问题,只需要定义泛型擦除的边界即可。<T extends Pet> 表示使用的类型参数必须继承自 Pet 类,此时的泛型参数将会擦除到泛型上界 Pet,不再是 Object 类,故可以安全的调用 getType() 方法。
public class PetStore<T extends Pet> {
private T pet;
public PetStore(T pet) {
this.pet = pet;
}
public void getType() {
pet.getType();
}
public static void main(String[] args) {
PetStore<Dog> dogPetStore = new PetStore<>(new Dog());
dogPetStore.getType();
}
}
This is a dog
小结
1.泛型是类的第二具体类型。
2.泛型的默认上界为 Object 类 。
3.可以指定泛型的边界来控制泛型擦除的范围。
4.不要盲目使用泛型,在 PetStore<T extends Pet> 的例子中,若不需要返回 T ,将 T 替换成基类 Pet 也可以实现该效果。使用泛型会增加代码的复杂度,当需要使用泛型返回确切类型时,才需要考虑使用泛型。
本次分享至此结束,希望本文对你有所帮助,若能点亮下方的点赞按钮,在下感激不尽,谢谢您的【精神支持】。
若有任何疑问,也欢迎与我交流,若存在不足之处,也欢迎各位指正!