前言

在 Java 开发中 String (字符串)对象是我们使用最频繁的对象,也是很重要的对象。正是使用得如此频繁,String 在实现层面上不断进行优化,从 Java6 到 Java7,再到 Java9 的新实现 ,都是为了提升 String 对象的性能,而其中不变的是 String 所生俱来的特性:不可变。本文主要聊一聊 String 的不可变,以及为什么存在的。

什么是 String 的不可变

首先我们先来看下什么是不可变对象:一旦对象被创建并初始化后,内部的状态数据就会保持不变。查看 JDK 源码中的 String 类,可以看到类本身被 final 修饰,并且内部的大部分属性都是 final 修饰的,除了字段 hash 是通过字符串内容计算并缓存起来的。这样的行为让 String 类无法被扩展,内部属性也无法被修改。

接着我们再来用画图的形式来说明下 String 的不可变性。

通常我们初始化字符串都是以下形式:

String 类型的引用变量 a 保留了一个字符串对象 string 的引用,就如同下图所示,箭头则表示了变量 a 与真正 String 对象的引用关系。

再通过上述代码,我们将变量 a 赋值给变量 b ,变量 b 也存储了字符串对象 string的引用,它们指向的是同一个对象。

当我们尝试对变量 a 重新赋值,看下对变量 b 会不会有影响呢

想必小伙伴一看就知道,打印的结果肯定是 string2,string,同样用画图的方式展示这两个变量与字符串对象的引用关系。

将变量 a 重新赋值后,保存了新的引用,而不是直接在原有的字符串对象上进行数据改变,同时变量 b 仍然存的是对象 string 的引用,变量 ab 两者相互独立,不影响,这也正是说明了 String 对象的不可变。

在这里初认 Java 的小伙伴还可能会有些困惑:对一个String对象 a 赋值 string,然后又让 a 值为 string2,这个时候a的值变成 了string2, a 的值改变了,为什么还说 String 对象不可变呢。

其实问题也很简单,这里的 a 只是存储 String 对象的引用,并不是对象本身,a 存储的是指向对象所在内存的地址引用罢了,当第二次赋值时,a 引用指向了对象 string2的内存地址,而对象 string2 是重新创建的,之前的 string 对象仍在内存中,并且由变量 b 引用着。

除此之外,String 类的返回 String 对象的方法不会改变自身,都是返回一个新的 String 对象来实现,比如 concatreplacesubstring 等等。

为什么 String 需要不可变

聊完什么是 String 的不可变后,接下来我们再说说 String 为什么需要不可变呢,又有什么好处呢?

字符串常量池的实现

在Java中,我们通常有两种方式创建字符串对象,一种是通过字符串字面量方式创建,就如上文的代码,另外一种就是通过 new 方式去创建,如 String c = new String("string 3"); 而两者区别就在于通过字符串字面量的方式创建时,JVM 会现在字符串池中检查字符串内容是否已经存在,如果存在就会直接返回对应的引用,而不是再次分配内存进行创建,如果不存在就会分配在内存中创建的同时将字符串数据缓存在字符串池中,便于重用。正是是由于字符串的不可变,同样的字符串内容可以让 JVM 可以减少额外的内存分配操作,直接使用在字符串池中字符串对象即可,对性能提升和内存节省都大有好处。

关于字符串池,这里稍微简单介绍一下:Java 的字符串池属于 JVM 专门给指定的特殊内存区域,用来存储字符串字面量。在 Java 7 之前,分配于 JVM 的方法区内,属于常量池的一部分;而 Java7 之后字符串池被移至堆内存进行管理,这样的好处就是允许被 JVM 进行垃圾回收操作,将未被引用的字符串所占内存即使回收,以此节省内存。

Hashcode 缓存

字符串作为基础的数据结构,大量地应用在一些集合容器之中,尤其是一些散列集合,在散列集合中,存放元素都要根据对象的 hashCode() 方法来确定元素的位置。由于字符串 hashcode 属性不会变更,保证了唯一性,使得类似 HashMap,HashSet 等容器才能实现相应的缓存功能。由于 String 的不可变,避免重复计算 hashcode,只有使用缓存的 hashcode 即可,这样一来大大提高了在散列集合中使用 String 对象的性能。

线程安全

在多线程中,只有不变的对象和值是线程安全的,可以在多个线程中共享数据。由于 String 天然的不可变,当一个线程”修改“了字符串的值,只会产生一个新的字符串对象,不会对其他线程的访问产生副作用,访问的都是同样的字符串数据,不需要任何同步操作。

安全性

由于字符串无论在任何 Java 系统中都广泛使用,会用来存储敏感信息,如账号,密码,网络路径,文件处理等场景里,保证字符串 String 类的安全性就尤为重要了,如果字符串是可变的,容易被篡改,那我们就无法保证使用字符串进行操作时,它是安全的,很有可能出现 SQL 注入,访问危险文件等操作。

结语

