目录
[模块一:8 大基本数据类型与包装类](#模块一:8 大基本数据类型与包装类)
[1. 8 大基本数据类型基础](#1. 8 大基本数据类型基础)
[2. 自动装箱 / 拆箱与包装类缓存池](#2. 自动装箱 / 拆箱与包装类缓存池)
[3. 基本类型与包装类的使用场景区别](#3. 基本类型与包装类的使用场景区别)
[1. 权限修饰符](#1. 权限修饰符)
[2. 运算符核心区别](#2. 运算符核心区别)
[3. 流程控制核心坑点](#3. 流程控制核心坑点)
[模块三:String 类核心(面试 100% 必问)](#模块三:String 类核心(面试 100% 必问))
[1. String 不可变的底层实现与好处](#1. String 不可变的底层实现与好处)
[2. StringTable(字符串常量池)与字符串拼接](#2. StringTable(字符串常量池)与字符串拼接)
[3. String、StringBuilder、StringBuffer 的区别](#3. String、StringBuilder、StringBuffer 的区别)
[Day1 当日验收清单](#Day1 当日验收清单)
[一、基础必考题(8 道,中小厂实习 100% 覆盖)](#一、基础必考题(8 道,中小厂实习 100% 覆盖))
[1. 请列出 Java 的 8 大基本数据类型、对应字节数和包装类](#1. 请列出 Java 的 8 大基本数据类型、对应字节数和包装类)
[2. 以下代码的输出结果是什么?为什么?](#2. 以下代码的输出结果是什么?为什么?)
[3. 请详细说明==和equals()的核心区别](#3. 请详细说明==和equals()的核心区别)
[4. String 为什么设计成不可变的?有什么核心好处?](#4. String 为什么设计成不可变的?有什么核心好处?)
[5. String、StringBuilder、StringBuffer 的核心区别是什么?](#5. String、StringBuilder、StringBuffer 的核心区别是什么?)
[6. 4 种权限修饰符的访问范围区别是什么?](#6. 4 种权限修饰符的访问范围区别是什么?)
[7. 自动装箱和拆箱的底层原理是什么?](#7. 自动装箱和拆箱的底层原理是什么?)
[8. switch 语句支持哪些数据类型?不支持哪些?](#8. switch 语句支持哪些数据类型?不支持哪些?)
[二、场景坑点题(6 道,大厂实习高频代码题 / 找错题)](#二、场景坑点题(6 道,大厂实习高频代码题 / 找错题))
[1. 以下是校园二手平台商品价格计算的代码,运行会报什么错?为什么?怎么修复?](#1. 以下是校园二手平台商品价格计算的代码,运行会报什么错?为什么?怎么修复?)
[2. 以下是订单状态处理的代码,输出结果是什么?为什么?](#2. 以下是订单状态处理的代码,输出结果是什么?为什么?)
[3. 以下是下架二手平台过期商品的代码,运行会报什么错?为什么?怎么正确删除?](#3. 以下是下架二手平台过期商品的代码,运行会报什么错?为什么?怎么正确删除?)
[4. 以下代码的输出结果是什么?为什么?](#4. 以下代码的输出结果是什么?为什么?)
[5. 以下用户判空代码运行会报什么错?为什么?](#5. 以下用户判空代码运行会报什么错?为什么?)
[6. 以下代码输出结果是什么?为什么?](#6. 以下代码输出结果是什么?为什么?)
[三、进阶原理题(4 道,中大厂实习拔高题)](#三、进阶原理题(4 道,中大厂实习拔高题))
[1. new String("abc")创建了几个对象?详细说明过程](#1. new String("abc")创建了几个对象?详细说明过程)
[2. JDK7 为什么要把 StringTable 从方法区(永久代)移到堆内存?](#2. JDK7 为什么要把 StringTable 从方法区(永久代)移到堆内存?)
[3. JDK7 开始 switch 支持 String 类型,底层是怎么实现的?](#3. JDK7 开始 switch 支持 String 类型,底层是怎么实现的?)
[4. 为什么 HashMap 的 key 推荐用 String、Integer 这类不可变类?](#4. 为什么 HashMap 的 key 推荐用 String、Integer 这类不可变类?)
模块一:8 大基本数据类型与包装类
1. 8 大基本数据类型基础
核心定义
Java 提供 8 种原生基本数据类型,直接存储值在栈内存,无对象实例化开销,每种都有固定内存大小、取值范围和对应的引用类型包装类:
| 数据类型 | 字节数 | 取值范围 | 对应包装类 |
|---|---|---|---|
| byte | 1 | [-128, 127] | Byte |
| short | 2 | [-32768, 32767] | Short |
| int | 4 | [-2^31, 2^31-1] | Integer |
| long | 8 | [-2^63, 2^63-1] | Long |
| float | 4 | 单精度浮点数 | Float |
| double | 8 | 双精度浮点数 | Double |
| char | 2 | [0, 65535](Unicode 字符) | Character |
| boolean | 1(编译后实现) | true/false | Boolean |
个人理解
基本数据类型是 Java 中最底层的数据单元,核心优势是性能高、内存占用小;包装类是对基本类型的对象化封装,让基本类型具备面向对象的特性(可存入集合、可赋值为 null、自带工具方法),是 Java"一切皆对象" 设计的补充。
项目实际使用场景
结合校园二手平台的开发实践:
- 业务数值字段(非空)用基本类型:商品库存、浏览量、订单编号、支付状态码,这类字段不会为 null,且频繁参与计算,用 int/long/boolean 基本类型,避免对象的内存和性能开销;
- 实体类可空字段用包装类:对应 MySQL 中允许为 NULL 的字段(如商品折扣价、用户备注长度),必须用 Integer/Double 包装类,避免基本类型默认值(如 int 默认 0)带来的业务逻辑错误(比如误判折扣价为 0 元);
- 小范围数值用低字节类型:性别编码、商品状态码(0 - 上架 / 1 - 下架)这类取值范围极小的字段,用 byte/short,节省堆内存。
面试考点标注
✅ 高频必背:熟记每种类型的字节数,尤其是 int (4)、long (8)、char (2);
✅ 坑点:boolean 类型在 JVM 中无明确大小规定,编译后默认用 int 实现,boolean 数组用 byte 数组实现;
✅ 场景题:实体类字段选基本类型还是包装类的判断标准。
2. 自动装箱 / 拆箱与包装类缓存池
核心定义
- 自动装箱 :编译器自动将基本类型转换为包装类,底层调用
包装类.valueOf(xxx)方法; - 自动拆箱 :编译器自动将包装类转换为基本类型,底层调用
xxxValue()方法(如Integer.intValue()); - 包装类缓存池 :为了复用常用对象,JDK 默认对部分包装类做了对象缓存:
- Byte/Short/Integer/Long:缓存范围
[-128, 127] - Character:缓存范围
[0, 127] - Boolean:缓存
true/false两个固定对象 - Float/Double:无缓存(浮点数取值无固定常用范围)
- Byte/Short/Integer/Long:缓存范围
个人理解
自动装箱拆箱是 Java 的语法糖,简化了代码书写,但本质还是编译器帮我们做了对象转换;缓存池是 JVM 的性能优化,对高频使用的小数值对象做复用,避免频繁创建销毁对象带来的内存浪费。
项目实际使用场景
- 订单状态码命中缓存 :订单状态(0 - 待支付 / 1 - 已支付 / 2 - 已完成)取值都在
[-128,127]内,自动装箱时会直接复用缓存池对象,不会创建新对象; - 禁止手动 new 包装类 :项目中绝对禁止写
Integer status = new Integer(1),这种写法会跳过缓存池,直接在堆中创建新对象,造成不必要的内存浪费; - 拆箱空指针避坑 :实体类中包装类字段拆箱前必须判空,比如
Integer discount = goods.getDiscount(); int dis = discount;,如果 discount 为 null,会直接抛出NullPointerException,这是项目中高频出现的线上 bug。
面试考点标注
✅ 必问:Integer a = 127; Integer b = 127; a == b 结果为 true,128 则为 false 的原因;
* **`Integer` 有缓存池:`-128 ~ 127`** * **自动装箱时,这个范围以内复用同一个对象** * **超出范围会 new 新对象** * **`==` 比较对象地址,不是比较值** * **包装类比较值永远用 `equals()`**
✅ 扩展:Integer 缓存池的上限可以通过 JVM 参数-XX:AutoBoxCacheMax修改;
✅ 坑点:包装类和基本类型混合运算时,包装类会自动拆箱,null 值会触发 NPE。
3. 基本类型与包装类的使用场景区别
核心定义
| 对比维度 | 基本类型 | 包装类 |
|---|---|---|
| 存储位置 | 栈内存 | 堆内存(对象头 + 数据) |
| null 值 | 不支持,有默认值 | 支持赋值为 null |
| 性能 | 极高,无对象开销 | 较低,有 GC 开销 |
| 泛型 / 集合 | 不支持 | 支持 |
| 对象方法 | 无 | 自带工具方法(如 Integer.parseInt ()) |
个人理解
核心判断标准只有两个:字段是否允许为 null 、是否需要对象特性。业务开发中优先用基本类型,只有需要对象特性或允许为 null 时才用包装类。
项目实际使用场景
- 局部变量 / 计算变量用基本类型:循环计数器、价格计算中间值、方法内临时变量,用基本类型性能最高;
- 泛型集合必须用包装类 :
List<Integer> goodsIdList、Map<String, Long> userMap,Java 泛型不支持基本类型; - 可选参数用包装类:商品搜索接口的价格区间参数(minPrice/maxPrice),允许用户不传(为 null),必须用 Double 包装类。
面试考点标注
✅ 场景题:开发中如何选择基本类型和包装类;
【常规计算用基本,可空集合用包装;数据库空选包装,泛型只能包装上。】
✅ 坑点:包装类的 equals 比较必须先判空,避免 NPE。
模块二:核心语法坑点
1. 权限修饰符
核心定义
4 种权限修饰符控制类、方法、变量的可见范围,是 Java 封装特性的核心实现,访问权限从大到小:public > protected > default(包私有,不写修饰符)> private
| 修饰符 | 同一类内 | 同一包内 | 不同包的子类 | 不同包的非子类 |
|---|---|---|---|---|
| public | ✅ | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ✅ | ❌ |
| default | ✅ | ✅ | ❌ | ❌ |
| private | ✅ | ❌ | ❌ | ❌ |
个人理解
权限修饰符的核心作用是控制代码暴露范围,降低耦合,不该对外暴露的方法 / 变量绝对不要暴露,减少外部调用的误用风险。
项目实际使用场景
- Service 层业务方法用 public:供 Controller 层跨包调用;
- 类内工具方法用 private:比如订单 Service 内部的计算运费方法,只在当前类使用,禁止外部调用;
- 同包基础类用 default:同包下的 BaseController、BaseService,只允许包内的 Controller/Service 继承,不对外暴露;
- 父类扩展方法用 protected:BaseService 中的初始化方法,只允许子类重写,不对外暴露。
面试考点标注
✅ 必背:4 种修饰符的访问边界,尤其是 protected 和 default 的区别;
✅ 细节:接口的成员变量默认是public static final,方法默认是public abstract;
✅ 限制:外部类只能用 public 或 default 修饰,内部类可以用全部 4 种修饰符。
2. 运算符核心区别
核心定义
- == 与 equals () :
- ==:基本类型比较值,引用类型比较对象内存地址;
- equals ():Object 类默认和 == 一致,常用类(String、Integer)都重写了该方法,用于比较对象内容。
- 短路与非短路逻辑符 :
&&/||:短路运算符,前面的条件能确定结果时,后面的条件不执行;&/|:非短路运算符,所有条件都会执行。
- 位移运算符 :
<<左移(等价于乘 2^n)、>>带符号右移(等价于除 2^n)、>>>无符号右移。
个人理解
运算符的坑点是业务 bug 的高频来源,尤其是字符串比较和空指针判断,开发中必须严格遵守规范。
项目实际使用场景
- 字符串比较必须用 equals ():用户登录时判断用户名、前端传的商品分类,绝对不能用 ==,因为前端传的字符串是 new 出来的对象,和常量池的字符串地址不同;
- 空指针判断用短路 && :
if (user != null && user.getId() != null),user 为 null 时后面的条件不会执行,避免 NPE;如果用 &,user 为 null 时仍会执行后面的代码,直接报错; - 数值计算用位移优化 :商品库存翻倍计算,用
stock << 1代替stock * 2,性能更高。
面试考点标注
✅ 必问:== 和 equals 的区别,分基本类型、引用类型、String 三种场景回答;
【
- 基本类型
-
== :比较实际数值
-
无 equals 方法 ,基本类型只能用
==
- 普通引用类型(Integer、自定义对象等)
-
== :比较内存地址
-
equals :默认也是比地址,重写后比对象内容
- String 字符串
-
== :比较字符串地址
-
equals :重写过,比较字符内容
】
✅ 场景题:if (a == null & a.getId()) 会抛出 NPE,换成 && 则不会;
✅ 细节:重写 equals () 必须同时重写 hashCode (),否则 HashMap 中会出现 key 重复的问题。
3. 流程控制核心坑点
核心定义
- switch 语句 :
- 支持类型:byte/short/int/char、枚举、String(JDK7+),不支持 long/float/double;
- String 支持底层:将 String 转为 hashCode,本质还是 int 类型的 switch;
- case 穿透:case 分支不加 break 时,会继续执行后续所有 case 的代码。
- foreach 循环 :底层基于 Iterator 迭代器实现,遍历过程中直接调用集合的 add/remove 方法,会触发
ConcurrentModificationException(并发修改异常)。
个人理解
switch 的 case 穿透是高频业务 bug 来源,foreach 的并发修改异常是集合操作的经典坑点。
项目实际使用场景
- 订单状态流转加 break 防穿透:用 switch 处理订单状态时,每个 case 后必须加 break,否则待支付订单处理完后会继续执行已支付的逻辑,造成业务错误;
- 遍历删除集合用 Iterator :遍历商品列表删除下架商品时,不能直接在 foreach 里调用
list.remove(),必须用iterator.remove(),避免并发修改异常; - 字符串类型的业务判断用 switch:根据订单类型(sale/rent/exchange)走不同逻辑,用 switch 比 if-else 可读性更高。
面试考点标注
✅ 坑点:switch 的 case 穿透场景和解决方法;
【case 匹配成功后,没有 break,会继续执行后续所有 case 代码,不会再判断条件。
解决穿透问题
-
每个 case 末尾加 break(最常用)匹配结束直接跳出 switch,阻止向下穿透
-
return 直接返回方法,终止执行
-
JDK14+ 使用箭头 case,天然无穿透】
✅ 原理:foreach 循环的底层实现,以及为什么会触发并发修改异常(fail-fast 机制);
【foreach 不是新语法,只是语法糖!
-
foreach 底层 = Iterator 迭代器
-
fail-fast 是集合的并发修改检测机制
-
遍历过程中用集合方法 add/remove → modCount 改变 → 抛出异常
-
想边遍历边删除,必须用迭代器的 remove ()
】
✅ 限制:switch 不支持 long、float、double 的原因。
【
-
底层判定逻辑: switch 依靠哈希等值匹配判断分支,要求数据能精准确定相等值。
-
浮点型缺陷: float、double 存在精度丢失,数值近似不等同实际相等,无法精准匹配 case 常量,判定结果不可靠。
-
long 类型缺陷: JDK 早期 switch 设计仅适配int 类型,byte、char、short 会自动转 int;long 占用 64 位,无法向上兼容转为 int,语法层面不兼容。
-
**补充支持类型:**仅支持:byte、short、char、int、枚举、String
】
模块三:String 类核心(面试 100% 必问)
1. String 不可变的底层实现与好处
核心定义
- 不可变定义:String 对象一旦创建,其字符内容无法修改,所有看似修改 String 的方法(substring、concat)都是返回新的 String 对象;
- 底层实现 :
- JDK8 及之前:
private final char[] value存储字符,数组引用被 final 修饰,无法指向新数组; - JDK9 及之后:优化为
private final byte[] value+ 编码标记,节省内存; - String 类本身被 final 修饰,无法被继承,没有子类可以修改其不可变逻辑。
- JDK8 及之前:
个人理解
String 不可变是 Java 的核心设计,本质是用 final 和私有封装,完全禁止外部修改 String 的内部数据,是 Java 安全性和性能优化的基础。
项目实际使用场景
- 敏感信息安全:用户的手机号、token、密码用 String 存储,不可变保证了这些信息不会被代码意外修改,避免安全漏洞;
- HashMap 的 key 首选 :商品分类缓存用
HashMap<String, GoodsCategory>,String 不可变保证 hashCode 不会变化,不需要重新计算哈希,也不会出现 key 找不到的问题; - 多线程安全:多线程环境下读取商品标题、用户昵称时,不可变的 String 不需要加锁,天然线程安全。
面试考点标注
✅ 必问:String 为什么设计成不可变?
✅ 必答:1. 线程安全;2. 可以复用字符串常量池,节省内存;3. 作为 HashMap 的 key 哈希值稳定;4. 敏感信息安全,避免被篡改。
2. StringTable(字符串常量池)与字符串拼接
核心定义
- StringTable:存储字符串常量的哈希表,JDK7 及之后移到堆内存(之前在方法区),作用是复用相同内容的字符串,避免重复创建对象;
- 字符串拼接优化 :
- 常量拼接(
"a"+"b"):编译期直接优化为"ab",存入常量池; - 动态拼接(变量 + 字符串):编译期优化为
StringBuilder.append(),最后调用toString()生成新 String 对象。
- 常量拼接(
个人理解
字符串常量池是 JVM 对 String 的核心性能优化,不同的创建方式会产生不同的对象位置,是面试的核心考点。
项目实际使用场景
- 商品搜索接口拼接用 StringBuilder:动态拼接搜索 SQL 条件、商品描述时,绝对不能用循环 +"+" 拼接,因为每次循环都会创建新的 StringBuilder 和 String 对象,性能极差;必须手动创建 StringBuilder,循环调用 append ();
- 优先用字面量创建 String :
String key = "goods:search:"直接从常量池取对象,比new String("goods:search:")节省内存,后者会额外在堆中创建新对象。
面试考点标注
✅ 必问:String s = new String("abc")创建了几个对象?
✅ 答:2 个(常量池的 "abc" + 堆中的 new String 对象);如果常量池已经存在 "abc",则只创建 1 个堆对象;
✅ 细节:JDK7 把 StringTable 移到堆的原因:方法区内存小、GC 频率低,堆的 GC 更灵活,适合回收不常用的字符串。
3. String、StringBuilder、StringBuffer 的区别
核心定义
| 对比维度 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变 | 可变 | 可变 |
| 线程安全 | 安全(不可变) | 不安全 | 安全(方法加 synchronized) |
| 性能 | 最低(每次修改都生成新对象) | 最高 | 较低(有锁开销) |
| 适用场景 | 少量字符串操作、常量定义 | 单线程大量拼接 | 多线程大量拼接 |
个人理解
99% 的业务开发场景都是单线程字符串拼接,所以优先用 StringBuilder,性能最高;只有明确的多线程拼接场景才用 StringBuffer。
项目实际使用场景
- 单线程拼接用 StringBuilder:Controller 层返回的 JSON 拼接、动态 SQL 拼接、商品列表的 HTML 描述拼接,都是单线程环境,用 StringBuilder 性能最优;
- 多线程日志拼接用 StringBuffer:全局日志拦截器中,多个线程同时拼接请求日志,用 StringBuffer 保证线程安全。
面试考点标注
✅ 必问:三者的核心区别,从可变性、线程安全、性能、使用场景四个维度回答;
✅ 细节:StringBuilder 和 StringBuffer 都继承自 AbstractStringBuilder,核心实现一致,只是 StringBuffer 的方法加了 synchronized 修饰。
Day1 当日验收清单
- 不看资料口述:8 大基本类型的包装类缓存规则、== 和 equals 的 3 种场景差异、String 不可变的 3 个核心好处;
- 结合项目口述:商品搜索接口为什么用 StringBuilder 拼接、实体类字段什么时候用包装类;
- 避坑确认:自动拆箱的 NPE 场景、foreach 修改集合的异常、switch 的 case 穿透。
面试模拟题(实习面试专属,附标准答案)
所有题目均来自一二线互联网公司 Java 实习面试高频题库,100% 覆盖 Day1 复习知识点,分为 3 个难度梯度,适配不同层次的实习面试要求。
一、基础必考题(8 道,中小厂实习 100% 覆盖)
1. 请列出 Java 的 8 大基本数据类型、对应字节数和包装类
【标准答案】
| 数据类型 | 字节数 | 对应包装类 |
|---|---|---|
| byte | 1 | Byte |
| short | 2 | Short |
| int | 4 | Integer |
| long | 8 | Long |
| float | 4 | Float |
| double | 8 | Double |
| char | 2 | Character |
| boolean | 1(编译后实现) | Boolean |
【考点对应】模块一:8 大基本数据类型基础
2. 以下代码的输出结果是什么?为什么?
java
Integer a = 127;
Integer b = 127;
Integer c = 128;
Integer d = 128;
System.out.println(a == b);
System.out.println(c == d);
【标准答案】输出结果:true、false原因:Integer 默认缓存[-128, 127]范围的对象,127 命中缓存,a 和 b 指向同一个缓存对象;128 超出缓存范围,会创建两个新的堆对象,地址不同。
【考点对应】模块一:包装类缓存池机制
3. 请详细说明==和equals()的核心区别
【标准答案】分 3 种场景区分:
- 基本类型 :
==只比较值,基本类型没有equals()方法; - 普通引用类型 :
==比较对象的内存地址,equals()默认和==一致(Object 类的实现),未重写时也是比较地址; - 重写了 equals 的类(String、Integer 等) :
==仍比较地址,equals()比较对象的内容。
【考点对应】模块二:运算符核心区别
4. String 为什么设计成不可变的?有什么核心好处?
【标准答案】底层实现:String 类被 final 修饰无法继承,存储字符的数组被private final修饰,不对外暴露修改数组的方法。核心好处:
- 线程安全:不可变对象多线程下无需加锁,不会被修改;
- 节省内存:可以复用字符串常量池的对象,避免重复创建;
- 哈希稳定:作为 HashMap 的 key 时,hashCode 不会变化,无需重复计算,不会出现 key 找不到的问题;
- 安全:敏感信息(如密码、token)不会被意外篡改。
【考点对应】模块三:String 不可变特性
5. String、StringBuilder、StringBuffer 的核心区别是什么?
【标准答案】
| 对比维度 | String | StringBuilder | StringBuffer |
|---|---|---|---|
| 可变性 | 不可变,每次修改生成新对象 | 可变,直接修改内部数组 | 可变 |
| 线程安全 | 安全(不可变) | 不安全 | 安全(方法加 synchronized 锁) |
| 性能 | 最低 | 最高 | 较低(有锁开销) |
| 适用场景 | 常量定义、少量字符串操作 | 单线程下大量字符串拼接 | 多线程下大量字符串拼接 |
【考点对应】模块三:String 类三者对比
6. 4 种权限修饰符的访问范围区别是什么?
【标准答案】权限从大到小:public > protected > default(包私有)> private
| 修饰符 | 同一类内 | 同一包内 | 不同包的子类 | 不同包的非子类 |
|---|---|---|---|---|
| public | ✅ | ✅ | ✅ | ✅ |
| protected | ✅ | ✅ | ✅ | ❌ |
| default | ✅ | ✅ | ❌ | ❌ |
| private | ✅ | ❌ | ❌ | ❌ |
【考点对应】模块二:权限修饰符
7. 自动装箱和拆箱的底层原理是什么?
【标准答案】
- 自动装箱:编译器自动调用
包装类.valueOf(基本类型)方法,将基本类型转为包装类对象; - 自动拆箱:编译器自动调用
包装类.xxxValue()方法(如Integer.intValue()),将包装类转为基本类型;本质是 Java 的语法糖,编译阶段完成转换,运行期无额外优化。
【考点对应】模块一:自动装箱 / 拆箱
8. switch 语句支持哪些数据类型?不支持哪些?
【标准答案】支持的类型:byte、short、int、char、枚举、String(JDK7+)不支持的类型:long、float、double,因为这些类型的取值范围大,无法做精确的等值匹配。
【考点对应】模块二:流程控制坑点
二、场景坑点题(6 道,大厂实习高频代码题 / 找错题)
1. 以下是校园二手平台商品价格计算的代码,运行会报什么错?为什么?怎么修复?
java
public class GoodsTest {
public static void main(String[] args) {
Integer discount = null; // 商品折扣,数据库查出来为null
int originPrice = 100;
int finalPrice = originPrice * discount / 10;
System.out.println(finalPrice);
}
}
【标准答案】报错:NullPointerException(空指针异常)原因:discount是 Integer 包装类,和基本类型运算时会自动拆箱,null 值拆箱会触发空指针。修复方案:拆箱前先判空,给默认值:
java
int finalPrice = originPrice * (discount == null ? 10 : discount) / 10;
【考点对应】模块一:自动拆箱空指针坑点
2. 以下是订单状态处理的代码,输出结果是什么?为什么?
java
public class SwitchTest {
public static void main(String[] args) {
int orderStatus = 1; // 1-已支付
switch (orderStatus) {
case 0:
System.out.println("待支付");
case 1:
System.out.println("已支付");
case 2:
System.out.println("已完成");
default:
System.out.println("状态异常");
}
}
}
【标准答案】输出结果:
java
已支付
已完成
状态异常
原因:case 穿透,case 1 分支没有加break,执行完后会继续执行后续所有 case 和 default 的代码。修复:每个 case 分支结束后加break。
【考点对应】模块二:switch case 穿透坑点
3. 以下是下架二手平台过期商品的代码,运行会报什么错?为什么?怎么正确删除?
java
List<Goods> goodsList = new ArrayList<>();
goodsList.add(new Goods(1, "手机", true));
goodsList.add(new Goods(2, "电脑", false));
goodsList.add(new Goods(3, "平板", false));
// 遍历删除下架商品
for (Goods goods : goodsList) {
if (!goods.isOnSale()) {
goodsList.remove(goods);
}
}
【标准答案】报错:ConcurrentModificationException(并发修改异常)原因:foreach 循环底层是 Iterator 迭代器,直接调用list.remove()会修改集合的 modCount 计数,迭代器检测到计数变化会触发 fail-fast 机制抛出异常。正确删除方式:使用 Iterator 的 remove () 方法
java
Iterator<Goods> iterator = goodsList.iterator();
while (iterator.hasNext()) {
Goods goods = iterator.next();
if (!goods.isOnSale()) {
iterator.remove();
}
}
【考点对应】模块二:foreach 循环坑点
4. 以下代码的输出结果是什么?为什么?
java
String s1 = "abc";
String s2 = new String("abc");
String s3 = "a" + "b" + "c";
String s4 = new String("abc").intern();
System.out.println(s1 == s2);
System.out.println(s1 == s3);
System.out.println(s1 == s4);
【标准答案】输出结果:false、true、true原因:
- s1 是常量池的对象,s2 是堆中的新对象,地址不同;
- s3 是常量拼接,编译期直接优化为常量池的 "abc",和 s1 指向同一个对象;
intern()方法会把堆中的 String 对象加入常量池,返回常量池的引用,和 s1 指向同一个对象。
【考点对应】模块三:字符串常量池与拼接优化
5. 以下用户判空代码运行会报什么错?为什么?
java
User user = null;
if (user != null & user.getId() == 1) {
System.out.println("用户ID为1");
}
【标准答案】报错:NullPointerException原因:&是非短路逻辑运算符,无论前面条件是否成立,后面的条件都会执行;user 为 null 时,仍会执行user.getId()触发空指针。修复:换成短路运算符&&,前面条件为 false 时后面不会执行。
【考点对应】模块二:短路与非短路运算符区别
6. 以下代码输出结果是什么?为什么?
java
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1.equals(s2));
System.out.println(s1 == s2);
【标准答案】输出结果:true、false原因:
- String 重写了 equals () 方法,比较的是字符数组的内容,两个对象内容都是 "abc",所以为 true;
==比较的是对象的内存地址,两个 new 出来的对象在堆中是不同的地址,所以为 false。
【考点对应】模块二:== 与 equals 的场景区别
三、进阶原理题(4 道,中大厂实习拔高题)
1. new String("abc")创建了几个对象?详细说明过程
【标准答案】分两种情况:
- 常量池没有 "abc" :创建 2 个对象
- 第一个:在字符串常量池中创建 "abc" 对象;
- 第二个:在堆内存中创建一个 new String 对象,内部 value 数组指向常量池的 "abc" 的字符数组。
- 常量池已经有 "abc" :创建 1 个对象
- 只在堆中创建 new String 对象,复用常量池已有的 "abc"。
【考点对应】模块三:String 对象创建原理
2. JDK7 为什么要把 StringTable 从方法区(永久代)移到堆内存?
【标准答案】核心原因有两个:
- 内存限制问题:永久代内存很小,默认只有几十 M,字符串是高频创建的对象,很容易触发永久代 OOM;堆内存空间大,可灵活调整;
- GC 效率问题:永久代的 GC 频率极低,大量不用的字符串无法被及时回收,造成内存浪费;堆的 GC 频率高,能及时回收不再使用的字符串常量,释放内存。
【考点对应】模块三:StringTable 内存位置
3. JDK7 开始 switch 支持 String 类型,底层是怎么实现的?
【标准答案】本质是语法糖,底层还是 int 类型的 switch,编译阶段会做两层转换:
- 先调用 String 的
hashCode()方法,把字符串转为 int 值,用 switch 做第一层 int 匹配; - 匹配到 hashCode 后,再调用
equals()方法做二次校验,避免 hash 冲突。所以 switch 支持 String 的本质,还是对 int 类型 switch 的封装。
【考点对应】模块二:switch 底层原理
4. 为什么 HashMap 的 key 推荐用 String、Integer 这类不可变类?
【标准答案】核心原因有两个:
- 哈希值稳定:不可变类的内容不会变,hashCode 不会变化,存入 HashMap 后不需要重新计算哈希值,性能更高;如果用可变对象做 key,key 修改后 hashCode 会变化,会出现存入 HashMap 后找不到 key 的问题;
- 线程安全:不可变类多线程下不会被修改,不会出现并发场景下哈希值错乱的问题。
【考点对应】模块三:String 不可变的设计意义
实习面试答题加分技巧(针对你的校园二手平台项目)
- 所有知识点绑定项目:回答问题时主动结合自己的项目,比如问 StringBuilder,就说「我做校园二手平台的商品搜索接口时,动态拼接 SQL 条件就是用的 StringBuilder,因为循环用 + 拼接会每次创建新对象,性能很差」,比单纯背知识点加分很多;
- 主动说踩坑经验:遇到坑点题,主动提自己项目中踩过的对应坑,比如自动拆箱 NPE,就说「我之前做订单模块的时候,数据库的折扣字段允许为 null,用 int 接收自动拆箱踩过 NPE 的坑,后来都改成包装类 + 判空」;
- 答题逻辑固定为「结论→原理→项目场景」:先给明确答案,再讲底层原理,最后举自己的项目例子,逻辑清晰,面试官认可度极高。