简要 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 的能力。

相关推荐
wbs_scy2 小时前
C++11:类新功能、lambda与包装器实战
开发语言·c++
知识分享小能手2 小时前
Oracle 19c入门学习教程,从入门到精通,Oracle 的闪回技术 — 语法知识点与使用方法详解(19)
数据库·学习·oracle
不光头强2 小时前
kafka学习要点
分布式·学习·kafka
凉、介2 小时前
ACRN Hypervisor 简介
笔记·学习·虚拟化
飞鹰512 小时前
深度学习算子CUDA优化实战:从GEMM到Transformer—Week4学习总结
c++·人工智能·深度学习·学习·transformer
2301_765703142 小时前
C++中的职责链模式实战
开发语言·c++·算法
好好研究2 小时前
Spring Boot - Thymeleaf模板引擎
java·spring boot·后端·thymeleaf
爬山算法2 小时前
Hibernate(76)如何在混合持久化环境中使用Hibernate?
java·后端·hibernate
顾西爵霞2 小时前
个人学习主页搭建指南:从毛坯房到精装户型
学习·html