Java基础常见面试题总结(上)
背诵重点
上篇只保留 Java 基础里真正高频、容易追问底层的问题。像注释类型、关键字清单、Java ME、标识符规则、break/continue/return 这类偏语法手册的问题,面试价值很低,不再展开。
建议按这条线背:
- Java 为什么跨平台:JVM、字节码、JIT。
- 基本类型和包装类:默认值、缓存池、装箱拆箱、空指针。
- 数值计算:浮点精度和
BigDecimal。 - 参数传递和方法:值传递、重载、重写、
static、final。
JVM、JDK、JRE 有什么区别?
JVM 是 Java 虚拟机,负责运行字节码。不同平台有不同 JVM 实现,因此同一份 .class 字节码可以在不同操作系统上运行。
JRE 是 Java 运行环境,包含 JVM 和 Java 基础类库,用于运行 Java 程序。
JDK 是 Java 开发工具包,包含 JRE,还包含 javac、javadoc、jdb、jconsole、javap 等开发和调试工具。
面试补充:
- JDK 9 后引入模块化,可以用
jlink裁剪运行时。 - JDK 11 后 Oracle 不再单独提供传统 JRE 下载。
- 开发和编译 Java 程序需要 JDK,只运行程序通常只需要运行时环境。
什么是字节码?为什么 Java 能跨平台?
字节码是 JVM 能识别的中间指令,通常保存在 .class 文件中。它不面向某个具体 CPU,而是面向 JVM。
Java 跨平台靠的是“字节码 + 不同平台的 JVM 实现”。源码经过 javac 编译成字节码,字节码再交给不同系统上的 JVM 执行。
一句话背诵:Java 不是源码跨平台,而是字节码跨平台,JVM 屏蔽了操作系统差异。
为什么说 Java 是编译与解释并存?
Java 源码会先通过 javac 编译成字节码,这是编译阶段。
程序运行时,JVM 会解释执行字节码;对于频繁执行的热点代码,JIT 会把它编译成本地机器码并缓存起来,提高后续执行效率。
所以 Java 既有编译过程,也有运行时解释和 JIT 编译优化。
面试追问:
- JIT:运行时根据热点代码动态优化,适合长期运行服务。
- AOT:提前编译成本地代码,启动更快,但运行时动态优化空间更小。
Java 语言有哪些面试需要提的特点?
回答不用背长清单,保留面试有价值的几点即可:
- 面向对象:封装、继承、多态。
- 跨平台:通过字节码和 JVM 实现。
- 自动内存管理:由 GC 管理对象回收。
- 支持多线程:语言和标准库提供线程、锁、并发工具。
- 生态成熟:Spring、MyBatis、Netty、Maven、Gradle 等生态完整。
“简单易学”“安全性强”“网络编程方便”这类点可以知道,但没必要展开。
Java 和 C++ 有什么区别?
Java 更偏托管语言,运行在 JVM 上,有 GC、反射、跨平台能力和成熟生态;C++ 更接近底层,直接管理内存,性能控制更细,但复杂度更高。
常见对比:
- Java 不支持指针直接操作,C++ 支持指针。
- Java 类是单继承、多实现,C++ 支持多继承。
- Java 有自动垃圾回收,C++ 需要开发者管理对象生命周期。
- Java 不支持运算符重载和头文件,C++ 支持。
- Java 程序通过 JVM 运行,C++ 通常编译成本地机器码运行。
Java 有哪些基本数据类型?
Java 有 8 种基本类型:
| 类型 | 关键字 |
|---|---|
| 整数 | byte、short、int、long |
| 浮点 | float、double |
| 字符 | char |
| 布尔 | boolean |
面试重点不是背清单,而是能说出这些追问:
boolean的大小没有在 Java 语言规范中精确定义。- 整数字面量默认是
int,小数字面量默认是double。 char使用 Unicode 字符,通常占 2 字节。
基本类型和包装类型有什么区别?
基本类型保存具体值,包装类型是对象引用。
主要区别:
- 默认值不同:成员变量里
int默认0,Integer默认null。 - 存储方式不同:基本类型直接存值,包装类型存对象引用。
- 比较方式不同:基本类型用
==比值,包装类型应该用equals()比值。 - 泛型限制不同:泛型只能使用引用类型,所以只能写
List<Integer>,不能写List<int>。
为什么需要包装类?
- 集合和泛型需要对象。
- 可以表达
null。 - 可以调用对象方法,比如类型转换、比较、解析。
- 反射和框架处理类型时更方便。
自动装箱和拆箱是什么?
自动装箱是基本类型自动转包装类型;自动拆箱是包装类型自动转基本类型。
Integer a = 100; // 编译后类似 Integer.valueOf(100)
int b = a; // 编译后类似 a.intValue()必须背住两个坑:
- 包装对象为
null时自动拆箱会抛NullPointerException。 - 频繁装箱会创建对象或触发缓存查找,可能带来性能和内存开销。
实际开发中,集合里拿 Integer 做计算前要注意判空。
Integer 缓存池机制是什么?
Integer.valueOf() 默认缓存 -128 到 127 之间的整数对象。
Integer a = 127;
Integer b = 127;
System.out.println(a == b); // true
Integer c = 128;
Integer d = 128;
System.out.println(c == d); // false原因是 127 命中缓存,128 默认没有命中缓存,通常会创建新对象。
注意:
==比较包装类型时比较的是引用地址。- 比较包装类型数值应该使用
equals()。 Byte、Short、Long、Character也有类似缓存机制,只是范围不同。
浮点数为什么会有精度问题?
浮点数采用二进制表示,很多十进制小数无法被二进制精确表示,比如 0.1。计算时只能保存近似值,所以可能出现精度误差。
System.out.println(0.1 + 0.2); // 0.30000000000000004所以浮点数适合科学计算、统计估算,不适合金额、库存结算这类需要精确计算的场景。
金额计算为什么用 BigDecimal?
BigDecimal 可以表示任意精度的十进制数,适合金额计算。
使用原则:
- 推荐用字符串构造:
new BigDecimal("0.1")。 - 不推荐用
new BigDecimal(0.1),因为传入前double已经有误差。 - 除法要指定精度和舍入模式,避免无限循环小数抛异常。
- 比较数值是否相等用
compareTo(),不要直接用equals()。
BigDecimal a = new BigDecimal("1.0");
BigDecimal b = new BigDecimal("1.00");
System.out.println(a.equals(b)); // false,值和精度都比较
System.out.println(a.compareTo(b) == 0); // true,只比较数值大小Java 是值传递还是引用传递?
Java 只有值传递。
基本类型传递的是值的副本。引用类型传递的是引用地址的副本,方法内部可以通过这个地址副本修改对象内容,但不能改变调用方引用变量的指向。
void change(User user) {
user.setName("new"); // 调用方对象内容会变
user = new User(); // 调用方引用不会改指向
}一句话背诵:Java 传对象时传的是对象地址的副本,不是引用本身。
成员变量和局部变量有什么区别?
成员变量属于对象或类,定义在类中、方法外。局部变量定义在方法、构造器或代码块内部。
关键区别:
- 成员变量有默认值,局部变量必须显式初始化后才能使用。
- 实例成员变量随对象存在,静态成员变量随类存在。
- 局部变量随方法调用创建,方法结束后失效。
- 成员变量可以被访问修饰符修饰,局部变量不能。
static 关键字怎么理解?
static 表示属于类,而不是属于某个对象。
常见用法:
- 静态变量:类级共享数据。
- 静态方法:无需创建对象即可调用。
- 静态代码块:类加载时执行一次。
- 静态内部类:不依赖外部类实例。
静态方法不能直接访问非静态成员,因为静态方法调用时可能还没有具体对象。
方法重载和方法重写有什么区别?
重载发生在同一个类中,方法名相同,参数列表不同,和返回值无关,属于编译期多态。
重写发生在父子类之间,子类改写父类方法,方法名、参数列表要相同,返回值类型要兼容,访问权限不能更小,抛出的异常不能更宽,属于运行期多态。
记忆点:
- 重载看参数。
- 重写看继承和运行时对象。
private、static、final方法不能被真正重写。- 构造方法不能被重写。
可变长参数有什么注意点?
可变长参数本质是数组。
void log(String... messages) {
System.out.println(messages.length);
}注意:
- 一个方法最多只能有一个可变长参数。
- 可变长参数必须放在参数列表最后。
- 方法重载时,可变长参数可能导致调用匹配不直观,公共 API 中要谨慎使用。
final、finally、finalize 有什么区别?
final 是关键字:
- 修饰类:不能被继承。
- 修饰方法:不能被重写。
- 修饰变量:只能赋值一次。
finally 是异常处理结构中的代码块,通常用于释放资源。
finalize() 是 Object 的方法,GC 前可能调用,不可靠,已经不推荐使用。
访问修饰符怎么背?
从大到小:
| 修饰符 | 同类 | 同包 | 子类 | 任意位置 |
|---|---|---|---|---|
public | 是 | 是 | 是 | 是 |
protected | 是 | 是 | 是 | 否 |
| 默认 | 是 | 是 | 否 | 否 |
private | 是 | 否 | 否 | 否 |
访问控制的目的不是背表,而是封装。字段通常设为 private,通过方法暴露必要能力。
上篇可以删除或只需了解的问题
这些内容不再单独展开:
- Java SE、Java EE、Java ME:知道 SE 是基础平台,EE 是企业规范集合即可。
- 注释、标识符、关键字完整清单:偏语法手册,面试很少深入问。
- 自增自减、移位运算、
break/continue/return:除非笔试题,否则不值得长篇背。 - 字符常量和字符串常量:知道字符是单引号、字符串是双引号即可。
- 方法返回值类型分类:过于基础。
上篇口述模板
回答基础题时按这个顺序:
- 先给结论。
- 再讲底层机制。
- 补一个常见坑。
- 最后说实际开发建议。
比如问“自动装箱拆箱”:
可以答:自动装箱是基本类型转包装类型,底层常用 valueOf();拆箱是包装类型转基本类型,底层调用 xxxValue()。坑点是包装对象为 null 时拆箱会抛空指针,Integer 在 -128 到 127 有缓存,所以包装类型数值比较不要用 ==,应该用 equals()。