通过本文,我们介绍 String 是不可变的,可以将它们的引用可以被当作一个普通的变量来使用,无论是在方法间,还是线程间传递它们,都不用担心它指向的实际 String 对象发生改变,并且不可变的特性也在语言层面和程序层面上带了许多好处,在平常编程实践中我们也应该多学习效仿,用 James Gosling,Java之父的话说就是”我会尽可能地使用不可变对象“。

推荐阅读

参考资料

聊一聊Java字符串的不可变的更多相关文章

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

    在逛 programcreek 的时候,发现了一些精妙绝伦的主题.比如说:为什么 Java 字符串是不可变的?像这类灵魂拷问的主题,非常值得深思. 对于绝大多数的初级程序员来说,往往停留在" ...

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

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

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

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

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

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

  5. 为什么java String是固定的 为什么字符串是不可变的

    String类不可变的好处 String是所有语言中最常用的一个类.我们知道在Java中,String是不可变的.final的.Java在运行时也保存了一个字符串池(String pool),这使得S ...

  6. Java String字符串的不可变

    Java 通过把String类设计为final使类不可继承,将变量value设置为private并且是final的,且value没有setter方法,不可修改. 为什么这么设计: 1.字符串常量池的需 ...

  7. Java字符串中常见的10个问题

    下面是Java中10个最常见的关于字符串的问题. 怎样比较字符串?使用==还是equals() 简单的说,“==”用于判断引用是否相等,equals()用于判断值是否相等.除非你要比较两个字符串是否是 ...

  8. Java字符串的10大热点问题,你都懂吗?

    转自 威哥干JAVA http://www.codingke.com 下面我为大家总结了10条Java开发者经常会提的关于Java字符串的问题,如果你也是Java初学者,仔细看看吧: 1.如何比较字符 ...

  9. [译]关于Java 字符串最常被问到的十个问题

    (说明,该文章翻译自Top 10 questions of Java Strings) 下面是关于Java字符串最常被问到的十个问题 1.怎么去比较字符串?使用==还是使用equals()? 简单来说 ...

随机推荐

  1. 工作经验之石氏thinking

    经常听到N多人说工作经验这个名词:也时常听到人说工作多少年就是多少年工作经验.我听着总觉得有点别扭,感觉他们把这个名词说的太简单了,而且觉得不是工作N年就一定有所谓的工作经验.我觉得归根结底还是在于工 ...

  2. 我的it博客开张啦

    今天怀着激动地心情,在这里写下第一篇开博.之前也在新浪.网易等申请过博客,并且将新浪博客作为我的个人技术博客,当有一天看到cnblog时,觉得这里的博客以一本精美的书的批复呈现时,顿觉得很有...咋说 ...

  3. 简易数据分析 06 | 如何导入别人已经写好的 Web Scraper 爬虫

    这是简易数据分析系列的第 6 篇文章. 上两期我们学习了如何通过 Web Scraper 批量抓取豆瓣电影 TOP250 的数据,内容都太干了,今天我们说些轻松的,讲讲 Web Scraper 如何导 ...

  4. 自实现input上传指定文件到服务器

    遇到问题,解决问题,记录问题,成长就是一步一步走出来的. 一.添加 input 标签 我的工作中遇到了,需要上传pdf文件到服务器的需求,而且只能上传pdf文件,accept指定了 pdf 类型. & ...

  5. 个人永久性免费-Excel催化剂功能第89波-批量多图片转PDF

    前一篇展示了从PDF中提取到有用信息如图片.文本.表格等功能,部分人可能对自己手中的转PDF格式的保护性有所顾虑,此篇从反向角度,提供数据保护作用,让PDF文件的数据保护更彻底,让文本型的PDF文件彻 ...

  6. 个人永久性免费-Excel催化剂功能第52波-相同内容批量合并单元格,取消合并单元格并填充内容

    在高级Excel用户群体中无比痛恨的合并单元格,在现实的表格中却阴魂不散的纠缠不断.今天Excel催化剂也来成为“帮凶”,制造更多的合并单元格.虽然开发出此功能,请使用过程中务必要保持节制,在可以称为 ...

  7. 个人永久性免费-Excel催化剂功能第38波-比Vlookup更好用的查找引用函数

    谈起Excel的函数,有一个函数生来自带明星光环,在表哥表姐群体中无人不知,介绍它的教程更是铺天盖地,此乃VLOOKUP函数也.今天Excel催化剂在这里冒着被火喷的风险,大胆地宣布一个比VLOOKU ...

  8. Python常用的标准库以及第三方库

    Python常用的标准库以及第三方库有哪些?   20个必不可少的Python库也是基本的第三方库 读者您好.今天我将介绍20个属于我常用工具的Python库,我相信你看完之后也会觉得离不开它们.他们 ...

  9. HttpServlet cannot be resolved to a type 解决办法

    刚开始学习Servlet,在Eclipse中新建了一个Servlet,不过页面上报错: Httpservlet cannot be resolved to a type,显然是Eclipse找不到相应 ...

  10. CIDR的介绍

    CIDR的介绍: CIDR(Classless Inter-Domain Routing,无类域间路由选择)它消除了传统的A类.B类和C类地址以及划分子网的概念,因而可以更加有效地分配IPv4的地址空 ...