【面试】Java 之 String 系列 -- String 为什么不可变?

news/2025/2/27 6:55:34

        在 Java 编程中,String 类是一个使用频率极高的类。而 String 对象具有不可变的特性,这一特性在 Java 设计中有着重要的意义。本文将深入探讨 String 不可变的含义、原因以及带来的好处。

一、String 不可变的含义

1. 概念解释

所谓 String 不可变,指的是一旦一个 String 对象被创建,它的内容(即字符序列)就不能被改变。在 Java 里,String 类被设计为 final 类,并且其内部用于存储字符序列的 value 数组也是 private 和 final 的。以下是 String 类中部分相关代码:

java">public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    // 其他代码...
}

从代码中可以看出,value 数组被 final 修饰,这意味着一旦数组被初始化,其引用就不能再指向其他数组。而 private 修饰符则保证了外部无法直接访问和修改这个数组。

2. 示例说明

下面通过一个简单的示例来直观感受 String 的不可变性:

java">public class StringImmutabilityExample {
    public static void main(String[] args) {
        String str = "Hello";
        str = str + " World";
        System.out.println(str);
    }
}

就像下面这个图示一样: 

在这个例子中,我们可能会认为 str 的内容从 "Hello" 变成了 "Hello World"。但实际上,str 最初指向的 "Hello" 对象并没有改变,当执行 str = str + " World" 时,Java 会创建一个新的 String 对象 "Hello World",然后让 str 引用这个新对象,而原来的 "Hello" 对象仍然存在于内存中。

二、String 不可变的原因

1. 安全性

String 在 Java 中广泛用于存储敏感信息,如用户名、密码、数据库连接信息等。如果 String 是可变的,那么这些敏感信息就可能被恶意修改,从而引发安全问题。例如,在多线程环境下,如果一个线程正在使用一个 String 对象存储的密码进行验证,而另一个线程同时修改了这个 String 对象的内容,那么验证结果就会变得不可靠。

2. 缓存哈希码

String 类重写了 hashCode() 方法,并且 String 对象的哈希码是在对象创建时就计算好并缓存起来的。因为 String 不可变,所以其哈希码不会改变,这样在使用 String 作为哈希表(如 HashMapHashSet)的键时,就可以避免重复计算哈希码,提高了性能。以下是 String 类中 hashCode() 方法的部分代码:

java">public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    private int hash; // Default to 0

    public int hashCode() {
        int h = hash;
        if (h == 0 && value.length > 0) {
            char val[] = value;

            for (int i = 0; i < value.length; i++) {
                h = 31 * h + val[i];
            }
            hash = h;
        }
        return h;
    }

    // 其他代码...
}

可以看到,如果 String 可变,那么哈希码就可能会因为内容的改变而改变,这将破坏哈希表的正常工作。

3. 便于字符串常量池的实现

Java 中的字符串常量池是一种特殊的内存区域,用于存储字符串常量。当使用双引号声明一个字符串时,Java 会首先在字符串常量池中查找是否已经存在相同内容的字符串,如果存在,则直接返回该字符串的引用;如果不存在,则在常量池中创建一个新的字符串对象。String 的不可变性保证了常量池中的字符串可以被安全地共享,避免了因内容改变而导致的混乱。例如:

java">String str1 = "Hello";
String str2 = "Hello";
System.out.println(str1 == str2); // 输出 true

在这个例子中,str1 和 str2 都指向字符串常量池中的同一个 "Hello" 对象。

三、String 不可变带来的好处

1. 线程安全

由于 String 不可变,所以在多线程环境下,多个线程可以同时访问同一个 String 对象,而不需要担心数据被修改的问题。这使得 String 成为了线程安全的类,开发者可以放心地在多线程程序中使用 String 对象,无需额外的同步机制。

2. 性能优化

正如前面提到的,String 的不可变性使得哈希码可以被缓存,这在使用 String 作为哈希表的键时可以显著提高性能。此外,由于 String 对象可以在字符串常量池中共享,减少了内存的使用,也提高了垃圾回收的效率。

3. 代码可读性和可维护性

String 的不可变性使得代码更加易于理解和维护。因为开发者不需要担心 String 对象的内容会在程序运行过程中被意外修改,所以可以更专注于业务逻辑的实现。

四、利用反射改变 String 的字符数据

        虽然我不能不能改变字符串,但是我们可以修改字符串的字符,Java 的反射机制允许我们在运行时检查和修改类的属性、方法等。通过反射,我们可以绕过 private 修饰符的限制,访问并修改 String 对象内部的 value 数组。

以下是一个示例代码:

java">package org.example.a;

import java.lang.reflect.Field;

public class Demo {
    public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {
        String s = "1234";
        System.out.println("改变前:s=" + s);
        // 获取 String 类的 value 属性
        Field f = s.getClass().getDeclaredField("value");
        // 设置该属性可访问,绕过 private 限制
        f.setAccessible(true);
        // 修改 value 数组为新的字符数组
        f.set(s, new char[]{'a', 'b', 'c'});
        System.out.println("改变后:s=" + s);
    }
}

