泛型知识理解

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

相关推荐
2401_833269306 小时前
Java网络编程入门
java·开发语言
金銀銅鐵6 小时前
[Java] 如何将 Lambda 表达式对应的类保存到 class 文件中?
java·后端
それども7 小时前
Gradle 构建疑难杂症 Could not find netty-transport-native-epoll-linux-aarch_64.ja
java·服务器·gradle·maven
正儿八经的少年7 小时前
application.yml 系列配置文件作用与区别
java·配置文件
鱼很腾apoc7 小时前
【学习篇】第20期 超详解 C++ 多态:从语法规则到底层原理
java·c语言·开发语言·c++·学习·算法·青少年编程
cheems95278 小时前
[Spring MVC] 统一功能与拦截器实践总结
java·spring·mvc
Full Stack Developme9 小时前
Spring Boot 事务管理完整教程
java·数据库·spring boot
城管不管9 小时前
前后端远程协作
java
青云计划9 小时前
Feed流
java·后端·spring
java1234_小锋9 小时前
String、StringBuilder、StringBuffer的区别?
java·开发语言