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)
    @Override
    public 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;

    // 通知校验ID
    private String notifyId;

    // 编码格式
    private String charset;

    // 接口版本
    private String version;

    // 签名算法类型
    private String signType;

    // 签名串
    private String sign;

    // 应用ID
    private 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) { // 安全读取为Number
        System.out.println(num.doubleValue());
    }
}

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

void addNumber(List<? extends Number> list) {
    list.add(100); // 编译错误!无法确定实际类型是否是Integer
    list.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); // 安全写入Integer
    list.add(200);
}

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

void readFirst(List<? super Integer> list) {
    Integer num = list.get(0); // 编译错误!实际类型可能是Object
    Object 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) {} // 编译错误:擦除后签名相同

 


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值