当前位置: 首页 > news >正文

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本身或者他的父类

 

相关文章:

  • Stellaris 群星 [DLC 解锁] CT 表 [Steam] [Windows SteamOS macOS]
  • rvalue引用()
  • 解决Jenkis安装、配置及账号权限分配时遇到的问题
  • 电脑怎么分屏操作?
  • 【Python】 `os.getenv()` vs. `os.environ.get()`:环境变量获取方式的本质差异解析
  • 用二进制魔法解锁复杂问题:状态压缩动态规划实战揭秘
  • 算法每日一题 | 入门-顺序结构-上学迟到
  • 9.城市基础设施更新工程
  • vulkanscenegraph显示倾斜模型(6.5)-vsg::DatabasePager
  • Linux网络编程 day4
  • 【Python】使用`python-dotenv`模块管理环境变量
  • 8.5/Q1,Charls高分经典文章解读
  • 代码随想录第33天:动态规划6(完全背包基础)
  • 第二章 - 软件质量
  • 【Windows】Windows 使用bat脚本备份SVN仓库
  • CUDA 初学者资源 (更新中)
  • <C++>冒泡排序、归并排序详解 时间复杂度 与应用
  • 开源库测试
  • [逆向工程]什么是“暗桩”
  • 代码随想录第34天:动态规划7(打家劫舍问题:链式、环式、树式房屋)
  • 青岛鞋企双星名人集团家族内斗:创始人发公开信指控子孙夺权
  • 特朗普宣布对进口电影征收100%关税
  • 国铁集团:5月4日全国铁路预计发送旅客2040万人次
  • 国铁集团:5月1日全国铁路预计发送旅客2250万人次
  • 人物|德国新外长关键词:总理忠实盟友、外交防务专家、大西洋主义者
  • 五一去哪儿|外国朋友来中国,“买买买”成为跨境旅游新趋势