掌上先机 Java 一面
面试题目
开场
- 自我介绍
Java 基础
- 基本类型有几个?
String类设置成常量(final)有什么好处?String的hashCode会被计算几次?- 为什么 Java 不能多继承?
类加载
new一个 Java 类的类加载过程是怎样的?
集合
HashMap底层数据结构是什么?HashMap的扩容算法是怎样的?- 为什么负载因子是 0.75?设置成 0.7 或 0.8 会有什么问题?
面向对象
- 面向对象三要素是什么?请简单介绍。
反问
- 反问环节
参考解析
Java 基本类型有几个?
共 8 种:byte、short、int、long、float、double、char、boolean。
对应包装类分别为 Byte、Short、Integer、Long、Float、Double、Character、Boolean。
String 设置成 final(常量)有什么好处?
- 安全性:字符串不可变,防止被子类篡改,适合作为 HashMap 的 key 和类加载参数。
- 线程安全:不可变对象天然线程安全,无需同步。
- 字符串常量池:相同内容的字符串可复用同一对象,节省内存。
- hashCode 可缓存:内容不变,hash 值只需计算一次。
String 的 hashCode 计算几次?
最多计算一次。String 内部有 hash 字段缓存结果,第一次调用 hashCode() 时计算并缓存,后续直接返回缓存值(但需注意 hash 值恰好为 0 时会重复计算,属于已知的小缺陷)。
为什么 Java 不能多继承?
主要是为了避免菱形继承(Diamond Problem):若类 C 同时继承 A 和 B,而 A、B 都重写了同一方法,编译器无法确定调用哪个版本,产生二义性。Java 通过接口(interface)+ 默认方法(default)的方式在一定程度上支持多继承行为,同时要求显式解决冲突。
new 一个对象的类加载过程
- 加载:ClassLoader 找到
.class文件,读入字节码,生成Class对象。 - 验证:校验字节码格式与安全性。
- 准备:为静态变量分配内存并赋默认值。
- 解析:将符号引用替换为直接引用。
- 初始化:执行静态代码块和静态变量赋值。
- 实例化:在堆中分配内存 → 执行构造方法 → 返回引用。
HashMap 底层数据结构
JDK 8 中为 数组 + 链表 + 红黑树。
- 数组(
Node[])存储桶; - 同一桶内冲突元素用链表连接;
- 链表长度 ≥ 8 且数组长度 ≥ 64 时,链表转为红黑树,提升查询效率至 O(log n)。
HashMap 扩容算法
- 触发条件:元素数量超过
capacity × loadFactor。 - 新容量 = 旧容量 × 2(始终保持 2 的幂次)。
- 重新散列:利用
(newCap - 1) & hash,原链表节点要么留在原位,要么移动到原索引 + 旧容量处,无需重新计算 hash,效率高。
负载因子为什么是 0.75?
这是空间与时间的折中:
- 过小(如 0.7):触发扩容更频繁,内存占用高,浪费空间。
- 过大(如 0.8):桶内冲突增多,链表/红黑树变长,查询性能下降。
- 0.75 在泊松分布下,桶中碰撞概率较低(长度 ≥ 8 的概率约为千万分之六),是工程实践中的最优经验值。
面向对象三要素
- 封装:隐藏内部实现,通过公开接口访问,提高安全性和可维护性。
- 继承:子类复用父类属性和方法,实现代码复用与扩展。
- 多态:同一接口/方法在不同对象上表现不同行为(编译时多态:重载;运行时多态:重写+向上转型)。