作者:明明如月学长, CSDN 博客专家,蚂蚁集团高级 Java 工程师,《性能优化方法论》作者、《解锁大厂思维:剖析《阿里巴巴Java开发手册》》、《再学经典:《EffectiveJava》独家解析》专栏作者。

热门文章推荐

  • (1)《人工智能时代,软件工程师们将会被取代?》
  • (2)《超全人工智能 AI工具导航网站合集》
  • (3)《如何写出高质量的文章:从战略到战术》
  • (4)《我的技术学习方法论》
  • (5)《什么? 你还没用过 Cursor? 智能 AI 代码生成工具 Cursor 安装和使用介绍》
  • (6)《我的性能方法论》
  • (7)《AI 时代的学习方式: 和文档对话》
  • (8)《人工智能终端来了,你还在用过时的 iterm?》
  • (9)《无需魔法打开即用的 AI 工具集锦》

一、背景

字符串的不可变性可以说是面试中的一个常见的“简单的” 问题。

常见的回答如:

字符串创建后不可改变。

字符串的不可变性是指字符串的字符不可变。

String 的 value 字符数组声明为 final 保证不可变。

真的是这样吗?

下面我们再思考两个问题:

  • 那么字符串的不可变究竟是指什么?
  • 是如何保证的呢?

下面看一个奇怪的现象:在程序一段程序的最后执行下面的语句居然打印了 “aw” 为什么?

// 前面代码省略
System.out.println("ab");

建议大家先思考,然后再看下文。

二、案例

你认为下面的示例代码的输出结果是啥?

import java.lang.reflect.Field;public class Test {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {String str = "ab";System.out.println("str=" + str);Class stringClass = str.getClass();Field field = stringClass.getDeclaredField("value");field.setAccessible(true);char[] value = (char[]) field.get(str);value[1] = 'w';System.out.println("str=" + str );}
}

输出结果为:

str=ab
str=aw

是不是和有些同学想的有些不一样呢?

字符串的字符数组可以通过反射进行修改,导致字符串的“内容”发生了变化。

我们再多打印一些:

import java.lang.reflect.Field;public class Test {public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {String str = "ab";System.out.println("str=" + str + "," + System.identityHashCode(str)+","+ str.hashCode());Class stringClass = str.getClass();Field field = stringClass.getDeclaredField("value");field.setAccessible(true);char[] value = (char[]) field.get(str);value[1] = 'w';System.out.println("str=" + str + "," + System.identityHashCode(str)+","+ str.hashCode());}
}

输出结果为:

str=ab,1638215613,3105
str=aw,1638215613,3105

通过这个例子我们可以看出,String 字符串对象的 value 数组的元素是可以被修改的。

简单看下 java.lang.System#identityHashCode 的源码:

    /*** Returns the same hash code for the given object as* would be returned by the default method hashCode(),* whether or not the given object's class overrides* hashCode().* The hash code for the null reference is zero.** @param x object for which the hashCode is to be calculated* @return  the hashCode* @since   JDK1.1*/public static native int identityHashCode(Object x);

native 方法,该函数给出对象唯一的哈希值(不管是否重写了 hashCode 方法)。

可知,对象没有变。

那么,我们知道 String 的哈希值是通过字符串的字符数组计算得来的(JDK8),那为啥两次 hashCode 函数返回值一样呢?

我们再仔细看下 java.lang.String#hashCode 源码:

