尽人事,听天命。博主东南大学硕士在读,热爱健身和篮球,乐于分享技术相关的所见所得,关注公众号 @ 飞天小牛肉,第一时间获取文章更新,成长的路上我们一起进步

本文已收录于 「CS-Wiki」Gitee 官方推荐项目,现已累计 1.6k+ star,致力打造完善的后端知识体系,在技术的路上少走弯路,欢迎各位小伙伴前来交流学习

如果各位小伙伴春招秋招没有拿得出手的项目的话,可以参考我写的一个项目「开源社区系统 Echo」Gitee 官方推荐项目,目前已累计 600+ star,基于 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 并提供详细的开发文档和配套教程。公众号后台回复 Echo 可以获取配套教程,目前尚在更新中


String 为啥不可变?因为 String 中的 char 数组被 final 修饰。这套回答相信各位已经背烂了,But 这并不正确!

  • 面试官:讲讲 StringStringBuilderStringBuffer 的区别
  • 我:String 不可变,而 StringBuilderStringBuffer 可变,叭叭叭 ......
  • 面试官:String 为什么不可变?
  • 我:Stringfinal 修饰,这说明 String 不可继承;并且String 中真正存储字符的地方是 char 数组,这个数组被 final 修饰,所以 String 不可变
  • 面试官:String 的不可变真的是因为 final 吗?
  • 我:是.....是的吧
  • 面试官:OK,你这边还有什么问题吗?
  • 我:卒......

什么是不可变?

《Effective Java》中对于不可变对象(Immutable Object)的定义是:对象一旦被创建后,对象所有的状态及属性在其生命周期内不会发生任何变化。这就意味着,一旦我们将一个对象分配给一个变量,就无法再通过任何方式更改对象的状态了。

String 不可变的表现就是当我们试图对一个已有的对象 "abcd" 赋值为 "abcde",String 会新创建一个对象:

String 为什么不可变?

String 用 final 修饰 char 数组,这个数组无法被修改,这么说确实没啥问题。

但是!!!这个无法被修改仅仅是指引用地址不可被修改(也就是说栈里面的这个叫 value 的引用地址不可变,编译器不允许我们把 value 指向堆中的另一个地址),并不代表存储在堆中的这个数组本身的内容不可变。举个例子:

如果我们直接修改数组中的元素,是完全 OK 的:

那既然我们说 String 是不可变的,那显然仅仅靠 final 是远远不够的

1)首先,char 数组是 private 的,并且 String 类没有对外提供修改这个数组的方法,所以它初始化之后外界没有有效的手段去改变它;

2)其次,String 类被 final 修饰的,也就是不可继承,避免被他人继承后破坏;

3)最重要的!是因为 Java 作者在 String 的所有方法里面,都很小心地避免去修改了 char 数组中的数据,涉及到对 char 数组中数据进行修改的操作全部都会重新创建一个 String 对象。你可以随便翻个源码看看来验证这个说法,比如 substring 方法:

为什么要设计成不可变的呢?

1)首先,字符串常量池的需要

我们来回顾一下字符串常量池的定义:大量频繁的创建字符串,将会极大程度的影响程序的性能。为此,JVM 为了提高性能和减少内存开销,在实例化字符串常量的时候进行了一些优化:

  • 为字符串开辟了一个字符串常量池 String Pool,可以理解为缓存区
  • 创建字符串常量时,首先检查字符串常量池中是否存在该字符串
  • 若字符串常量池中存在该字符串,则直接返回该引用实例,无需重新实例化;若不存在,则实例化该字符串并放入池中。

如下面的代码所示,堆内存中只会创建一个 String 对象:

String str1 = "hello";
String str2 = "hello"; System.out.println(str1 == str2) // true

假设 String 允许被改变,那如果我们修改了 str2 的内容为 good,那么 str1 也会被修改,显然这不是我们想要看见的结果。

2)另外一点也比较容易想到,String 被设计成不可变就是为了安全

作为最基础最常用的数据类型,String 被许多 Java 类库用来作为参数,如果 String 不是固定不变的,将会引起各种安全隐患。

