专栏名称: 纯洁的微笑
分享微服务实践与Java技术干货、偶尔讲讲故事。在人工智能的时代,一起学习微服务架构演进和大数据治理。
目录
相关文章推荐
人力葵花  ·  人事规章制度(精细版).doc ·  昨天  
纪法指引  ·  【镜鉴】组织部长欧阳玉军,任上被查! ·  2 天前  
薪人薪事企小薪  ·  第一学历不好,求职中怎么弥补? ·  2 天前  
51好读  ›  专栏  ›  纯洁的微笑

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

纯洁的微笑  · 公众号  ·  · 2019-11-18 12:12

正文

点击蓝色“ Java极客技术 ”关注我哟

加个“ 星标 ”,一起快乐成长



这是 Java极客技术的第 251 篇原创文章

在逛 programcreek 的时候,发现了一些精妙绝伦的主题。比如说:为什么 Java 字符串是不可变的?像这类灵魂拷问的主题,非常值得深思。

对于绝大多数的初级程序员来说,往往停留在“知其然不知其所以然”的层面上——会用,但要说底层的原理,可就只能挠挠头双手一摊一张问号脸了。

很长一段时间内,我也一直处于这种层面上。导致的局面就是,我在挖一些高深点的技术方案时,往往束手无策;在读一些高深点的技术文章时,往往理解不了作者在说什么。

借此机会,我就和大家一起,对“为什么 Java 字符串是不可变的”进行一次深入地研究。注意了,准备打怪升级了!

01. 图文分析

来看下面这行代码。

String alita = "阿丽塔";

这行代码在字符串常量池中创建了一个内容为“阿丽塔”的对象,并将其赋值给了字符串变量 alita(存储的是字符串对象"阿丽塔"的引用)。如下图所示。

再来看下面这行代码。

String wanger = alita;

这行代码将字符串变量 alita 赋值给了字符串变量 wanger。这时候,wanger 和 alita 存储的是同一个字符串对象的引用。如下图所示。

再来看下面这行代码。

alita = "战斗天使".concat(alita);

这行代码将字符串“战斗天使”拼接在字符串变量 alita 的前面,并重新赋值给 alita。这个过程就比之前的复杂了。我们需要先来看看 concat() 方法做了什么,源码如下所示。

public String concat(String str) {
int otherLen = str.length();
if (otherLen == 0) {
return this;
}
int len = value.length;
char buf[] = Arrays.copyOf(value, len + otherLen);
str.getChars(buf, len);
return new String(buf, true);
}

可以看得出, "战斗天使".concat(alita) 这行代码会先在字符串常量池中创建一个新的字符串对象,内容为“战斗天使”,然后 concat() 方法会将其对应的字符数组和“阿丽塔”对应的字符数组复制到一个新的字符数组 buf 中,最后,再通过 new 关键字创建了一个新的字符串对象,并返回。如下图所示。

从上图中可以得出结论,alita 此时引用的是在堆中新创建的字符串对象。

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 关键字重新创建的新字符串对象。







请到「今天看啥」查看全文