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

java基础-泛型

文章目录

目录

文章目录

前言

一、泛型的作用

1.类型安全

2.通用性

这里再举个例子

二、泛型的实现

1.泛型类

2.泛型接口

3.泛型方法

4.T符号的起源(额外)

三、泛型擦除

四、泛型通配符

1.上界通配符( )

为什么用于读取?

2. 下界通配符( )

为什么用于写入?

3. 无界通配符( )

​​语法特点​​

?和T

总结

举例

五、泛型限制与注意事项


前言

Java 泛型(Generics)是 JDK 5 引入的核心特性,通过参数化类型实现代码的通用性类型安全


一、泛型的作用

1.类型安全


在泛型出现前,使用Object实现通用容器会导致运行时类型转换错误

List list = new ArrayList();
list.add("Hello");
Integer num = (Integer) list.get(0); // 运行时抛出ClassCastException

使用泛型后,能在编译期发现类型错误:

List<String> list = new ArrayList<>();
list.add(100); // 编译错误:无法添加整数到String列表

2.通用性

常见的例子(集合)都是泛型接口,为的就是确保通用性

试想,如果ArrayList<Integer>是这样的,那么路就走窄了,其他String,Double怎么办,再写一个ArrayList<String>接口出来?明显不合理,而且自己定义的User,Car这个对象,集合怎么存储呢,只有依靠泛型

这里再举个例子

试想一个场景,有多个接口接收到了N个参数,其中M个是通用参数,V个是特有参数,那么这时候为了通用性,我们定义了一个实体类来存储着N个参数叫BaseNotify类,那么问题来了,BaseNotify这个类中我知道有M个是通用参数,这很好写,那么其他的V个参数怎么存放呢

如果有10个接口,每个接口有V个参数是特有的,不可能就要在一个类中写10*V个字段吧,这不科学,那么泛型就很好解决这个问题

看如下示例,在通用类中再定义一个泛型字段,其中OrderNotify是特有的字段,getBizContentObject方法会将bizContentObj这个JSON数据转为T(OrderNotify)类型,这样就实现通用性了

    @Transactional(rollbackFor =Exception.class)@Overridepublic void signalAgentPayCallback(BaseNotify<OrderNotify> notify) {OrderNotify orderNotify;try {orderNotify =  notify.getBizContentObject(OrderNotify.class);} catch (JsonProcessingException e) {throw new RuntimeException(e);}}
@Data
public class BaseNotify<T> {// 通知发送时间@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")private Date notifyTime;// 通知类型private String notifyType;// 通知校验IDprivate String notifyId;// 编码格式private String charset;// 接口版本private String version;// 签名算法类型private String signType;// 签名串private String sign;// 应用IDprivate String appId;// 业务参数集合 - 存储原始JSON字符串private String bizContent;// 已解析的业务参数对象private transient T bizContentObj;/*** 解析bizContent字符串到指定类型的对象* @param clazz 目标类型* @return 解析后的对象* @throws JsonProcessingException 如果解析失败*/public T getBizContentObject(Class<T> clazz) throws JsonProcessingException {if (bizContentObj == null && bizContent != null) {ObjectMapper mapper = new ObjectMapper();// 配置 ObjectMapper 以忽略未知字段mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);bizContentObj = mapper.readValue(bizContent, clazz);}return bizContentObj;}
}
@Data
public class OrderNotify {/*** 必填: 是*/private String orderNo;/*** 商户订单号 (支付订单)* 必填: 是*/private String bizOrderNo;/*** 必填: 否* 说明: 退款订单读字段才返回*/private String oriOrderNo;/*** 原商户订单号* 必填: 否* 说明: 退款订单读字段才返回*/private String oriBizOrderNo;
}

二、泛型的实现

1.泛型类

public class Box<T> {private T content;public void setContent(T content) {this.content = content;}public T getContent() {return content;}
}// 使用
Box<String> stringBox = new Box<>();
stringBox.setContent("Java");
String value = stringBox.getContent(); // 类型安全