举个例子,我们来看看将可变的字符串 StringBuilder 存入 HashSet 的场景:

我们把可变字符串 s3 指向了 s1 的地址,然后改变 s3 的值,由于 StringBuilder 没有像 String 那样设计成不可变的,所以 s3 就会直接在 s1 的地址上进行修改,导致 s1 的值也发生了改变。于是,糟糕的事情发生了,HashSet 中出现了两个相等的元素,破坏了 HashSet 的不包含重复元素的原则。

另外,在多线程环境下,众所周知,多个线程同时想要修改同一个资源,是存在危险的,而 String 作为不可变对象,不能被修改,并且多个线程同时读同一个资源,是完全没有问题的,所以 String 是线程安全的。

String 真的不可变吗?

想要改变 String 无非就是改变 char 数组 value 的内容,而 value 是私有属性,那么在 Java 中有没有某种手段可以访问类的私有属性呢?

没错,就是反射,使用反射可以直接修改 char 数组中的内容,当然,一般来说我们不这么做。

看下面代码:

总结

总结来说,并不是因为 char 数组是 final 才导致 String 的不可变,而是为了把 String 设计成不可变才把 char 数组设置为 final。下面是一些创建不可变对象的简单策略,当然,也并非所有不可变类都完全遵守这些规则:

  • 不要提供 setter 方法(包括修改字段的方法和修改字段引用对象的方法);
  • 将类的所有字段定义为 final、private 的;
  • 不允许子类重写方法。简单的办法是将类声明为 final,更好的方法是将构造函数声明为私有的,通过工厂方法创建对象;
  • 如果类的字段是对可变对象的引用,不允许修改被引用对象。

关注公众号 | 飞天小牛肉,即时获取更新

  • 博主东南大学硕士在读,利用课余时间运营一个公众号『 飞天小牛肉 』,2020/12/29 日开通,专注分享计算机基础(数据结构 + 算法 + 计算机网络 + 数据库 + 操作系统 + Linux)、Java 基础和面试指南的相关原创技术好文。本公众号的目的就是让大家可以快速掌握重点知识,有的放矢。希望大家多多支持哦,和小牛肉一起成长
  • 并推荐个人维护的开源教程类项目: CS-Wiki(Gitee 推荐项目,现已累计 1.6k+ star), 致力打造完善的后端知识体系,在技术的路上少走弯路,欢迎各位小伙伴前来交流学习 ~
  • 如果各位小伙伴春招秋招没有拿得出手的项目的话,可以参考我写的一个项目「开源社区系统 Echo」Gitee 官方推荐项目,目前已累计 600+ star,基于 SpringBoot + MyBatis + MySQL + Redis + Kafka + Elasticsearch + Spring Security + ... 并提供详细的开发文档和配套教程。公众号后台回复 Echo 可以获取配套教程,目前尚在更新中。

