目录
String、StringBuffer、StringBuilder的对比
String s1 = new String("abc");这句话创建的字符串对象数量
Object
Object 类的常用方法
/**
* native 方法,用于返回当前运行时对象的 Class 对象,使用了 final 关键字修饰,故不允许子类重写。
*/
public final native Class<?> getClass()
/**
* native 方法,用于返回对象的哈希码,主要使用在哈希表中,比如 JDK 中的HashMap。
*/
public native int hashCode()
/**
* 用于比较 2 个对象的内存地址是否相等,String 类对该方法进行了重写以用于比较字符串的值是否相等。
*/
public boolean equals(Object obj)
/**
* native 方法,用于创建并返回当前对象的一份拷贝。
*/
protected native Object clone() throws CloneNotSupportedException
/**
* 返回类的名字实例的哈希码的 16 进制的字符串。建议 Object 所有的子类都重写这个方法。
*/
public String toString()
/**
* native 方法,并且不能重写。唤醒一个在此对象监视器上等待的线程(监视器相当于就是锁的概念)。如果有多个线程在等待只会任意唤醒一个。
*/
public final native void notify()
/**
* native 方法,并且不能重写。跟 notify 一样,唯一的区别就是会唤醒在此对象监视器上等待的所有线程,而不是一个线程。
*/
public final native void notifyAll()
/**
* native方法,并且不能重写。暂停线程的执行。注意:sleep 方法没有释放锁,而 wait 方法释放了锁 ,timeout 是等待时间。
*/
public final native void wait(long timeout) throws InterruptedException
/**
* 多了 nanos 参数,这个参数表示额外时间(以纳秒为单位,范围是 0-999999)。 所以超时的时间还需要加上 nanos 纳秒。。
*/
public final void wait(long timeout, int nanos) throws InterruptedException
/**
* 跟之前的2个wait方法一样,只不过该方法一直等待,没有超时时间这个概念
*/
public final void wait() throws InterruptedException
/**
* 实例被垃圾回收器回收的时候触发的操作
*/
protected void finalize() throws Throwable { }
== 与 equals 的区别
| 符号 / 方法 | 核心作用 | 对比的本质 |
| == | 运算符(原生语法) | 1. 基本数据类型:对比值是否相等; 2. 引用数据类型:对比内存地址是否相等(是否指向同一个对象)。 |
| equals() | Object 类的方法(可重写) | 1. 未重写(如 Object 默认实现):等价于==,对比内存地址; 2. 重写后(如 String、Integer):对比对象的属性是否相等。 |
场景 1:未重写 equals()(如自定义类、Object 类)
class Person {
private String name;
public Person(String name) { this.name = name; }
}
// 测试
Person p1 = new Person("张三");
Person p2 = new Person("张三");
System.out.println(p1 == p2); // false(内存地址不同)
System.out.println(p1.equals(p2)); // false(Object默认equals等价于==)
场景 2:重写了 equals()(如 String、Integer、Date 等 JDK 内置类)
// String类重写了equals(),对比字符串内容
String s1 = new String("abc");
String s2 = new String("abc");
System.out.println(s1 == s2); // false(两个不同对象,地址不同)
System.out.println(s1.equals(s2)); // true(内容相同)
// Integer类(缓存池补充,面试加分)
Integer i1 = 127;
Integer i2 = 127;
System.out.println(i1 == i2); // true(缓存池复用对象,地址相同)
System.out.println(i1.equals(i2)); // true(值相同)
Integer i3 = 128;
Integer i4 = 128;
System.out.println(i3 == i4); // false(超出缓存池[-128,127],新建对象)
System.out.println(i3.equals(i4)); // true(值相同)
hashcode 和 equals 方法的关系
-
如果两个对象
equals()返回true,那么它们的hashCode()必须返回相同的值; -
如果两个对象的
hashCode()返回相同的值,它们的equals()不一定返回true(哈希碰撞); -
如果两个对象
equals()返回false,它们的hashCode()可以相同也可以不同(但建议不同,能提升哈希集合性能); -
重写
equals()时,必须重写hashCode(),否则会违反上述约定。
重写equals()时须重写hashCode()方法
对应上面的第一条。
因为两个相等的对象的 hashCode 值必须是相等。也就是说如果 equals 方法判断两个对象是相等的,那这两个对象的 hashCode 值也要相等。
如果重写 equals() 时没有重写 hashCode() 方法的话就可能会导致 equals 方法判断是相等的两个对象,hashCode 值却不相等。
hashCode的意义
以“HashSet 如何检查重复”为例子来说明为什么要有 hashCode?
先利用 hashCode() 将候选范围缩小到同一个桶内,再在桶内少量元素上调用 equals() 做精确判断,HashSet 大大减少了 equals() 的调用次数,从而提高了查找和插入的执行效率。
String
String、StringBuffer、StringBuilder的对比
| 类 | 核心特性 | 线程安全 | 底层实现 |
| String | 不可变字符串(final 修饰 char 数组) | - | final char[](JDK8)/ byte[](JDK9+) |
| StringBuffer | 可变字符串,支持动态拼接 | 线程安全 | char [](有 synchronized 修饰方法) |
| StringBuilder | 可变字符串,支持动态拼接 | 非线程安全 | char [](无同步锁) |
1. 不可变性 vs 可变性
1. String:底层存储字符的数组被 final 修饰,一旦创建,字符串内容无法修改。看似 “拼接” 的操作(如 s = s + "abc"),实际是创建了新的 String 对象,原对象不变:
String s = "a";
s += "b"; // 实际创建了新的 String 对象"ab",原"a"对象被回收
2. StringBuffer / StringBuilder:底层 char 数组无 final 修饰,支持直接修改数组内容,拼接、插入、删除等操作不会创建新对象(数组扩容时仅重新分配内存,而非创建新对象),效率远高于 String。
2. 线程安全性
-
StringBuffer:所有修改字符串的方法(如
append()、insert())都被synchronized关键字修饰,多线程环境下操作不会出现数据错乱,线程安全,但同步锁会带来性能开销。 -
StringBuilder:方法无
synchronized修饰,非线程安全,但避免了同步开销,单线程下性能比 StringBuffer 更高。 -
String:因不可变,天然线程安全(无修改操作,多线程读取不会有冲突)。
3. 性能
性能排序:StringBuilder > StringBuffer > String(字符串频繁拼接场景)。
-
String:频繁拼接时会创建大量临时对象,触发 GC(垃圾回收),性能最差; -
StringBuffer:因同步锁的开销,性能略低于 StringBuilder; -
StringBuilder:无同步锁,单线程下是拼接字符串的最优选择。
总结:
String是不可变字符串,频繁拼接性能差,适合内容固定的场景;StringBuffer是可变、线程安全的,多线程拼接用;StringBuilder是可变、非线程安全的,单线程拼接性能最优,是日常开发首选。
String 是不可变的
-
底层存储字符的数组被
final修饰且私有化; -
String 类本身被
final修饰,且没有提供修改字符数组的公开方法。最终表现为:String 对象一旦创建,其内容(字符序列)无法被修改。public final class String { // 1. 类被final修饰,无法被继承,避免子类修改不可变逻辑 // 2. 存储字符的数组被private + final修饰 private final char value[]; // 3. 哈希值缓存(不可变所以能缓存) private int hash; // 构造方法:初始化value数组,无任何修改value的公开方法 public String(String original) { this.value = original.value; this.hash = original.hash; } // 看似“修改”的方法(如substring、replace),实际返回新String对象 public String substring(int beginIndex) { // 底层是创建新String,复制原数组的部分内容,原value数组不变 return new String(Arrays.copyOfRange(value, beginIndex, value.length)); } }
字符串拼接用“+” 还是StringBuilder
| 拼接场景 | 推荐使用 | 原因 |
| 编译期确定的少量拼接 | “+” 运算符 | 编译器会自动优化为 StringBuilder,代码简洁且性能无损耗 |
| 运行期动态拼接(如循环) | StringBuilder | 避免 “+” 创建大量临时 String 对象,大幅提升性能 |
| 多线程环境动态拼接 | StringBuffer | 线程安全版的 StringBuilder(补充考点,体现考虑全面) |
编译期固定的少量拼接用 “+”,编译器会优化,代码更简洁;运行期动态拼接(比如循环)必须用 StringBuilder,避免创建大量临时对象,提升性能;多线程场景用 StringBuffer。
字符串常量池的作用
字符串常量池 是 JVM 为了提升性能和减少内存消耗针对字符串(String 类)专门开辟的一块区域,主要目的是为了避免字符串的重复创建。
// 1.在字符串常量池中查询字符串对象 "ab",如果没有则创建"ab"并放入字符串常量池
// 2.将字符串对象 "ab" 的引用赋值给 aa
String aa = "ab";
// 直接返回字符串常量池中字符串对象 "ab",赋值给引用 bb
String bb = "ab";
System.out.println(aa==bb); // true
String s1 = new String("abc");这句话创建的字符串对象数量
-
常量池无
"abc":创建 2 个对象(1 个在常量池,1 个在堆内存) -
常量池已有
"abc":创建 1 个对象(仅在堆内存创建new String()对象)
场景 1:常量池无 "abc"(首次出现)
执行 new String("abc") 时,JVM 会分两步执行:
-
第一步:扫描字符串常量池,发现没有
"abc",则先在常量池中创建一个字符串对象"abc"; -
第二步:执行
new String(),在堆内存中创建一个 String 对象,该对象的底层value数组指向常量池"abc"的字符数组(JDK8 及以前); -
最终:
s1引用的是堆内存的 String 对象,总共创建 2 个对象。
场景 2:常量池已有 "abc"(非首次出现)
如果之前已有代码(如 String s = "abc";)在常量池创建了 "abc",执行 new String("abc") 时:
-
扫描常量池,发现已有
"abc",跳过常量池对象创建; -
仅在堆内存创建 1 个 String 对象;
-
最终:总共创建 1 个对象。
分两种情况:如果字符串常量池里没有 "abc",创建 2 个对象(常量池 1 个、堆 1 个);如果常量池已有 "abc",只创建 1 个堆对象。开发中建议用字面量 "abc" 而非 new String,减少堆对象创建。
上述内容也同步在我的飞书,欢迎访问
https://my.feishu.cn/wiki/QLauws6lWif1pnkhB8IcAvkhncc?from=from_copylink
如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,你们的支持就是我坚持下去的动力!

2377

被折叠的 条评论
为什么被折叠?



