泛型知识理解

最近用Java刷算法题的过程中, 很多地方用到了泛型的知识. 快忘完了正好借此机会重新熟悉一下, 在脑子里面建立对泛型的理解.

一、泛型的编译期需要知道的地方

1.1 泛型类型约束机制

泛型的本质是编译器对类型的约束,通过在声明时指定具体类型,编译器会强制检查所有操作是否符合该类型约束。

示例代码

java 复制代码
// 声明泛型类型为Integer
List<Integer> list = new ArrayList<>();

// 编译器会检查add方法的参数类型
list.add(100);          // 合法:参数为Integer类型
list.add("hello");      // 编译错误:参数必须为Integer类型

// 编译器自动插入类型转换代码
Integer value = list.get(0);  // 等价于 (Integer)list.get(0)

1.2 类型擦除后的底层实现

带泛型的对象在实际创建时所有泛型参数都会被替换为Object或上界类型, 如下所示:

java 复制代码
// 我们编写完的泛型类长这样
public class Box<T> {
    private T value;
    public T get() { return value; }
    public void set(T value) { this.value = value; }
}

// 实际创建对象时, 对象在内存中实际长这样
public class Box {
    private Object value;
    public Object get() { return value; }
    public void set(Object value) { this.value = value; }
}

1.3 菱形语法的类型推断

Java 7引入的菱形语法(<>)允许省略右侧泛型声明,编译器会根据左侧类型自动推断。

示例代码

java 复制代码
// 完整写法(Java 6及以前)
List<String> list1 = new ArrayList<String>();

// 简化写法(Java 7+)
List<String> list2 = new ArrayList<>();  // 自动推断为String类型

// 复杂类型推断示例
Map<Integer, List<String>> map = new HashMap<>();
map.put(1, List.of("a", "b"));

二、JVM层面对理解泛型

2.1 引用类型内存一致性

所有引用变量在内存中占用相同大小的存储空间(64位JVM通常为8字节),因此JVM在内存中创建的对象虽然都一样, 但是给泛型赋上不同类型后还是能正常存储就是这个原因.

内存分配示意图

java 复制代码
// 泛型类定义
class Pair<T, U> {
    private T first;   // 引用类型,占8字节
    private U second;  // 引用类型,占8字节
}

// 实例化不同类型的Pair
Pair<String, Integer> p1 = new Pair<>();
Pair<Double, List> p2 = new Pair<>();

// 内存布局(简化)
// p1: [first引用(8B) | second引用(8B)]
// p2: [first引用(8B) | second引用(8B)]

2.2 这也解释了为什么泛型T不能是基本类型

泛型参数不能是基本类型,因为基本类型的存储大小不一致(如int占4字节,long占8字节),无法用统一的内存布局处理, 如下假如可以放入int类型的值, JVM在内存中创建的是8字节的数组, 那放进去就会出问题.

错误示例

java 复制代码
List<int> errorList = new ArrayList<>();  // 编译错误:不能使用基本类型

// 必须使用包装类
List<Integer> correctList = new ArrayList<>();

2.3 自动装箱与拆箱

使用包装类时,编译器会自动处理基本类型与包装类型之间的转换。

示例代码

java 复制代码
List<Integer> list = new ArrayList<>();
list.add(10);  // 自动装箱:int → Integer
int value = list.get(0);  // 自动拆箱:Integer → int

// 等价的手动写法
list.add(Integer.valueOf(10));
int value = list.get(0).intValue();

三、总结

等号右边创建的均是一个T->object的对象, 我们用的时候只要关注右边是什么对象就行, 不用考虑那么多;

等号左边声明的是一个T->特定类型的引用变量, 编译器在编译时起到了保安的作用, 会检查你输入的元素是否是这个特定类型.

所以见到泛型和用泛型的时候, 例如:

Java 复制代码
List<Integer> list = new ArrayList<>();

我们的理解应该是, 左边是一个List类型的引用变量, 右边是ArrayList类型的对象, 大类的引用变量接小类的对象. 此外现在还加上了<Integer> , 代表这个List引用只能加入Integer的类型的元素.

相关推荐
NightDW10 分钟前
连续周更任务模块的设计与实现
java·后端·mysql
华仔啊11 分钟前
什么情况下用线程池,怎么用?看完就会
java·后端
灵魂猎手14 分钟前
8. Mybatis插件体系
java·后端·源码
SimonKing14 分钟前
布隆过滤器:用微小的空间代价换取高效的“可能存在”判定
java·后端·程序员
阿冲Runner15 分钟前
Lombok的@Builder与Mybatis-Plus配合使用踩坑
java·后端·mybatis
菜鸟的迷茫18 分钟前
Java 锁机制对比:Synchronized、ReentrantLock、StampedLock
java·后端
花花无缺20 分钟前
java的异常-Exception、Error
java·后端
架构师沉默21 分钟前
架构师的秘密武器:Java SPI 插件机制解密
java·后端·架构
xiaohezi22 分钟前
搞懂 ThreadLocal,其实就三件事:它是谁?它在哪?用完它咋办?
java
whitepure24 分钟前
万字详解Java多线程(一)
java·后端