Java Object类与String相关高频面试题
一、Object类有哪些方法?
核心回答(11个核心方法,按功能分类)
java.lang.Object 是Java所有类的顶级父类,所有类默认继承Object,核心方法如下:
| 方法 | 作用 | 核心考点 |
|---|---|---|
equals(Object obj) |
判断两个对象是否相等 | 重写规则、与==的区别 |
hashCode() |
返回对象的哈希码值 | 与equals的契约、哈希表底层 |
toString() |
返回对象的字符串表示 | 默认格式:类名@哈希码十六进制,建议重写 |
getClass() |
返回对象的运行时Class对象 | 反射核心,final不可重写 |
clone() |
创建并返回对象的副本 | 浅克隆/深克隆、需实现Cloneable接口 |
finalize() |
对象被GC回收前调用 | Java9已废弃,不推荐使用 |
wait() / wait(long timeout) / wait(long timeout, int nanos) |
让当前线程等待,释放对象锁 | 线程通信、必须在synchronized中调用 |
notify() |
唤醒一个等待该对象锁的线程 | 线程通信、随机唤醒 |
notifyAll() |
唤醒所有等待该对象锁的线程 | 线程通信、全部唤醒 |
补充考点
getClass()、notify()、notifyAll()、wait()被final修饰,不能被重写equals()、hashCode()、toString()、clone()是可重写的,也是面试高频考点finalize()仅执行一次,且不保证立即执行,Java 9 标记为废弃,Java 18 完全移除
二、== 与 equals 有什么区别?
核心回答(分基本类型/引用类型)
1. == 的作用
-
基本数据类型 :比较的是数值是否相等
javaint a = 10; int b = 10; System.out.println(a == b); // true,数值相等 -
引用数据类型 :比较的是对象的内存地址是否相等 (是否是同一个对象)
javaString s1 = new String("abc"); String s2 = new String("abc"); System.out.println(s1 == s2); // false,两个不同对象,地址不同
2. equals() 的作用
equals() 是Object类的方法,默认实现和==完全一致 ,比较对象地址;
但很多类(如String、Integer)重写了equals(),改为比较对象的内容/值是否相等。
java
System.out.println(s1.equals(s2)); // true,String重写了equals,比较内容
核心区别总结
| 对比维度 | == |
equals() |
|---|---|---|
| 基本类型 | 比较数值 | 不能用于基本类型(只能用于对象) |
| 引用类型 | 比较内存地址 | 默认比较地址,可重写为比较内容 |
| 可重写性 | 运算符,不可重写 | 方法,可重写 |
| 空值处理 | null == null 为true |
null.equals(obj) 抛空指针异常 |
避坑示例(String常量池)
java
String s1 = "abc"; // 常量池对象
String s2 = "abc"; // 复用常量池对象
System.out.println(s1 == s2); // true,地址相同
System.out.println(s1.equals(s2)); // true,内容相同
三、hashCode() 和 equals() 方法有什么关系?
核心回答(哈希契约,面试必背)
hashCode() 和 equals() 是Object类的两个核心方法,必须遵守以下契约 ,否则会导致哈希表(如HashMap、HashSet)工作异常:
- 如果两个对象
equals()相等,那么它们的hashCode()必须相等 - 如果两个对象
hashCode()相等,equals()不一定相等(哈希碰撞) - 重写
equals()时,必须重写hashCode(),否则违反契约
原理说明
hashCode()用于哈希表快速定位对象存储位置,equals()用于最终确认对象是否相等- 哈希表的查找流程:先通过
hashCode()定位桶位置,再用equals()在桶内遍历匹配 - 若只重写
equals()不重写hashCode(),两个内容相等的对象会有不同哈希码,导致HashSet认为是两个不同对象,存入重复数据
代码示例(正确重写)
java
public class User {
private String name;
private int age;
// 重写equals:比较内容
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
User user = (User) o;
return age == user.age && Objects.equals(name, user.name);
}
// 必须同时重写hashCode
@Override
public int hashCode() {
return Objects.hash(name, age);
}
}
四、Java里String的常用方法有哪些?
核心回答(按功能分类,高频常用)
1. 长度/判空
length():返回字符串长度isEmpty():判断字符串是否为空(长度为0)isBlank()(Java 11+):判断字符串是否为空或仅含空白字符
2. 查找/获取
charAt(int index):获取指定索引的字符indexOf(String str):返回子字符串第一次出现的索引,未找到返回-1lastIndexOf(String str):返回子字符串最后一次出现的索引substring(int beginIndex)/substring(int beginIndex, int endIndex):截取子字符串(左闭右开)
3. 转换/格式化
toLowerCase()/toUpperCase():转小写/大写trim():去除字符串首尾空白字符(Java 11+推荐strip(),支持Unicode空白)split(String regex):按正则表达式分割字符串replace(char oldChar, char newChar)/replaceAll(String regex, String replacement):替换字符/字符串valueOf(xxx):将其他类型转换为字符串(静态方法)
4. 比较/判断
equals(Object obj):比较字符串内容(区分大小写)equalsIgnoreCase(String anotherString):比较内容(不区分大小写)contains(CharSequence s):判断是否包含指定子字符串startsWith(String prefix)/endsWith(String suffix):判断是否以指定前缀/后缀开头/结尾
5. 拼接
concat(String str):拼接字符串(比+效率高,底层直接数组复制)join(CharSequence delimiter, CharSequence... elements)(Java 8+):用分隔符拼接多个字符串(静态方法)
五、String、StringBuffer、StringBuilder的区别和联系
核心回答(表格对比+原理)
1. 核心区别
| 对比维度 | String | StringBuffer | StringBuilder |
|---|---|---|---|
| 可变性 | 不可变(final修饰的char数组,Java9+为byte数组) | 可变(可变数组,无final) | 可变(可变数组,无final) |
| 线程安全 | 安全(不可变,无并发修改) | 安全(方法加synchronized修饰) |
不安全(无锁) |
| 性能 | 最低(每次修改都创建新对象) | 中等(有锁开销) | 最高(无锁,直接修改数组) |
| 适用场景 | 少量字符串操作、常量 | 多线程环境下大量字符串操作 | 单线程环境下大量字符串操作 |
| 继承结构 | 实现Serializable、Comparable、CharSequence |
继承AbstractStringBuilder,实现CharSequence |
继承AbstractStringBuilder,实现CharSequence |
2. 核心原理
- String不可变的原因 :底层存储用
private final byte[] value(Java 9+),final保证数组引用不可变,且无对外修改数组的方法,因此每次修改(如concat、substring)都会创建新的String对象,避免并发修改问题。 - StringBuffer/StringBuilder可变的原因 :继承
AbstractStringBuilder,底层用byte[] value(无final),可直接修改数组内容,无需创建新对象,性能更高。 - 线程安全的区别 :StringBuffer的所有公共方法都加了
synchronized,保证多线程安全,但带来锁开销;StringBuilder无锁,单线程下性能是StringBuffer的1.5~2倍。
3. 联系
- 三者都实现了
CharSequence接口,都表示字符序列 - 都可以通过
toString()转换为String对象 - 核心操作(如
append、insert、delete)逻辑一致,仅线程安全和性能不同
代码示例(性能对比)
java
// 单线程大量拼接:StringBuilder最快
long start = System.currentTimeMillis();
StringBuilder sb = new StringBuilder();
for (int i = 0; i < 100000; i++) {
sb.append("a");
}
long end = System.currentTimeMillis();
System.out.println("StringBuilder耗时:" + (end - start) + "ms"); // 约1ms
// StringBuffer次之
start = System.currentTimeMillis();
StringBuffer sbf = new StringBuffer();
for (int i = 0; i < 100000; i++) {
sbf.append("a");
}
end = System.currentTimeMillis();
System.out.println("StringBuffer耗时:" + (end - start) + "ms"); // 约2ms
// String最慢
start = System.currentTimeMillis();
String str = "";
for (int i = 0; i < 100000; i++) {
str += "a"; // 每次创建新对象,触发GC
}
end = System.currentTimeMillis();
System.out.println("String耗时:" + (end - start) + "ms"); // 约1000ms+
补充考点
- String的常量池 :直接赋值的字符串会存入字符串常量池,复用对象;
new String()会在堆中创建新对象,同时在常量池生成副本(Java 7+常量池移到堆中) - StringBuilder的初始容量 :默认初始容量16,当数组长度不足时,会按
新容量 = 旧容量*2 + 2扩容,可通过new StringBuilder(1000)指定初始容量,避免频繁扩容 - Java 9+的优化 :String、StringBuffer、StringBuilder底层从
char[]改为byte[]+ 编码标记,节省50%内存(Latin1字符仅占1字节)
六、面试答题技巧
- Object类方法 :先分类讲11个方法,重点讲
equals、hashCode、wait/notify,体现对线程和哈希表的理解 ==与equals:分基本类型和引用类型,结合String常量池示例,讲清地址和内容的区别hashCode与equals契约 :强调"重写equals必须重写hashCode",结合HashMap的底层原理- String常用方法:按功能分类,避免零散罗列,重点讲高频方法的使用场景
- String三兄弟区别:用表格对比,讲清可变性、线程安全、性能的核心差异,结合代码示例