String 的不可变真的是因为 final 吗?的更多相关文章

  1. 在Java中String类为什么要设计成final?String真的不可变吗?其他基本类型的包装类也是不可变的吗?

    最近突然被问到String为什么被设计为不可变,当时有点懵,这个问题一直像bug一样存在,竟然没有发现,没有思考到,在此总结一下. 1.String的不可变String类被final修饰,是不可继承和 ...

  2. 你真的知道final关键字吗?

    概述 final在英文中是最终的,不可更改的.在Java中final修饰变量,函数和类,就像这个单词的意思,一旦使用赋值之后不可更改. final修饰的变量不可以被改变 finalTest类 publ ...

  3. String为什么不可变

    转载:http://www.importnew.com/7440.html https://www.cnblogs.com/leskang/p/6110631.html 什么是不可变对象? 众所周知, ...

  4. String 与不可变对象

    什么是不可变对象 ?不可变对象指的是在创建一个对象之后 ,不能再改变它的状态 ,那么这个对象就是不可变的 .不能改变状态的意思是 ,不能改变对象内的成员变量 ,包括基本数据类型的值不能改变 ,引用类型 ...

  5. 6.你以为你真的了解final吗?

    1. final的简介 final可以修饰变量,方法和类,用于表示所修饰的内容一旦赋值之后就不会再被改变,比如String类就是一个final类型的类.即使能够知道final具体的使用方法,我想对fi ...

  6. 为什么字符串String是不可变字符串&&"".equals(str)与str.equals("")的区别

    为什么字符串String是不可变字符串 实际上String类的实现是char类型的数组 虽然说源码中设置的是private final char[] value; final关键词表示不可变动 但是只 ...

  7. 在java中String类为什么要设计成final?

    大神链接:在java中String类为什么要设计成final? - 程序员 - 知乎 我进行了重新排版,并且更换了其中的一个例子,让我们更好理解. String很多实用的特性,比如说“不可变性”,是工 ...

  8. Java String 对象,你真的了解了吗?

    String 对象的实现 String对象是 Java 中使用最频繁的对象之一,所以 Java 公司也在不断的对String对象的实现进行优化,以便提升String对象的性能,看下面这张图,一起了解一 ...

  9. 你以为你真的了解final吗?

    本人免费整理了Java高级资料,涵盖了Java.Redis.MongoDB.MySQL.Zookeeper.Spring Cloud.Dubbo高并发分布式等教程,一共30G,需要自己领取.传送门:h ...

随机推荐

  1. 一文搞懂 js 中的各种 for 循环的不同之处

    一文搞懂 js 中的各种 for 循环的不同之处 See the Pen for...in vs for...of by xgqfrms (@xgqfrms) on CodePen. for &quo ...

  2. WiFi & QR Code

    WiFi & QR Code https://github.com/bndw/wifi-card https://wifi.dev.bdw.to/ 扫码登录 WIFI:T:WPA;S:free ...

  3. 漫画 | C语言哭了,过年回家,只有我还没对象

    C语言回家过年,遇到不少小伙伴. 大家都在外地打拼,一年难得见面,聚到一起吃饭,都非常高兴. 听Java提到TIOBE, 正在喝酒的C语言激动起来. 自己常年在那里排名第二,人类用自己写的程序可真不少 ...

  4. C++算法代码——阿克曼函数

    题目来自: 题目描述 阿克曼( Ackmann) 函数 A(x, y) 中, x, y 定义域是非负整数, 函数值定义为: 输入 输入两个数,表示m和n. 两个数均不超过10. 输出 输出一个数,表示 ...

  5. Window下Scala开发环境搭建

    在Windows下搭建Scala开发环境,需要做以下几个步骤 1) 安装JDK 2) 安装Scala,并配置环境变量 3) Idea安装并创建Scala 类 1.安装JDK JDK安装,这里不再介绍, ...

  6. 微信小程序登录流程解析

    小程序可以通过微信官方提供的登录能力方便地获取微信提供的用户身份标识openid,快速建立小程序内的用户体系. 登录流程时序: 1.首先,调用 wx.login获取code ,判断用户是否授权读取用户 ...

  7. 如何使用 Navicat Premium 的新“自动运行”工具自动运行行数据库复制。

    数据库复制有至少三种不同的方式: 快照复制:一台服务器上的数据复制到同一台或不同服务器上的另一个数据库. 合并复制:来自两个或多个数据库的数据被合并到一个数据库中. 事务复制:用户收到数据库的完整初始 ...

  8. SpringBoot接收map类型的参数

    如果某个controller的某个接口的参数特别多,也可以使用map的方式来接收参数,接收之后使用get方法获取即可. 1)get请求方式,定义map接收方式 @RequestParam(requir ...

  9. React源码 commit阶段详解

    转: React源码 commit阶段详解 点击进入React源码调试仓库. 当render阶段完成后,意味着在内存中构建的workInProgress树所有更新工作已经完成,这包括树中fiber节点 ...

  10. dubbo-zookeeper quick start

    目录 dubbo快速开始 服务提供者(Service provider) 定义服务接口(Defining service interfaces) 在服务提供方实现接口(Implement interf ...