Java基础常见面试题总结(中)
背诵重点
中篇只保留面向对象、Object 和 String 相关高频题。这部分最容易被追问“底层原因”和“容器里会不会出问题”,不要只背定义。
建议按这条线背:
- 面向对象:封装、继承、多态。
- 接口和抽象类:共同点、区别、怎么选。
Object:equals()、hashCode()、clone()。String:不可变、拼接、常量池、intern()。
面向对象和面向过程有什么区别?
面向过程把问题拆成一个个步骤,通过方法顺序执行解决问题。
面向对象先抽象出对象,再通过对象之间的协作解决问题。
面试回答重点:
- 面向对象更适合复杂系统,强调封装、复用和扩展。
- 面向过程更直接,适合简单流程。
- 不要简单说面向对象性能一定低,性能取决于具体实现、运行环境和优化方式。
面向对象三大特征是什么?
封装:把数据和行为放在对象内部,通过访问控制隐藏实现细节,对外暴露稳定接口。
继承:子类复用父类能力,表达 is-a 关系。Java 类只能单继承,避免多继承带来的菱形继承问题,但接口可以多实现。
多态:父类引用指向子类对象,运行时调用实际对象的方法。多态依赖继承、方法重写和向上转型。
一句话背诵:封装提高可维护性,继承提高复用性,多态提高扩展性。
对象实例和对象引用有什么区别?
对象实例是真正创建在堆内存中的对象。
对象引用是指向对象实例的变量,通常保存在栈帧的局部变量表或对象字段中。
一个引用可以不指向任何对象,也可以指向一个对象;一个对象可以被多个引用指向。
User user = new User();这里 new User() 是对象实例,user 是对象引用。
对象相等和引用相等有什么区别?
引用相等比较两个引用是否指向同一个对象,通常用 ==。
对象相等比较两个对象的内容是否相等,通常依赖重写后的 equals()。
String a = new String("abc");
String b = new String("abc");
System.out.println(a == b); // false
System.out.println(a.equals(b)); // true构造方法有哪些特点?能被重写吗?
构造方法用于初始化对象,方法名和类名相同,没有返回值。
特点:
- 如果没有显式声明构造方法,编译器会生成无参构造方法。
- 如果已经声明了构造方法,编译器不会再自动生成无参构造方法。
- 构造方法不能被继承,因此不能被重写。
- 子类构造方法必须先调用父类构造方法,默认是
super()。
接口和抽象类有什么共同点?
共同点:
- 都不能直接实例化。
- 都可以定义抽象方法。
- 都可以通过子类或实现类完成具体行为。
- 都能体现面向抽象编程。
共同点答完后必须继续说区别和选择,否则回答会显得太浅。
接口和抽象类有什么区别?
核心区别:抽象类描述“是什么”,接口描述“能做什么”。
具体区别:
- 类只能继承一个抽象类,但可以实现多个接口。
- 抽象类可以有成员变量、构造方法、普通方法;接口更适合定义能力规范。
- 接口字段默认是
public static final。 - Java 8 后接口可以有
default和static方法,Java 9 后接口可以有私有方法。 - 抽象类适合承载模板逻辑和共享状态,接口适合定义可插拔能力。
选择:
- 多个类共享状态或公共流程,用抽象类。
- 只定义行为规范,或需要多实现,用接口。
为什么 Java 不支持类多继承?
主要是为了避免多继承带来的复杂性,尤其是菱形继承问题。
如果一个类同时继承两个父类,而两个父类中有同名方法或状态,子类调用时容易产生歧义。
Java 通过“类单继承 + 接口多实现”来平衡复用和扩展。接口默认方法发生冲突时,实现类必须显式重写解决。
多态怎么理解?
多态是同一个父类引用,在运行时根据实际子类对象调用不同方法。
Animal animal = new Dog();
animal.say();编译期看引用类型 Animal,运行期看实际对象 Dog。如果 Dog 重写了 say(),最终调用的是 Dog 的实现。
多态的价值:
- 调用方依赖抽象,不依赖具体实现。
- 新增子类时,调用方代码可以少改甚至不改。
- 常用于策略模式、模板方法、Spring Bean 注入等场景。
Object 类常见方法有哪些?
Object 是所有 Java 类的根类。
常见方法:
equals():比较对象是否相等,默认比较引用地址。hashCode():返回哈希值,常用于哈希容器定位桶。toString():返回对象字符串表示。getClass():返回运行时类型。clone():对象复制,默认浅拷贝。wait()、notify()、notifyAll():线程协作。finalize():GC 前可能调用,已经不推荐使用。
== 和 equals() 有什么区别?
==:
- 比基本类型:比较值。
- 比引用类型:比较地址。
equals():
- 默认来自
Object,本质也是比较地址。 - 很多类会重写它,比如
String、Integer,用于比较内容。
回答时一定要分“基本类型”和“引用类型”,不要只说 == 比地址。
hashCode() 有什么用?
hashCode() 常用于哈希容器,比如 HashMap、HashSet。
哈希容器会先用 hashCode() 定位桶,再用 equals() 判断桶内对象是否真正相等。
这样做的价值是提高查找效率:先快速缩小范围,再做精确比较。
为什么重写 equals() 必须重写 hashCode()?
因为 Java 约定:两个对象 equals() 为 true,它们的 hashCode() 必须相同。
如果只重写 equals() 不重写 hashCode(),对象放进 HashSet 或作为 HashMap 的 key 时,可能出现明明内容相等却查不到、去重失败的问题。
记忆:
equals()相等,hashCode()必须相等。hashCode()相等,equals()不一定相等,因为可能哈希冲突。
深拷贝、浅拷贝、引用拷贝有什么区别?
引用拷贝:只复制引用,两个引用指向同一个对象。
浅拷贝:复制对象本身,但对象里的引用字段仍指向原来的子对象。
深拷贝:复制对象本身,也递归复制引用字段指向的对象,新旧对象完全独立。
Object.clone() 默认是浅拷贝,因为它只复制当前对象字段值。
实现深拷贝的方式:
- 手写复制逻辑。
- 复制构造器。
- 序列化和反序列化。
- 使用对象映射工具。
String、StringBuilder、StringBuffer 有什么区别?
String 不可变,每次修改都会产生新对象。适合少量字符串和常量。
StringBuilder 可变,非线程安全,性能高,适合单线程大量拼接。
StringBuffer 可变,方法加了同步,线程安全但性能较低,现在使用场景较少。
实际建议:
- 少量拼接用
+,可读性好。 - 循环中大量拼接用
StringBuilder。 - 多线程共享同一个可变字符串对象时才考虑
StringBuffer,但更常见做法是避免共享可变对象。
String 为什么不可变?
原因:
String类是final,不能被继承破坏行为。- 内部字符数据不对外暴露可变引用。
- 字符串常量池可以安全复用。
- 不可变对象线程安全。
- 可以缓存
hashCode(),适合作为HashMap的 key。
注意:final 修饰引用只能保证引用不变,不能单独保证对象内容不变。String 不可变是类设计共同保证的结果。
字符串拼接用 + 还是 StringBuilder?
分场景:
- 编译期常量拼接会被编译器直接优化。
- 普通少量拼接用
+没问题。 - 循环中大量拼接用
StringBuilder。
String s = "a" + "b"; // 编译期可优化成 "ab"循环里如果一直用 +,可能反复创建中间对象,影响性能。
字符串常量池有什么作用?
字符串常量池用于复用字符串对象,减少重复创建,节省内存。
String a = "abc";
String b = "abc";
System.out.println(a == b); // true两个字符串字面量都指向常量池里的同一个 "abc"。
new String("abc") 创建几个对象?
分情况:
- 如果常量池中没有
"abc",会创建两个对象:常量池中的"abc"和堆上的String对象。 - 如果常量池中已经有
"abc",只创建一个堆上的String对象。
String s = new String("abc");面试时不要死背“两个”,要说清是否已经存在于常量池。
String.intern() 有什么作用?
intern() 会返回字符串在常量池中的引用。
如果常量池中已经有相同内容的字符串,直接返回池中引用;如果没有,则尝试把当前字符串放入常量池,并返回池中引用。
面试重点:
intern()返回的是常量池引用。- JDK 7 后字符串常量池移到堆中,具体表现和早期版本不同。
- 实际业务中不要为了“优化”随便大量调用
intern(),要结合内存和重复率评估。
String 类型变量和常量做 + 运算有什么区别?
常量之间的 + 会在编译期优化。
String a = "a" + "b"; // 编译后等价于 "ab"变量参与拼接通常在运行期完成。
String x = "a";
String y = x + "b";运行期拼接可能通过 StringBuilder 或 invokedynamic 优化,具体和 JDK 版本有关。面试只需说清:常量拼接编译期优化,变量拼接运行期处理。
中篇可以删除或只需了解的问题
这些内容不再单独展开:
- “创建对象用什么运算符”:知道
new即可。 - “对象引用像绳子气球”这类长比喻:适合入门,不适合背诵。
- 面向对象和面向过程的大段示例代码:面试不需要手写这种示例。
String#equals()和Object#equals()的单独问题:已合并进==和equals()。
中篇口述模板
回答对象和字符串题时按这个顺序:
- 先说明概念。
- 再说明底层规则。
- 补充容器或常量池里的坑。
- 结合项目实践给建议。
比如问“为什么重写 equals() 必须重写 hashCode()”:
可以答:因为哈希容器会先用 hashCode() 定位桶,再用 equals() 判断对象是否相等。如果两个对象 equals() 为 true 但哈希值不同,它们可能落到不同桶里,导致 HashSet 去重失败或 HashMap 查不到 key。
