泛型记录了解

Java泛型详解

一、什么是泛型(Generics)
  它可以让类、接口或方法在定义时不指定具体类型,而在使用时由调用者确定,从而实现类型安全和模板复用。

  通俗比喻:动物园:
    动物分类:猫科动物都有锋利的牙齿和利爪(共有属性),会利用利爪捕猎(共有行为);犬科动物也有自己的共性。
    差异化:不同种类的猫科动物有不同的花纹、叫声和习性,不同犬科动物也各自不同。
    如果建动物园时只是把所有动物随便放在一个大笼子里(用父类或子类管理),游客在参观时就会混乱,不知道每只动物具体是什么。

  泛型的作用:
    泛型就像给每类动物建造一个“指定的区域”,并贴上清晰的标签(类型参数)。
    在这个模板区域里,共有属性和行为被统一管理,而每种动物的特有属性和行为也能被正确识别。
  这样既保证了统一管理的逻辑(模板),又保证了类型安全(不同科类不会混在一起)。
  简单来说,泛型就是在编程中实现了这种“分类模板”,让相似逻辑可复用,同时不会丢失具体类型信息。
  当然泛型并不是取代继承的,而是为了互补。

二、泛型的分类与使用
1.泛型类
  定义模板类,字段或者方法的类型不固定,由调用者指定。

class Box<T> {
    private T content;
    public void set(T item) { content = item; }
    public T get() { return content; }
}

// 使用 TV(电视)是自定义的类,当然也可以使用 Integer Boolean 等
// 在java中而是因为java泛型 只接受引用类型(对象),所以需要使用包装类。
Box<TV> tvBox = new Box<>();
tvBox.set(new TV());
TV tv = tvBox.get();  // 类型安全,不需要强转
// Fridge(冰箱)是自定义的类
Box<Fridge> fridgeBox = new Box<>();
fridgeBox.set(new Fridge());
Fridge f = fridgeBox.get();

2.泛型方法
  方法级别模板,方法内部的类型由调用者指定

public static <T> T createInstance(Class<T> clazz) throws Exception {
    return clazz.getDeclaredConstructor().newInstance();
}

ITV tv = createInstance(TV.class); // 编译期就知道类型是 TV
public static <T> T createInstance(String className, Class<T> clazz) throws Exception {
	return (T) Class.forName(className).getDeclaredConstructor().newInstance();
}

// 使用
ITV tv = createInstance("com.sh.service.impl.TVImpl",ITV.class)

第二种写法是不推荐的,因为通过Class.forName获取类时,编译器无法在编译时验证类型,导致需要在运行时进行强制转换。这会失去编译时类型安全检查,如果类路径错误或类型不匹配,会在运行时抛出ClassCastException。

3.泛型接口
  接口中方法的参数或返回值类型由使用者指定

interface IRepository<T> {
    void save(T entity);
    T findById(String id);
}

IRepository<User> userRepo;
IRepository<Product> productRepo;

三、泛型边界
  有时候需要泛型只允许特定类型或者子类,就需要泛型边界(约束)

  泛型上界:可以接受Device 或其子类,上界指的是这个T最大的范围,可以操作读取,但是写入不安全,因为不知道具体是哪个类型,只能当他是Device

public <T extends Device> void handleDevice(List<T> device) {
    device.get(0).powerOn();
    //device.add(new device());// 编译错误。
}

  泛型下界:可以接受 Device 或其父类,下界指的是这个T最小的范围,可以写入,但是不能读取,因为不知道具体的值类型,只能当 Object 处理

public void addDevice(List<? super Device> devices) {
    devices.add(new TV());     // 安全
    //device.get(0).powerOn(); // 编译错误
}

  多重边界:多重边界是对泛型类型参数 同时施加多个约束,限制它必须满足 多个类型条件。
    最多只能继承一个类(必须放在第一个位置)
    可以实现多个接口(用 & 连接)
    调用方法时可以使用类和接口的方法

public <T extends ClassA & InterfaceB & InterfaceC> void method(T t) {
	//T必须满足必须是 classA或者classA子类,但是同时又得是interfaceB和interfaceC的接口。
	// 三个条件都满足才可以。
}

四、<?>

<?> = 盒子里装 某种具体但未指定的类型 → 可以看,但不能随便放东西。
<? extends T> = 盒子里装 T 或子类 → 可以取出来当 T,但不能放。
<? super T> = 盒子里装 T 或父类 → 可以放 T,但取出来只能当 Object。

五、协变和逆变
在泛型中,类型参数 在继承关系下的行为叫做变性(Variance)。
  协变(Covariant,协变):允许将 子类型泛型对象赋值给父类型泛型变量。
  逆变(Contravariant,逆变):允许将 父类型泛型对象赋值给子类型泛型变量。
  不变(Invariant):泛型类型不能自动转换,即使泛型参数有继承关系。

协变(Covariance)
  含义:类型参数是只读的(Producer),只能读取,不能写入
  Java 表示法:? extends T

List<Interger> ints = new ArraryList<>();
List<Object> nums = ints;// 不对,虽然 interger是Object的子类,但是泛型不支持继承兼容
// 通过一下方式可以实现
List<? extends Number> nums = new ArrayList<Integer>();
Number n = nums.get(0); // 读取安全
// nums.add(10);         // 写入不安全,因为具体子类可能不同

逆变(Contravariance)
  含义:类型参数是只写的(Consumer),可以写入,但读取不确定类型
  Java 表示法:? super T

List<? super Integer> list = new ArrayList<Number>();
list.add(10); // 写入安全
Object o = list.get(0); // 读取只能当 Object
类型比喻可读可写
协变盒子装的是某类动物的子类 → 可以观察(读取)✅可以读取为父类❌写入不安全
逆变盒子可以装某类动物的父类 → 可以放入具体子类❌ 读取只能当 Object✅ 写入安全

六、类型擦拭

泛型擦拭是 Java 编译器在编译阶段做的一件事:
 把泛型类型信息从字节码中移除(擦掉具体类型参数)。
 编译器会在必要的地方插入 类型转换(cast) 和 边界检查。
 目的是 保证向下兼容老版本的 Java(因为泛型是在 JDK 1.5 引入的)。

public static <T> T createInstance(Class<T> clazz) throws Exception {
    return clazz.getDeclaredConstructor().newInstance();
}

ITV tv = createInstance(TV.class);

编译后伪代码

public static Object createInstance(Class clazz) throws Exception {
    return clazz.getDeclaredConstructor().newInstance();
}
ITV tv = (ITV) createInstance((Class) TV.class);

T 被擦掉 → 编译器插入强制类型转换
如果类型不匹配,会在运行时抛 ClassCastException

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值