目录
一、通配符解决什么问题
二、通配符上界
三、通配符下界 编辑
四、泛型类的擦除机制
引言:
在这篇文章中,我主要介绍前一篇泛型没介绍完整的泛型通配符和泛型的擦除机制Java中泛型的详细介绍
一、通配符解决什么问题
static class Message<T> {
private T message ;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class TestDemo {
public static void main(String[] args) {
Message<String> message = new Message<>() ;
message.setMessage("比特就业课欢迎您");
fun(message);
}
public static void fun(Message<String> temp){
System.out.println(temp.getMessage());
}
}
以上程序会带来新的问题,如果现在泛型的类型设置的不是String,而是Integer.
public class TestDemo {
public static void main(String[] args) {
Message<Integer> message = new Message() ;
message.setMessage(99);
fun(message); // 出现错误,只能接收String
}
public static void fun(Message<String> temp){
System.out.println(temp.getMessage());
}
}
我们需要的解决方案:可以接收所有的泛型类型,但是又不能够让用户随意修改。这种情况就需要使用通配符"?"来 处理
示例:使用通配符
public class TestDemo {
public static void main(String[] args) {
Message<Integer> message = new Message() ;
message.setMessage(55);
fun(message);
}
// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<?> temp){
//temp.setMessage(100); 无法修改!
System.out.println(temp.getMessage());
}
}
二、通配符上界
<? extends 上界>
<? extends Number>//可以传入的实参类型是Number或者Number的子类
示例:
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Banana extends Fruit {
}
class Message<T> { // 设置泛型
private T message ;
public T getMessage() {
return message;
}
public void setMessage(T message) {
this.message = message;
}
}
public class TestDemo {
public static void main(String[] args) {
Message<Apple> message = new Message<>() ;
message.setMessage(new Apple());
fun(message);
Message<Banana> message2 = new Message<>() ;
message2.setMessage(new Banana());
fun(message2);
}
// 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
public static void fun(Message<? extends Fruit> temp){
//temp.setMessage(new Banana()); //仍然无法修改!
//temp.setMessage(new Apple()); //仍然无法修改!
System.out.println(temp.getMessage());
}
}
public static void fun(Message<? extends Fruit> temp){
//temp.setMessage(new Banana()); //仍然无法修改!
//temp.setMessage(new Apple()); //仍然无法修改!
Fruit b = temp.getMessage();
System.out.println(b);
}
通配符的上界,不能进行写入数据,只能进行读取数据。
三、通配符下界
<? super 下界>
<? super Integer>//代表 可以传入的实参的类型是Integer或者Integer的父类类型
示例:
class Food {
}
class Fruit extends Food {
}
class Apple extends Fruit {
}
class Plate<T> {
private T plate ;
public T getPlate() {
return plate;
}
public void setPlate(T plate) {
this.plate = plate;
}
}
public class TestDemo {
public static void main(String[] args) {
Plate<Fruit> plate1 = new Plate<>();
plate1.setPlate(new Fruit());
fun(plate1);
Plate<Food> plate2 = new Plate<>();
plate2.setPlate(new Food());
fun(plate2);
}
public static void fun(Plate<? super Fruit> temp){
// 此时可以修改!!添加的是Fruit 或者Fruit的子类
temp.setPlate(new Apple());//这个是Fruit的子类
temp.setPlate(new Fruit());//这个是Fruit的本身
//Fruit fruit = temp.getPlate(); 不能接收,这里无法确定是哪个父类
System.out.println(temp.getPlate());//只能直接输出
}
}
通配符的下界,不能进行读取数据,只能写入数据。
四、泛型类的擦除机制
Java 中的泛型是在编译时期进行类型检查的机制,但在编译后会被擦除。这意味着在运行时,Java 虚拟机不会知道泛型的具体类型,而是使用泛型类型的原始类型。这个过程被称为类型擦除。
具体来说,编译器在编译泛型类或方法时,会将其中的类型参数用其上限(如果没有指定上限,则为 Object来替换,并插入必要的类型转换以确保类型安全。这样做是为了保持向后兼容性,因为泛型是在 Java 5 中引入的,而在此之前的版本中并没有泛型支持。
下面是一个简单的例子来说明泛型类的擦除机制:
import java.util.*;
public class GenericExample<T> {
private T data;
public GenericExample(T data) {
this.data = data;
}
public T getData() {
return data;
}
public static void main(String[] args) {
GenericExample<String> stringExample = new GenericExample<>("Hello");
GenericExample<Integer> integerExample = new GenericExample<>(123);
// 编译后会擦除泛型类型
System.out.println(stringExample.getClass()); // class GenericExample
System.out.println(integerExample.getClass()); // class GenericExample
// 因此,在运行时无法获取泛型的具体类型
System.out.println(stringExample.getData()); // Hello
System.out.println(integerExample.getData()); // 123
// 编译器插入了类型转换以确保类型安全
// 运行时会执行类型转换
String str = stringExample.getData(); // No explicit casting needed
Integer num = integerExample.getData(); // Explicit casting needed
}
}
在上面的例子中,尽管我们在实例化 GenericExample
时指定了具体的类型参数,但在运行时,通过 getClass()
方法可以看到实际的类型是 GenericExample
,而不是 GenericExample<String>
或 GenericExample<Integer>
。这就是类型擦除的表现。在获取数据时,编译器会自动插入类型转换以确保类型安全。