灵魂拷问:为什么 Java 字符串是不可变的?(2)

简介: 灵魂拷问:为什么 Java 字符串是不可变的?

02、对象和对象引用


可能有些读者看完上面的图文分析没有理解反而更疑惑了:alita 不是变了吗?从“阿丽塔”变为“战斗天使阿丽塔”?怎么还说字符串是不可变的呢?


这里需要给大家解释一下,什么是对象,什么是对象引用。


在 Java 中,由于不能直接操作对象本身,所以就有了对象引用这个概念,对象引用存储的是对象在内存中的地址。


PS:Java 虚拟机在执行程序的过程中会把内存区域划分为若干个不同的数据区域,如下图所示。


image.png


对象存储在堆(heap)中,而对象的引用存储在栈(stack)中。


我们通常所说的“字符串是不可变的”是指“字符串对象是不可变的”。alita 是字符串对象“阿丽塔”或者“战斗天使阿丽塔”的引用。这下应该明白了吧?


03、源码分析


我们来看一下 String 类的部分源码。


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



可以看得出, String 类其实是通过操作字符数组 value 实现的。而 value 是 private 的,也没有提供 serValue() 这样的方法进行修改;况且 value 还是 final 的,意味着 value 一旦被初始化,就无法进行改变。


另外呢,String 类提供的方法,比如说 substring():


public String substring(int beginIndex) {
    int subLen = value.length - beginIndex;
    return (beginIndex == 0) ? this : new String(value, beginIndex, subLen);
}
toLowerCase():
public String toLowerCase(Locale locale) {
    return new String(result, 0, len + resultOffset);
}


还有之前提到的 concat(),看似都能改变字符串的内容,但其实都是在方法内部使用 new 关键字重新创建的新字符串对象。


04、为什么要不可变


String 类的源码中还有一个重要的字段 hash,用来保存字符串对象的 hashCode。


public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** Cache the hash code for the string */
    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;
    }
}



因为字符串是不可变的,所以一旦被创建,它的 hash 值就不会再改变了。由此字符串非常适合作为 HashMap 的 key 值,这样可以极大地提高效率。


另外呢,不可变对象天生是线程安全的,因此字符串可以在多个线程之间共享。


举个反面的例子,假如字符串是可变的,那么数据库的用户名和密码(字符串形式获得数据库连接)将不再安全,一些高手可以随意篡改,从而导致严重的安全问题。


05、最后


总结一下,字符串一旦在内存中被创建,就无法被更改。String 类的所有方法都不会改变字符串本身,而是返回一个新的字符串对象。如果需要一个可修改的字符序列,建议使用 StringBuffer 或 StringBuilder 类代替 String 类,否则每次创建的字新符串对象会导致 Java 虚拟机花费大量的时间进行垃圾回收。


好了各位读者朋友们,以上就是本文的全部内容了。能看到这里的都是人才,二哥必须要为你点个赞👍。如果觉得不过瘾,还想看到更多,可以查看我的个人博客。另外呢,给大家一个承诺,我每周都会更新一篇《程序人生》和一篇 Java 技术栈相关的文章,敬请期待。如果你有什么问题需要我的帮助,或者想喷我了,欢迎留言哟。


养成好习惯!如果觉得这篇文章有点用的话,求点赞、求关注、求分享、求留言,这将是我写下去的最强动力!如果大家想要第一时间看到二哥更新的文章,可以扫描下方的二维码,关注我的公众号。我们下篇文章见!


相关文章
|
8天前
|
存储 安全 Java
Java灵魂拷问13个为什么,你都会哪些?
大家好,我是 V 哥。今天分享 13 个 Java 编程中的常见问题,包括 `BigDecimal` 的 `equals` 方法、`HashMap` 初始化容量、`Executors` 创建线程池等。这些问题都是 V 哥在日常编码中总结的经验,希望能帮助大家提升代码质量和性能。如果内容对你有帮助,请点赞关注,让我们在 Java 路上共同进步。
Java灵魂拷问13个为什么,你都会哪些?
|
18天前
|
存储 安全 Java
Java零基础-字符串详解
【10月更文挑战第18天】Java零基础教学篇,手把手实践教学!
95 60
|
3月前
|
安全 Java API
【Java字符串操作秘籍】StringBuffer与StringBuilder的终极对决!
【8月更文挑战第25天】在Java中处理字符串时,经常需要修改字符串,但由于`String`对象的不可变性,频繁修改会导致内存浪费和性能下降。为此,Java提供了`StringBuffer`和`StringBuilder`两个类来操作可变字符串序列。`StringBuffer`是线程安全的,适用于多线程环境,但性能略低;`StringBuilder`非线程安全,但在单线程环境中性能更优。两者基本用法相似,通过`append`等方法构建和修改字符串。
64 1
|
8天前
|
缓存 算法 Java
本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制
在现代软件开发中,性能优化至关重要。本文聚焦于Java内存管理与调优,介绍Java内存模型、内存泄漏检测与预防、高效字符串拼接、数据结构优化及垃圾回收机制。通过调整垃圾回收器参数、优化堆大小与布局、使用对象池和缓存技术,开发者可显著提升应用性能和稳定性。
29 6
|
8天前
|
存储 Java 程序员
Java灵魂拷问13个为什么,你都会哪些?
【11月更文挑战第6天】本文回答了一些常见的 Java“灵魂拷问”,包括 Java 跨平台的原因、垃圾回收机制的作用、接口不能有实例变量的原因、字符串不可变的好处、异常处理机制的意义、类加载机制的双亲委派模型、多线程同步机制的重要性、重写方法访问修饰符的限制、包装类的存在意义、`equals()` 和 `hashCode()` 方法一起重写的原因、静态方法不能被重写的原因、`ArrayList` 扩容策略的权衡,以及 `final` 关键字的多种用途。
|
1月前
|
Java 数据库
案例一:去掉数据库某列中的所有英文,利用java正则表达式去做,核心:去掉字符串中的英文
这篇文章介绍了如何使用Java正则表达式从数据库某列中去除所有英文字符。
46 15
|
1月前
|
Java
JAVA易错点详解(数据类型转换、字符串与运算符)
JAVA易错点详解(数据类型转换、字符串与运算符)
48 4
|
2月前
|
Java 数据库
java小工具util系列1:日期和字符串转换工具
java小工具util系列1:日期和字符串转换工具
50 3
|
2月前
|
SQL Java 索引
java小工具util系列2:字符串工具
java小工具util系列2:字符串工具
18 2