public final class Stringimplements java.io.Serializable, Comparable<String>, CharSequence {/** The value is used for character storage. */private final char value[];/** 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;}//省略其他
}

发现在第一次调用 hashCode 函数之后,字符串对象内通过 hash 这个属性缓存了 hashCode的计算结果(只要缓存过了就不会再重新计算),因此第二次和第一次相同。

那么如何保证不可变性的呢?

首先将 String 类声明为 fianl 保证不可继承。

然后,所有修改的方法都返回新的字符串对象,保证修改时不会改变原始对象的引用。

    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);}

其次,字符串字面量都会指向同一个对象。

  public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {// 字符串字面量String str = "ab";System.out.println("str=" + str + "," + System.identityHashCode(str)+","+ str.hashCode());Class stringClass = str.getClass();Field field = stringClass.getDeclaredField("value");field.setAccessible(true);char[] value = (char[]) field.get(str);value[1] = 'w';System.out.println("str=" + str + "," + System.identityHashCode(str)+","+ str.hashCode());// 字符串字面量System.out.println("ab");}

可以看到打印结果为:

str=ab,1638215613,3105
str=aw,1638215613,3105
aw

很多人不理解,为啥 System.out.println("ab"); 打印 aw ?

是因为字符串字面量都指向字符串池中的同一个字符串对象(本质是池化的思想,通过复用来减少资源占用来提高性能)。

https://docs.oracle.com/javase/specs/jls/se8/html/jls-3.html#jls-3.10.5

A string literal is a reference to an instance of class String (§4.3.1, §4.3.3).

字符串字面量是指向字符串实例的一个引用。

Moreover, a string literal always refers to the same instance of class String. This is because string literals - or, more generally, strings that are the values of constant expressions (§15.28) - are “interned” so as to share unique instances, using the method String.intern.

字符串字面量都指向同一个字符串实例。
因为字面量字符串都是常量表达式的值,都通过String.intern共享唯一实例。

    /*** Returns a canonical representation for the string object.* <p>* A pool of strings, initially empty, is maintained privately by the* class {@code String}.* <p>* When the intern method is invoked, if the pool already contains a* string equal to this {@code String} object as determined by* the {@link #equals(Object)} method, then the string from the pool is* returned. Otherwise, this {@code String} object is added to the* pool and a reference to this {@code String} object is returned.* <p>* It follows that for any two strings {@code s} and {@code t},* {@code s.intern() == t.intern()} is {@code true}* if and only if {@code s.equals(t)} is {@code true}.* <p>* All literal strings and string-valued constant expressions are* interned. String literals are defined in section 3.10.5 of the* <cite>The Java&trade; Language Specification</cite>.** @return  a string that has the same contents as this string, but is*          guaranteed to be from a pool of unique strings.*/public native String intern();

对象池中存在,则直接指向对象池中的字符串对象,否则创建字符串对象放到对象池中并指向该对象。

因此可以看出,字符串的不可变性是指引用的不可变。

虽然 String 中的 value 字符数组声明为 final,但是这个 final 仅仅是让 value的引用不可变,而不是为了让字符数组的字符不可替换。

由于开始的 ab 和最后的 ab 属于字面量,指向同一个字符串池中的同一个对象,因此对象的属性修改,两个地方打印都会受到影响。

三、思考

很多简单的问题并没有看起来那么简单。

大家在看技术博客,在读源码的时候,一定要有自己的思考,多问几个为什么,有机会多动手实践。

大家在学习某个技术时要养成本质思维,即思考问题的本质是什么。

面试的时候,简单的问题要回答全面又有深度,不会的问题要回答出自己的思路,这样才会有更多的机会。


创作不易,如果本文对你有帮助,欢迎点赞、收藏加关注,你的支持和鼓励,是我创作的最大动力。

欢迎加入我的知识星球,知识星球ID:15165241 一起交流学习。
https://t.zsxq.com/Z3bAiea 申请时标注来自CSDN。

欢迎加入我们的 slack 工作区,在里面可以对ai 和我进行提问。
https://join.slack.com/t/ai-yx51081/shared_invite/zt-1t8cp1lk3-ZMAFutZcN3PCW~8WQDGjPg

你真的理解Java 字符串的不可变性吗?相关推荐

  1. [转载] Java内存管理-你真的理解Java中的数据类型吗(十)

    参考链接: Java中的字符串类String 1 做一个积极的人 编码.改bug.提升自己 我有一个乐园,面向编程,春暖花开! 推荐阅读 第一季 0.Java的线程安全.单例模式.JVM内存结构等知识 ...

  2. 理解Java字符串常量池与intern()方法