2.泛型接口

public interface Comparator<T> {int compare(T o1, T o2);
}// 实现
public class StringComparator implements Comparator<String> {public int compare(String s1, String s2) {return s1.length() - s2.length();}
}

3.泛型方法

public <T> T getFirstElement(List<T> list) {return list.isEmpty() ? null : list.get(0);
}// 使用
List<Integer> numbers = Arrays.asList(1, 2, 3);
Integer first = getFirstElement(numbers); // 自动类型推断

4.T符号的起源(额外)

上面不管是泛型类还是泛型方法看见其中我们用的参数符号都是T,为什么呢

说白了就是约定俗成,既有语义明确性:T(Type)类型,又不是其他关键字

 其他字符的使用场景

符号​​常见用途​​示例​
T通用类型(Type)List<T>Pair<T>
E集合元素(Element)List<E>Set<E>
K/V键值对(Key-Value)Map<K, V>
R返回值(Result)<R> R execute()
?通配符(Wildcard)List<?><? extends T>

三、泛型擦除

Java泛型是编译期的特性运行时会被擦除为原生类型(Raw Type):

  • List<String> → List

  • T → Object(或边界类型)

  • 插入强制类型转换:(String) list.get(0)

限制

  • 不能创建泛型数组:new T[10] ❌

  • 不能实例化类型参数:new T() ❌

  • 静态成员不能使用泛型类型:

class Box<T> {static T defaultValue; // 编译错误
}

四、泛型通配符

PECS原则(Producer Extends, Consumer Super)