代码解释

  • Field f = s.getClass().getDeclaredField("value");:通过 getClass() 方法获取 s 对象的 Class 对象,然后使用 getDeclaredField("value") 方法获取 String 类中名为 value 的属性。
  • f.setAccessible(true);:将 value 属性的可访问性设置为 true,这样就可以绕过 private 修饰符的限制,对其进行访问和修改。
  • f.set(s, new char[]{'a', 'b', 'c'});:将 s 对象的 value 属性设置为新的字符数组 {'a', 'b', 'c'}

执行结果

改变前:s=1234
改变后:s=abc

从执行结果可以看出,我们成功地通过反射修改了 String 对象的字符数据。

3. 这种做法的风险和注意事项

虽然通过反射可以改变 String 的字符数据,但这种做法并不推荐在实际开发中使用,原因如下:

  • 破坏不可变性原则String 的不可变性是 Java 语言设计的重要特性之一,许多系统和库都依赖于这一特性。使用反射修改 String 会破坏这种不可变性,可能导致程序出现难以调试的错误。
  • 安全问题:如果恶意代码利用反射修改 String 对象,可能会导致安全漏洞,比如篡改敏感信息等。
  • 兼容性问题:不同的 Java 版本或虚拟机实现可能对反射操作的支持有所不同,使用反射修改 String 可能会导致兼容性问题。

http://www.niftyadmin.cn/n/5869687.html

相关文章

为什么办公电脑需要使用企业级杀毒软件?--火绒企业版V2.0

首先&#xff0c;办公电脑处在一个网络连接和数据传输频繁的环境中&#xff0c;员工经常会接收和发送电子邮件、浏览网页、下载文件等&#xff0c;因此存在着各种网络安全威胁的风险。 其次&#xff0c;企业办公电脑作为业务运营的关键枢纽&#xff0c;存储着大量商业机密、客…

【Java企业生态系统的演进】从单体J2EE到云原生微服务

Java企业生态系统的演进&#xff1a;从单体J2EE到云原生微服务 目录标题 Java企业生态系统的演进&#xff1a;从单体J2EE到云原生微服务摘要1. 引言2. 整体框架演进&#xff1a;从原始Java到Spring Cloud2.1 原始Java阶段&#xff08;1995-1999&#xff09;2.2 J2EE阶段&#x…

总结一下Java中的Synchronized同步锁的常见面试题

部分内容来源&#xff1a;JavaGuide Synchronized是什么&#xff1f;有什么用 Synchronized是同步的意思&#xff0c;主要解决多个线程之间访问资源的同步性&#xff0c;是一个同步锁 我们会真的把我们的资源给锁住 保证被他修饰的资源或代码块在任意时刻只能有一个线程来执…

Android系统使用ftrace查看Binder驱动日志

目录 一,打开开关 二&#xff0c;打开 signal trace&#xff1a; 三&#xff0c;设置缓冲大小 四&#xff0c;执行 cat /sys/kernel/debug/tracing/tracing_on echo 1 >/sys/kernel/debug/tracing/tracing_on 这一步很重要&#xff0c;不然在使用cat /sys/kernel/debug…

点云配准技术的演进与前沿探索:从传统算法到深度学习融合(1)

1、点云配准的基础理论 1.1 点云数据的特性与获取 点云数据是一种通过大量离散的三维坐标点来精确表示物体或场景表面几何形状和空间位置关系的数字化信息表达方式。在实际应用中&#xff0c;点云数据展现出诸多独特的特性。 从表达形式来看&#xff0c;点云数据能够直观地呈现…

解决uniapp二次打包的安卓APP安装到物理手机后,部分页面无法访问的问题

日常开发中&#xff0c;我们用uniapp开发安卓某个APP&#xff0c;新增页面&#xff0c;再次打包安装到物理手机后&#xff0c;我们发现新增的页面图标可以在原页面看到&#xff0c;但是点击后无法跳转到新的页面&#xff0c;只能重新卸载物理机的原打包APP,再次安装才能正常使用…

普中单片机-51TFT-LCD显示屏(1.8寸 STM32)

普中官方论坛&#xff1a; http://www.prechin.cn/gongsixinwen/208.html 普中科技-各型号开发板资料链接&#xff1a;https://www.bilibili.com/read/cv23681775/?spm_id_from333.999.0.0 27-TFTLCD显示实验_哔哩哔哩_bilibili 2.程序烧录 2.1设置彩屏驱动 3.实验效果

进入DeepSeek部署第一阵营后,奇墨科技推进多元应用场景落地

. 作为首个实现"超低部署成本全栈自主可控"的生成式AI解决方案&#xff0c;DeepSeek-R1的价值远超越技术参数本身。在应用场景落地博弈白热化的当下&#xff0c;其开创的私有化部署模式直击企业核心诉求&#xff1a;通过本地化部署架构&#xff0c;企业可将大模型深…