【Java基础 | 第六篇】Object 和 String

目录

Object

Object 类的常用方法

== 与 equals 的区别

hashcode 和 equals 方法的关系

重写equals()时须重写hashCode()方法

hashCode的意义

String

String、StringBuffer、StringBuilder的对比

String 是不可变的

字符串拼接用“+” 还是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. 线程安全性

  1. StringBuffer:所有修改字符串的方法(如 append()insert())都被 synchronized 关键字修饰,多线程环境下操作不会出现数据错乱,线程安全,但同步锁会带来性能开销。

  2. StringBuilder:方法无 synchronized 修饰,线程安全,但避免了同步开销,单线程下性能比 StringBuffer 更高。

  3. String:因不可变,天然线程安全(无修改操作,多线程读取不会有冲突)。

3. 性能

  性能排序:StringBuilder > StringBuffer > String(字符串频繁拼接场景)。

  1. String:频繁拼接时会创建大量临时对象,触发 GC(垃圾回收),性能最差;

  2. StringBuffer:因同步锁的开销,性能略低于 StringBuilder;

  3. StringBuilder:无同步锁,单线程下是拼接字符串的最优选择。

总结:String 是不可变字符串,频繁拼接性能差,适合内容固定的场景;StringBuffer 是可变、线程安全的,多线程拼接用;StringBuilder 是可变、非线程安全的,单线程拼接性能最优,是日常开发首选。

String 是不可变的

  1. 底层存储字符的数组被 final 修饰且私有化;

  2. 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 会分两步执行:

  1. 第一步:扫描字符串常量池,发现没有 "abc",则先在常量池中创建一个字符串对象 "abc"

  2. 第二步:执行 new String(),在堆内存中创建一个 String 对象,该对象的底层 value 数组指向常量池 "abc" 的字符数组(JDK8 及以前);

  3. 最终:s1 引用的是堆内存的 String 对象,总共创建 2 个对象。

场景 2:常量池已有 "abc"(非首次出现)

  如果之前已有代码(如 String s = "abc";)在常量池创建了 "abc",执行 new String("abc") 时:

  1. 扫描常量池,发现已有 "abc",跳过常量池对象创建;

  2. 仅在堆内存创建 1 个 String 对象;

  3. 最终:总共创建 1 个对象。

分两种情况:如果字符串常量池里没有 "abc",创建 2 个对象(常量池 1 个、堆 1 个);如果常量池已有 "abc",只创建 1 个堆对象。开发中建议用字面量 "abc" 而非 new String,减少堆对象创建。


上述内容也同步在我的飞书,欢迎访问

https://my.feishu.cn/wiki/QLauws6lWif1pnkhB8IcAvkhncc?from=from_copylink

如果我的内容对你有帮助,请点赞,评论,收藏。创作不易,你们的支持就是我坚持下去的动力!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值