1.上界通配符(<? extends T>

用于读取数据(Producer Extends),白话来说就是该类型最高向上转型到T类型(是T类型或者其子类):

public void printNumbers(List<? extends Number> list) {for (Number num : list) {System.out.println(num);}
}
// 可接收List<Integer>, List<Double>等

该泛型类型可以是T或其子类类型(例如List<? extends Number>可以接收List<Integer>List<Double>等)。 

为什么用于读取?

读取安全
当使用<? extends T>时,编译器知道集合中的元素至少是T类型(或其子类),因此可以安全地将元素当作T类型读取

void printNumbers(List<? extends Number> list) {for (Number num : list) { // 安全读取为NumberSystem.out.println(num.doubleValue());}
}

 写入不安全
由于实际类型可能是T的任意子类(例如IntegerDouble),无法确定具体类型,因此不能向集合中添加元素(除了null)。

void addNumber(List<? extends Number> list) {list.add(100); // 编译错误!无法确定实际类型是否是Integerlist.add(3.14); // 编译错误!无法确定实际类型是否是Double
}

2. 下界通配符(<? super T>

用于写入数据(Consumer Super):

public void addNumbers(List<? super Integer> list) {list.add(100);list.add(200);
}
// 可接收List<Integer>, List<Number>, List<Object>

泛型类型可以是T或其父类类型(例如List<? super Integer>可以接收List<Integer>List<Number>List<Object>)。

为什么用于写入?

写入安全
当使用<? super T>时,编译器知道集合的元素类型至少是T的父类,因此可以安全地写入T类型的元素(因为T是父类的子类型)。

void fillList(List<? super Integer> list) {list.add(100); // 安全写入Integerlist.add(200);
}

 读取不安全
由于实际类型可能是T的任意父类(例如NumberObject),读取时只能当作Object处理,无法确定具体类型。

void readFirst(List<? super Integer> list) {Integer num = list.get(0); // 编译错误!实际类型可能是ObjectObject obj = list.get(0); // 安全,但需要手动类型检查
}

3. 无界通配符(<?>

无界通配符 ? 表示“任意类型”,但​​不关心具体类型​​,仅允许读取为Object

public void processList(List<?> list) {for (Object obj : list) {System.out.println(obj);}
}

​语法特点​

  • ​仅用于引用声明​​:不能用于泛型类、接口或方法的类型参数定义(如 public class Box<?> 是非法的)。
  • ​只能出现在方法参数或局部变量中​​:例如 void printList(List<?> list)

 可以说无界通配符?是上届、下届通配符的基础

?和T

可以互相替换吗?答:不行

?:处理完全未知类型,无需关心具体类型(如通用工具方法)

T:当需要​操作具体类型​​(如调用其方法、约束继承关系)时,使用 T 定义泛型类/方法。

无界通配符适用于​既不生产也不消费数据​的场景(如仅调用 size()clear()

总结

  • Producer(生产者)
    如果泛型对象负责生产数据(如返回元素),使用<? extends T>,因为需要安全读取。

  • Consumer(消费者)
    如果泛型对象负责消费数据(如接收元素),使用<? super T>,因为需要安全写入。

举例

下面是Collections.copy方法,其中src(P)用于生产读取数据到dest(C)消费者中

    public static <T> void copy(List<? super T> dest, List<? extends T> src) {int srcSize = src.size();if (srcSize > dest.size())throw new IndexOutOfBoundsException("Source does not fit in dest");if (srcSize < COPY_THRESHOLD ||(src instanceof RandomAccess && dest instanceof RandomAccess)) {for (int i=0; i<srcSize; i++)dest.set(i, src.get(i));} else {ListIterator<? super T> di=dest.listIterator();ListIterator<? extends T> si=src.listIterator();for (int i=0; i<srcSize; i++) {di.next();di.set(si.next());}}}

所以说我们用copy方法的时候

Collections.copy(A,B);就代表将Bcopy到A

​特性​​无界通配符 ?​上界通配符 ? extends T​下界通配符 ? super T
​类型范围​任意类型T 或其子类T 或其父类
​读操作​元素视为 Object元素视为 T元素视为 Object
​写操作​仅允许 null仅允许 null允许 T 或其子类
​典型场景​通用工具方法、只读遍历只读且需类型安全(如计算总和)写入数据(如添加元素)

五、泛型限制与注意事项

  1. 不能使用基本类型
    List<int> ❌ → 使用包装类List<Integer> ✔️

  2. 不能重载相同原始类型的方法

void print(List<String> list) {}
void print(List<Integer> list) {} // 编译错误:擦除后签名相同

 


http://www.xdnf.cn/news/6025.html

相关文章:

  • tails os系统详解
  • 实物工厂零件画图案例(上)
  • 进程与线程:09 进程同步与信号量
  • Linux的域名解析服务器
  • OAuth安全架构深度剖析:协议机制与攻防实践
  • 【Nacos】env NACOS_AUTH_IDENTITY_KEY must be set.
  • SparkSQL 连接 MySQL 并添加新数据:实战指南
  • uniapp+vue3中自动导入ref等依赖
  • 通义灵码2.5版本全新体验
  • CSP-J普及组第一轮真题单选题专项训练(二)
  • NumPy 2.x 完全指南【九】常量
  • 虹科应用 | 探索PCAN卡与医疗机器人的革命性结合
  • 软件测试(2)软件测试分类及流程
  • 【自学30天掌握AI开发】 - 课程简介
  • Spring事务失效的全面剖析
  • C++:重载>>和<<(输入和输出运算符)
  • [FA1C4] 博客链接
  • OpenTiny icons——超轻量的CSS图标库,引领图标库新风向
  • Weblogic 反序列化远程命令执行漏洞 CVE-2019-2725 详解
  • Eaton XV-102-BE-35TQRC-10是伊顿(Eaton)公司推出的一款高性能触摸屏人机界面(HMI)
  • Python | Dashboard制作
  • 【报错解决】服务器重启后vscode远程连接失败
  • MySQL推荐书单:从入门到精通
  • 3545. 不同字符数量最多为 K 时的最少删除数
  • 【登录认证】JWT令牌
  • RDD-自定义分区器案例
  • 3541. 找到频率最高的元音和辅音
  • mysql8创建用户并赋权
  • Cascadeur2025如何无限制导出FBX文件
  • 优艾智合机器人助力半导体智造,领跑国产化替代浪潮