前言

在 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. py+selenium运行时报错Can not connect to the Service IEDriverServer.exe

    问题: 运行用例时,出现报错(host文件已加入127.0.0.1 localhost): raise WebDriverException("Can not connect to the ...

  2. JAVA面试题 StringBuffer和StringBuilder的区别,从源码角度分析?

    面试官Q1:请问StringBuffer和StringBuilder有什么区别? 这是一个老生常谈的话题,笔者前几年每次面试都会被问到,作为基础面试题,被问到的概率百分之八九十.下面我们从面试需要答到 ...

  3. Vue匿名组件使用keep-alive后动态清除缓存

    在使用Vue开发管理系统项目的时候,为了保存页面的浏览状态,我们可以使用内置组件keep-alive来缓存组件内部状态,避免重新渲染. <keep-alive> <router-vi ...

  4. [分享] 一款极简单的 BaseEntity CRUD 方法

    前言 尝试过 ado.net.dapper.ef,以及Repository仓储,甚至自己还写过生成器工具,以便做常规CRUD操作. 它们日常操作不方便之处: 每次使用前需要声明,再操作: 很多人一个实 ...

  5. Spring Cloud Config 实现配置中心,看这一篇就够了

    Spring Cloud Config 是 Spring Cloud 家族中最早的配置中心,虽然后来又发布了 Consul 可以代替配置中心功能,但是 Config 依然适用于 Spring Clou ...

  6. Linux 下载文件命令(wget)

    wget是Linux最常用的下载命令, 一般的使用方法是: wget + 空格 + 要下载文件的url路径 例如: # wget http://www.linuxsense.org/xxxx/xxx. ...

  7. 实现一个Golang的reverse函数

    Reverse函数,用来反转列表,本例子用golang实现,反转一个slice列表. 因为slice是引用类型,因此直接修改参数的值即可. func myReverse(l []string) { f ...

  8. Jmeter之CSS选择器/JQuery选择器关联

    选择器: CSS选择器或JQuery选择器是Jmeter支持的两种语法,下面对其两种语法进行简单介绍 CSS选择器 JQuery选择器 Chrome - 复制CSS选择器 Google Chrome在 ...

  9. 【MySQL】导出长数字到 Excel 避免转为科学计数法方法

    MySQL 导出比较长的数字到 Excel 时,最后几位会变成 0,解决方法如下: 如果只需要导出展示.打印:可使用 CONCAT("\t",str) 如果需要后续处理,引用,最好 ...

  10. 前端笔记之React(五)Redux深入浅出

    一.Redux整体感知 Redux是JavaScript状态管理容器,提供了可被预测状态的状态管理容器.来自于Flux思想,Facebook基于Flux思想,在2015年推出Redux库. 中文网站: ...