泛型知识理解

最近用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的类型的元素.

相关推荐
Ro Jace20 分钟前
计算机专业基础教材
java·开发语言
mango_mangojuice39 分钟前
Linux学习笔记(make/Makefile)1.23
java·linux·前端·笔记·学习
程序员侠客行43 分钟前
Mybatis连接池实现及池化模式
java·后端·架构·mybatis
时艰.1 小时前
Java 并发编程 — 并发容器 + CPU 缓存 + Disruptor
java·开发语言·缓存
丶小鱼丶1 小时前
并发编程之【优雅地结束线程的执行】
java
市场部需要一个软件开发岗位1 小时前
JAVA开发常见安全问题:Cookie 中明文存储用户名、密码
android·java·安全
忆~遂愿1 小时前
GE 引擎进阶:依赖图的原子性管理与异构算子协作调度
java·开发语言·人工智能
MZ_ZXD0011 小时前
springboot旅游信息管理系统-计算机毕业设计源码21675
java·c++·vue.js·spring boot·python·django·php
PP东1 小时前
Flowable学习(二)——Flowable概念学习
java·后端·学习·flowable
ManThink Technology1 小时前
如何使用EBHelper 简化EdgeBus的代码编写?
java·前端·网络