这里写自定义目录标题
- [1、Java 的四种引用和应用场景](#1、Java 的四种引用和应用场景)
- [2、HashMap 和 ConcurrentHashMap 的底层实现机制](#2、HashMap 和 ConcurrentHashMap 的底层实现机制)
- [3、ArrayList 和 LinkedList 的底层原理区别](#3、ArrayList 和 LinkedList 的底层原理区别)
- [4、Checked Exception 和 Unchecked Exception 的区别](#4、Checked Exception 和 Unchecked Exception 的区别)
-
- [Checked Exception](#Checked Exception)
- [Unchecked Exception](#Unchecked Exception)
- 5、反射机制原理和使用事项
- 6、深拷贝和浅拷贝的实现方式
- [7、Java 泛型的本质与类型擦除机制](#7、Java 泛型的本质与类型擦除机制)
1、Java 的四种引用和应用场景
- 强引用
- 普通常用的对象;GC 不会主动回收,需要置 null;内存不足会引起 OOM
- 软引用
- 当内存不足时会进行回收;常见于图片缓存、数据缓存等
- 弱引用
- 当发生 GC 时会进行回收;常用于 ThreadLocal 等
- 虚引用
- 回收前通知(get() 永远为空);用于堆外内存释放、资源清理等
2、HashMap 和 ConcurrentHashMap 的底层实现机制
HashMap
- 数据结构
- 数组 + 链表/红黑树
- 数组默认初始长度:16
- 计算 Key 的 hash 值:对 Key 高 16 位与低 16 位进行异或,能更均匀地分布(而非直接使用 Key 的 hashcode)
- 对 Key 的定位:使用 (n-1)&hash值(而非 hash值%n,位运算更快)
- 扩容机制
- 默认因子 0.75,当数组长度大于 16*0.75=12 时,开始翻倍扩容长度
- 哈希冲突
- 当 Key 计算的 hash 值有冲突时,在数组位置的以链表形式往下增加
- 当 链表长度大于 8 且数组长度大于 64 时,单链表转为红黑树结构
ConcurrentHashMap
- 数据结构同上
- 不同:线程安全
- CAS:用于初始化数组、创建第一个节点、扩容数组时
- synchronized:用于发生哈希冲突时,仅锁住当前链表或者树的头节点
- 高效的扩容机制
- 旧数据与新数据会同时存在,旧数据存在特殊节点(哈希值为-1的节点)
- 当一个线程查到特殊节点是,会自动到新数组找数据
- 当一个线程发现在扩容,会协助一起进行数据迁移
3、ArrayList 和 LinkedList 的底层原理区别
两个都是有序的容器,ArrayList 像一排储物格,LinkedList 像串起来的珍珠。
ArrayList
- 底层是数组,开辟一块连续的内存地址,数组默认长度为 10
- 扩容时,数组长度翻1.5倍,将数据复制到新的数组上
- 查找快;队尾新增元素快;队中新增元素慢(要挪后续的)
LinkedList
- 底层是双向链表,使用零散的内除地址
- 每个元素存储数据+前后节点的引用地址
- 每个节点由于额外消耗存放指针的内存,比 ArrayList 多用 20~30% 内存
对比
- 随机查询(get)
- ArrayList: O(1)
- LinkedList: O(n)
- 尾部添加(add)
- ArrayList: O(1)
- LinkedList: O(1)
- 中间插入/删除
- ArrayList: O(n) ------ 底层是 C 语言的 System.arraycopy, 也极快
- LinkedList: O(1) ------ 实际需要先遍历到目标, 先耗时 O(M)
- 场景
- ArrayList:查得多,查得快
- LinkedList:实现栈、队列
4、Checked Exception 和 Unchecked Exception 的区别
Checked Exception
- 继承于java.lang.Exception,编译期就会检查
- 常见于网络中断、资源文件不存在等外部因素
- 这种属于可预见性的,但又是无法避免的情况:需要 try-catch 捕捉,解决或者抛出让人来解决
Unchecked Exception
- 继承于 java.lang.RuntimeException or Error,编译期不会强制检查
- 常见于编写程序时的逻辑错误或不完善,如未判空、数组越界等
- 这种可以在编写程序时"处处"加上判断,或在不断优化完善代码中解决(主要是通过不断修正代码逻辑来避免这种异常)
5、反射机制原理和使用事项
原理
- 反射允许在运行过程中,动态地获取类信息,并操作类的成员
- "前置操作" ------ 反射的基石
- 类加载:JVM 的类加载器将一个类 .class 字节码文件,读取放入内存中
- 元数据:JVM 在内存中读出类的字节码后,解析出这个类的结构信息,如类名、类方法、类字段等
- Class 对象:JVM 在堆内存中创建这个类的唯一入口 ------ java.lang.Class
- 反射的使用
- 获取入口:通过 Class.forName() 或 Obj.getClass() 获取目标类的 Class 对象
- 查询元数据:通过 Class 类对象提供的方法,获取类的构造器、方法、字段等信息
- 创建对象:通过获取的类构造器等,进行创建
使用事项
- 性能开销
- 反射操作慢很多,比年高频调用代码时使用
- 安全性
- 反射可以绕过访问控制修饰符(如 private)
- 代码可维护性
- 可读性以及编译器难发现错误
6、深拷贝和浅拷贝的实现方式
深拷贝
- 创建新的内存空间,复制对象本身后,再递归循环其引用类型字段
- 核心实现:
- 方式一:重写 clone() 方法(递归克隆),要求所有字段都实现了 Cloneable 接口(并重写了 clone() 方法)
- 方式二:序列化与反序列化,将对象序列化成字节流,在反序列化出新对象,要求都实现了 Serializable 接口
浅拷贝
- 复制对象本身,对于对象内的引用类型字段,只复制其引用地址
- 原对象和被复制对象,其引用字段是指向同一个堆内存地址
- 核心实现:实现 Cloneable 接口,重写 clone() 方法 ------ 重写时调用 super.clone()
7、Java 泛型的本质与类型擦除机制
泛型是什么?
- 泛型是一种编译期的类型安全工具。它允许我们编写通用的类、接口或方法(如 List),在使用时再指定具体的类型(如 List)。这样既能复用代码,又能避免强制类型转换和运行时错误。
- 通俗理解:泛型是提供了一种模式(只需要一个通用的类、方法等),让使用泛型的地方,在程序员编写代码的时候可以任意指定类型;然后启动程序开始编译时,编译器会将指定的类型给擦除,即泛型擦除,让所有用了该东西的地方回归"原始";等在运行的时候,由于是通过了编译,所以在运行到这块的时候也不会有啥问题。
类型擦除是怎么回事?
- Java 的泛型是通过 "类型擦除" 实现的:
- 编译时:编译器会检查泛型使用的合法性(比如不能往 List 里放整数);
- 编译后:所有泛型信息都会被擦除,替换为原始类型(如 List)或上界类型(如 Object);
- 运行时:JVM 完全不知道泛型的存在,执行的是擦除后的普通代码。
为什么要有擦除?
- 主要是为了向后兼容------让 Java 5 引入泛型后,新代码仍能在旧版本的 JVM 上运行。
擦除带来的限制(常见问题)
- 运行时无法获取泛型的具体类型:
- list instanceof List 是非法的,因为运行时 List 已变成 List。
- 不能创建泛型数组:
- new T[] 或 new ArrayList[] 会编译报错,因为数组需要运行时类型信息,而泛型没有。
- 不能直接实例化类型参数:
- 无法写 new T(),因为编译器不知道 T 到底是什么类,也无法调用其构造函数。
- 多态需"桥接方法"支持:
- 编译器会自动生成额外方法,确保泛型继承下的多态行为正确(对开发者透明)。
关键一句话记住:"泛型只存在于源码中,编译后就消失了。" ------ 泛型是编译器的承诺,不是JVM 的能力。