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

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 字符串是不可变的?的更多相关文章

  1. 灵魂拷问:Java对象的内存分配过程是如何保证线程安全的?(阿里面试)

    JVM内存结构,是很重要的知识,相信每一个静心准备过面试的程序员都可以清楚的把堆.栈.方法区等介绍的比较清楚. 上图,是一张在作者根据<Java虚拟机规范(Java SE 8)>中描述的J ...

  2. 灵魂拷问:Java 的 substring() 是如何工作的?

    在逛 programcreek 的时候,我发现了一些小而精悍的主题.比如说:Java 的 substring() 方法是如何工作的?像这类灵魂拷问的主题,非常值得深入地研究一下. 另外,我想要告诉大家 ...

  3. 为什么Java字符串是不可变对象?

    转自 http://developer.51cto.com/art/201503/468905.htm 本文主要来介绍一下Java中的不可变对象,以及Java中String类的不可变性,那么为什么Ja ...

  4. 聊一聊Java字符串的不可变

    前言 在 Java 开发中 String (字符串)对象是我们使用最频繁的对象,也是很重要的对象.正是使用得如此频繁,String 在实现层面上不断进行优化,从 Java6 到 Java7,再到 Ja ...

  5. 求求你,别问了,Java字符串是不可变的

    最近,又有好几个小伙伴问我这个问题:"二哥,为什么 Java 的 String 要设计成不可变的啊?"说实话,这也是一道非常经典的面试题,面试官超喜欢问.我之前写过这方面的文章,现 ...

  6. 灵魂拷问:Java如何获取数组和字符串的长度?length还是length()?

    限时 1 秒钟给出答案,来来来,听我口令:"Java 如何获取数组和字符串的长度?length 还是 length()?" 在逛 programcreek 的时候,我发现了上面这个 ...

  7. 为什么Java中的字符串是不可变的?

    原文链接:https://www.programcreek.com/2013/04/why-string-is-immutable-in-java/ java字符串是不可变的.不可变类只是一个不能修改 ...

  8. 灵魂拷问:创建 Java 字符串,用""还是构造函数

    在逛 programcreek 的时候,我发现了一些小而精悍的主题.比如说:创建 Java 字符串,用 "" 还是构造函数?像这类灵魂拷问的主题,非常值得深入地研究一下. 01.& ...

  9. 灵魂拷问:如何检查Java数组中是否包含某个值 ?

    在逛 programcreek 的时候,我发现了一些专注细节但价值连城的主题.比如说:如何检查Java数组中是否包含某个值 ?像这类灵魂拷问的主题,非常值得深入地研究一下. 另外,我想要告诉大家的是, ...

随机推荐

  1. C语言存储类别和链接

    目录 C语言存储类别和链接 存储类别 存储期 五种存储类别 C语言存储类别和链接 ​ 最近详细的复习C语言,看到存储类别的时候总感觉一些概念模糊不清,现在认真的梳理一下.C语言的优势之一能够让程序员恰 ...

  2. phpstudy 升级mysql到mysql5.7

    前言 今天在工作发现一个错误,在往本地导数据表的时候老是报错: ? 1 [Err] 1294 - Invalid ON UPDATE clause for '字段名' column 报错的数据表字段: ...

  3. 一个基于Net Core3.0的WPF框架Hello World实例

    目录 一个基于Net Core3.0的WPF框架Hello World实例 1.创建WPF解决方案 1.1 创建Net Core版本的WPF工程 1.2 指定项目名称,路径,解决方案名称 2. 依赖库 ...

  4. 详解PHP中的三大经典模式

    单例模式 单例模式的含义: 作为对象的创建模式,单例模式确保某一个类只有一个实例,而且自行实例化并向整个系统全局地提供这个实例.它不会创建实例副本,而是会向单例类内部存储的实例返回一个引用. 单例模式 ...

  5. python-nmap使用及案例

    nmap概念及功能 概念 NMap,也就是Network Mapper,最早是Linux下的网络扫描和嗅探工具包. nmap是一个网络连接端扫描软件,用来扫描网上电脑开放的网络连接端.确定哪些服务运行 ...

  6. Mysql常用数据类型归纳总结1

    一直在用Mysql数据库,Mysql的数据类型也最常打交道的.但关于Mysql的一些常用数据类型了解程度仅限于一知半解,仅仅能满足满足于平时一些最简单的操作.而Mysql常用数据类型的定义以及规范理解 ...

  7. pymssql默认关闭自动模式开启事务行为浅析

    使用Python采集SQL Server数据库服务器磁盘信息时,遇到了一个错误"CONFIG statement cannot be used inside a user transacti ...

  8. NOIP提高组/CSP-S复赛需掌握的算法

    1.排序算法(快排.选择.冒泡.堆排序.二叉排序树.桶排序) 2.DFS/BFS 也就是搜索算法,剪枝务必要学! 学宽搜的时候学一下哈希表! 3.树 ①遍历 ②二叉树 ③二叉排序树(查找.生成.删除) ...

  9. 子树问题(DP)

    这题显然是DP 首先,\(dp[i][j]\)表示树深度小于等于i,树的大小为j的有根树的数量$ 可以循环枚举根节点编号次大的子树的大小k. \(dp[i][j]=\sum^{j-1}_{k=1}dp ...

  10. 使用Typescript重构axios(六)——实现基础功能:获取响应数据

    0. 系列文章 1.使用Typescript重构axios(一)--写在最前面 2.使用Typescript重构axios(二)--项目起手,跑通流程 3.使用Typescript重构axios(三) ...