02、对象和对象引用
可能有些读者看完上面的图文分析没有理解反而更疑惑了:alita 不是变了吗?从“阿丽塔”变为“战斗天使阿丽塔”?怎么还说字符串是不可变的呢?
这里需要给大家解释一下,什么是对象,什么是对象引用。
在 Java 中,由于不能直接操作对象本身,所以就有了对象引用这个概念,对象引用存储的是对象在内存中的地址。
PS:Java 虚拟机在执行程序的过程中会把内存区域划分为若干个不同的数据区域,如下图所示。
对象存储在堆(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 技术栈相关的文章,敬请期待。如果你有什么问题需要我的帮助,或者想喷我了,欢迎留言哟。
养成好习惯!如果觉得这篇文章有点用的话,求点赞、求关注、求分享、求留言,这将是我写下去的最强动力!如果大家想要第一时间看到二哥更新的文章,可以扫描下方的二维码,关注我的公众号。我们下篇文章见!