likes
comments
collection
share

String不可变性

作者站长头像
站长
· 阅读数 44

什么是不可变类?

这样理解: 一个对象在创建完成后,不能去改变它的状态,不能改变它的成员变量(如果成员变量包含基本数据类型,那么这个基本数据类型的值不能改变;如果包含引用类型,那么这个引用类型的变量不能指向别的对象)

String类

源码

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // value 数组被 final 修饰
    private final char value[];
    ...
}

我们可以看到有两点:

  • String是一个final类它不能被其他类继承
  • String类存放字符串内容是因为它具有char类型的value[]属性,被 final 修饰,表示字符串一旦被创建,就不可修改

final修饰引用类型,它保存的仅仅是一个引用,final只保证这个引用类型变量所引用的地址不会改变,也就是一直引用同一个对象,但这个对象完全可以发生改变。

拿下面的例子来说,char类型的数组被final修饰,表示value不可指向新的数组但是当前数组里面的值是可变的。

String不可变性

可以看出修改数组中的值是可以的,但是让当前变量指向新数组时会报错

String内存图

字面量赋值

String str="Hello";

String不可变性

先从常量池查看是否有"Hello"数据空间,如果有则直接指向;如果没有则重新创建,然后指向str最终指向的是常量池的空间地址**【jdk1.7以及之后版本字符串常量池在堆中,具体看这篇文章】**

通过构造器

String str2=new String("Hello");

String不可变性

先在堆中创建空间,里面具有value属性,指向常量池的"Hello"空间。如果常量池没有"Hello",重新创建,如果有则直接通过value指向。最终指向的是堆中的空间地址。 根据以上的创建过程,不难看出方式一是直接指向常量池中的字符串常量,方式二则是先指向堆中,然后根据堆中的地址指向常量池中的字符串常量

String不可变的本质

我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。 String 真正不可变有下面几点原因: 

  1. 保存字符串的数组被 final 修饰且为私有的,并且String 类没有提供/暴露修改这个字符串的方法。
  2.  String 类被 final 修饰导致其不能被继承,进而避免了子类破坏 String 不可变

解惑

这时就有人疑惑了:为什么 String 不可变?但我的代码中经常改变 String 啊,如下:

String str = "HELLO";
str = "WORLD";
System.out.println(str);    // WORLD

虽然字符串的内容看上去从“HELLO” 变成了“WORLD”,但实际上,这已经是生成了一个新的字符串了: 

String str = "HELLO";
System.out.println(str.hashCode());  // 68624562
str = "WORLD";
System.out.println(str.hashCode());  // 82781042 

变量 str 前后的 hashCode 值不一样,说明了 str 在改变前后,指向了不同的对象。所以,变量 str 只是指向了不同对象,字符串 “HELLO”对象本身没有被改变。

参考文章:

从JVM理解String、StringBuffer类

java String类

java StringBuilder 和 StringBuffer 万字详解

Java学习 -- String、StringBuffer、StringBuilder

转载自:https://juejin.cn/post/7242720768885997623
评论
请登录