Java——泛型

引出泛型

先来看下面一串代码

class MyArrayList {

    public int usedSize;
    public int[] arr;

    public MyArrayList() {
        this.arr = new int[10];
    }

    public void add(int val) {
        arr[usedSize] = val;
        usedSize++;
    }
}

public class Test {

    public static void main(String[] args) {
        MyArrayList myArrayList = new MyArrayList();
        myArrayList.add(1);
        myArrayList.add(2);
    }
}

可以看到顺序表可以添加整形元素,那么如果要添加String类型的呢

可以看到会报错,那如果想要放字符串类型的元素是不是要再重新写一个MyArrayList类,那是不是也太麻烦了

这里就要引出java中提供的泛型语法

什么是泛型

一般的类和方法,只能使用具体的类型: 要么是基本类型,要么是自定义的类。如果要编写可以应用于多种类型的代码,这种刻板的限制对代码的束缚就会很大。简单来说就是,泛型:就是适用于许多许多类型。从代码上讲,就是对类型实现了参数化。

语法:

class 泛型类名称<类型形参列表>

泛型类<类型实参> 变量名; // 定义一个泛型类引用
new 泛型类<类型实参>(构造方法实参); // 实例化一个泛型类对象
 

所以上述代码可以改为

class MyArrayList<T> {

    public int usedSize;
    public T[] arr;

    public MyArrayList() {
        this.arr = (T[])new Object[10];
    }

    public void add(T val) {
        arr[usedSize] = val;
        usedSize++;
    }
}

public class Test {

    public static void main(String[] args) {
        //存放String类型
        MyArrayList<String> myArrayList1 = new MyArrayList<>();
        myArrayList1.add("abc");
        myArrayList1.add("xyz");
        
        //存放int类型
        MyArrayList<Integer> myArrayList2 = new MyArrayList<>();
        myArrayList2.add(1);
        myArrayList2.add(2);
    }
}

这样就实现了通用

这里还有一个疑问?

这个数组为什么实例化的时候要new 一个object类,还要强制类型转换呢?

这里就要说到泛型的擦除机制了 

什么时擦除机制

泛型逻辑语法比较难,具体的泛型擦除机制可以看这篇文章:https://zhuanlan.zhihu.com/p/51452375

在编译的过程当中,将所有的T替换为Object这种机制,我们称为:擦除机制。

所以在编译期间T就会被擦除为object,明明时new的object[] ,T也会被擦除为object,那为啥还要强制类型转换?

  • 编译器的视角

    • T 是一个“未知但具体的类型”(比如 StringInteger 等)。

    • new Object[10] 创建的是 Object[],而 T[] 理论上应该是 String[]Integer[] 等。

    • 直接赋值 T[] arr = new Object[10] 会导致类型不匹配(因为 Object[] 不是 T[] 的子类型),所以需要强制转换。

  • 运行时的实际行为

    • 由于类型擦除,T[] 在运行时就是 Object[],所以强制转换 (T[]) 实际上什么都不做。

    • 但编译器会插入 隐式的类型检查,确保后续对 arr 的操作符合 T 的约束(比如不能存入非 T 类型的值)。

 为什么不能直接new T[] ?

  • 数组在运行时需要知道具体的类型(比如 String[] 和 Integer[] 是不同的)。

  • 但泛型 T 在运行时被擦除为 Object,无法确定应该创建 String[] 还是 Integer[]

  • 如果允许 new T[],可能会导致错误的数组存储(比如把 Integer 存入 String[])。

所以 Java 强制要求开发者使用 Object[] + 强制转换的方式,并自行保证类型安全。

 上述泛型的中被擦除后一定时object吗?

不一定的,擦除后变成的类型时T的上界,那什么是泛型的上界?

语法定义:

class 泛型类名称<类型形参 extends 类型上边界> 

代码示例:

实例化的时候只能传入类型上边界或者其子类

class Person {
    
}
class Student extends Person {
    
}
class AAA {
    
}
class MyArrayList<T extends Person> {
    
}

public class Test {

    public static void main(String[] args) {
        MyArrayList<Student> myArrayList1 = new MyArrayList<>();
        MyArrayList<Person> myArrayList2 = new MyArrayList<>();
                
      
    }
}

如果传入AAA呢,AAA没由继承Person,可见会报错

 接下来我们讲通配符

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 fun(/*这里的类型接收*/Message<String> temp) {
        System.out.println(temp.getMessage());
    }

    public static void main(String[] args) {
        Message<String> message = new Message<>();
        message.setMessage("i love china");
        fun(message);
    }
}

看到类型接收的地方,如果换成传入的int类型,这里就会报错

此时也要把 接受类型改为Integer

同样也不具有一般性,这里就要用到通配符了,通配符就是把类型换成 ?(问号)

通配符更改后

public class TestDemo {

    public static void fun(/*这里的类型接收*/Message<?> temp) {
        System.out.println(temp.getMessage());
    }

    public static void main(String[] args) {
        Message<String> message1 = new Message<>();
        Message<Integer> message2 = new Message<>();

        message1.setMessage("i love china");
        message2.setMessage(99);
        fun(message1);
        fun(message2);
    }
}

这样就具有通用性了

通配符不仅由上界,同时还具有下界

? extends 类:设置通配符上限
? super 类:设置通配符下限
 

class Food {

}
class Fruit extends Food {

}
class Apple extends Fruit {

}
class Banana extends Fruit {

}
class Message2<T> { // 设置泛型
    private T message ;

    public T getMessage() {
        return message;
    }

    public void setMessage(T message) {
        this.message = message;
    }

}

public class TestDemo2 {
    // 此时使用通配符"?"描述的是它可以接收任意类型,但是由于不确定类型,所以无法修改
    public static void fun(/*传入类型*/Message2<? extends Fruit> temp){
        //temp.setMessage(new Banana()); //仍然无法修改!
        //temp.setMessage(new Apple()); //仍然无法修改!
        System.out.println(temp.getMessage());
    }

    public static void main(String[] args) {
        Message2<Apple> message = new Message2<>() ;
        message.setMessage(new Apple());
        fun(message);

        Message2<Banana> message2 = new Message2<>() ;
        message2.setMessage(new Banana());
        fun(message2);

    }

}

此时那个传入类型必须是fruit或者其子类,同时接受的值也不能修改

通配符的下界

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 TestDemo3 {
    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());//只能直接输出
    }

    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);
    }

}

使用 <? super T> 表示,它表示 “某个未知类型,但必须是 T 或其父类”,上述函数的接受值必须是fruit本身或者他的父类

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值