    理解Java字符串常量池与intern()方法 阅读目录 Java内存区域 两种创建方式在内存中的区别 解释开头的例子 intern()方法 参考资料 String s1 = "Hello& ...

  3. Java 字符串的不可变性

    String在Java中特别常用,而且我们经常要在代码中对字符串进行赋值和改变他的值,但是,为什么我们说字符串是不可变的呢? 首先,我们需要知道什么是不可变对象? 不可变对象是在完全创建后其内部状态保 ...

  4. Java字符串的不可变性

    源码 在IDEA中找到,String的源码 // Java 11 public final class String implements Serializable, Comparable<St ...

  5. 你真的理解Java的按引用传递吗?

    首先我们来看下面这段代码: public class Test1 { String a = "123"; public static void change(Test1 test) ...

  6. java 不可变性_Java字符串的不可变性

    简明现代魔法 -> Java编程语言 -> Java字符串的不可变性 Java字符串的不可变性 2009-11-08 String 对象是不可变的. 看似修改了 String 对象的方法, ...

  7. 你有真正理解 Java 的类加载机制吗?| 原力计划

    作者 | 宜春 责编 | Elle 出品 | CSDN 博客 你是否真的理解Java的类加载机制?点进文章的盆友不如先来做一道非常常见的面试题,如果你能做出来,可能你早已掌握并理解了Java的类加载机 ...

  8. 深入理解字符串的不可变性[java]

    深入理解字符串的不可变性 /* 这篇文章是我认为写的最好的一篇文章,只要认真去看,我相信你一定会有收获 */ //只要你把其中的例题做对,也就代表你掌握了 首先,我们要探讨字符串的不可变性,那么究竟什 ...

  9. 你真的了解java编译优化吗?15个问题考察自己是否理解

    [摘要] 早期编译过程 晚期编译优化 jvm编译优化学习笔记 早期 第一步: -------词法分析: -------语法分析(注意实际上只是生成一个语法树,还没做语法的校验): -------填充符 ...

最新文章

  1. 理解神经网络,从简单的例子开始(1)7行python代码构建神经网络
  2. struts2拦截器的实现原理及源码剖析
  3. 08_传智播客iOS视频教程_Foundation框架
  4. C语言递归遍历一棵二叉树(附完整源码)
  5. python网络套接字_Python网络编程 Python套接字编程
  6. linux产生随机数方法
  7. 怎样在ArcIMS 上实现专题图
  8. 【英语学习】【WOTD】purview 释义/词源/示例
  9. Map(String ArrayList(Student))相关操作和遍历和利用Map(k v)统计字母出现次数
  10. c语言冒泡排序的两种实现方式,c语言中冒泡排序的实现原理是什么?
  11. ssh框架 验证码实现
  12. Linux之——udp端口测试连接
  13. 2019年9月全国计算机二级Office题库软件
  14. mysql-front服务器_mysql-front远程连接自己linux服务器上的mysql服务器
  15. 大白菜u盘装win10步骤图解
  16. Mysql中查询连续一段时间内统计数据
  17. 感知机算法(一)---原理
  18. unity 开发EasyAR发布IOS和安卓坑记录
  19. 微博指定日期舆情数据爬虫获取—基于中文金融词典(python)
  20. 海明校验码原理(详解)

热门文章

  1. 17. Thymeleaf 模板 的 使用 和 语法
  2. win10重置进度条不动了_win10重置卡在33%不动了怎么解决
  3. 渲染路径-实时渲染中常用的几种Rendering Path
  4. Python网络爬虫实例1:股票数据定向爬虫
  5. 实现前端项目自动化部署(webpack+nodejs)
  6. 华为畅享8plus停产了吗_华为畅享8和华为畅享8Plus有什么区别-太平洋IT百科
  7. 汇编语言:计算S =(8000 -(X*Y+ Z))/X
  8. 分布式日志系统Graylog、Loki及ELK的分析和对比
  9. 工作面试老大难 - 锁
  10. 前端三剑客 - JavaScript