前面聊了聊面试必考 String 的坑,具体可以细看《你真的懂 Java 的 String 吗?》,也留下了一个疑问,为什么 String 要被设计成 final 呢?其实,如果你读的认真的话,前文中其实已经解释了主要的原因。

什么是不可变对象?

String 类是不可变的,那么到底什么是不可变的对象呢?可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

如下图,给一个已有字符串"abcd"第二次赋值成"abcdel",不是在原内存地址修改数据,而是重新指向一个新对象,新地址。

String 类不可变性的好处

不知道你注意前文中的一段文字没,“一般来说,字符串的分配和其他对象分配一样,是需要消耗时间和空间的,而且字符串使用的非常多。JVM 为了提高性能和减少内存的开销,在实例化字符串的时候进行了一些优化:使用字符串常量池。每当创建字符串常量时,JVM 会首先检查字符串常量池,如果该字符串已经存在常量池中,那么就直接返回常量池中的实例引用。如果字符串不存在常量池中,就会实例化该字符串并且将其放到常量池中。由于 String 字符串的不可变性,常量池中一定不存在两个相同的字符串。

只有当字符串是不可变的,字符串池才有可能实现,字符串池的实现可以在运行时节约很多 heap 空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么 String intern 将不能实现,因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。

其次,如果字符串是可变的,那么会引起很严重的安全问题,譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在 socket 编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的。否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。而正是因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。

另外,基于效率考虑,因为字符串是不可变的,所以我们可以缓存 String 的 hashcode,不需要重新计算,这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。

String 真的不可变吗?

需要补充一个知识点:当使用 final 修饰基本类型变量时,不能对基本类型变量重新赋值,因此基本类型变量不能被改变。但对于引用类型变量而言,它保存的仅仅是一个引用,final 只保证这个引用变量所引用的地址不会改变,即一直引用同一个对象,但这个对象完全可以发生改变。

前文源码可知 String 的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中,value 比较特殊,它是一个数组变量引用,而不是真正的对象。value是 final 修饰的,也就是说final 不能再指向其他数组对象,那么我能改变 value 指向的数组吗?

一般来说在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个 value 引用,更不能通过这个引用去修改数组。那么用什么方式可以访问私有成员呢?没错,用反射,可以通过反射出获取 String对象中的 value 属性,进而改变通过获得的 value 引用改变数组的结构。

在这个过程中,s 始终引用的同一个 String 对象,但是在反射前后,这个 String 对象发生了变化,也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。

如何设计一个不可变类

讲了那么多,String 是如何实现不可变的呢,或者说如何实现一个final 类?在有些场景下,我们需要特殊的对象作为 map 的 key,实现这个 key 需要注意什么?

不可变类,可以理解为,只允许读,不允许修改的类,类的对象一旦创建,就不能再进行修改了。

需要遵循以下几个原则:

  • Immutable 对象的状态在创建之后就不能发生改变,任何对它的改变都应该产生一个新的对象。
  • Immutable类的所有的属性都应该是 final 的。
  • 对象必须被正确的创建,比如:对象引用在对象创建过程中不能泄露( leak )。
  • 对象应该是 final 的,以此来限制子类继承父类,以避免子类改变了父类的 immutable 特性。
  • 如果类中包含 mutable 类对象,那么返回给客户端的时候,返回该对象的一个拷贝,而不是该对象本身, Effective Java 第39条中提到:必要时进行保护性拷贝,总结一句就是:成员变量不要被外部引用直接赋值,而是拷贝之后的赋值。

具体来说,要将一个 Java 类构造成不可变的类,必须执行以下操作:

1、 将类声明为 final,这样就不会重写它。如果可以重写类的话,则可以重写它的方法的行为,因此最安全的选择就是不允许将类子类化。

2、 保证所有成员变量必须私有,并且加上final修饰。通过这种方式保证成员变量不可改变。但只做到这一步还不够,因为如果是对象成员变量有可能在外部改变其值。所以第4点弥补这个不足。

3、 不提供改变成员变量的方法,包括setter,避免通过其他接口改变成员变量的值,破坏不可变特性。

4、 通过构造器初始化所有成员,进行深拷贝(deep copy),为了保证内部的值不被修改,可以采用深度copy来创建一个新内存保存传入的值。

5、 在getter方法中,不要直接返回对象本身,而是克隆对象,并返回对象的拷贝这种做法也是防止对象外泄,防止通过getter获得内部可变成员对象后对成员变量直接操作,导致成员变量发生改变。

String 还经常遇到的问题就是编码,Java中理论说是一个字符(汉字 字母)占用两个字节。但是在UTF-8的时候 new String("字").getBytes().length 返回的是3 表示3个字节,知道是为什么吗,Java中char占多少字节?下期回答,请继续关注

死磕 Java String 序列文章

你真的懂 Java 的 String 吗?

为什么写了value属性 jq赋值value值不显示_为什么 String 要设计成 final,又如何设计一个不可变类呢?...相关推荐

  1. 为什么写了value属性 jq赋值value值不显示_[Go基础]理解 Go 标准库中的 atomic.Value 类型

    转载声明 文章作者:喵叔 上次更新:2019-03-15 许可协议:CC BY-NC-ND 4.0(转载请注明出处) 原文链接:https://blog.betacat.io/post/golang- ...

  2. ts定义IProps类型,给Props(object类型的属性)赋值默认值的方式

    定义一个props的接口: interface IPropsUpload {accept?: string,multiple?: boolean,options: UploadOptions, } / ...

  3. jq校验输入框值变化时_谈谈自己对CRC校验的理解

    1.CRC是用来干嘛的? 检测数据传输过程中是否出现错误(某些位,或某几位,或者某块区域位错误). 2.CRC是否能校正数据传输中的错误? CRC只能检错,不能纠错.如果发现错误,可根据双方协议规定要 ...

  4. ASP.Net TextBox只读时不能通过后台赋值取值

    ASP.Net TextBox只读时不能通过后台赋值取值 今天在开发一个asp.net的页面遇到了TextBox设置了ReadOnly属性,在js中赋值后,后台代码取不到值的情况,经过在网上查找,找到 ...

  5. TextBox只读时不能通过后台赋值取值解决办法

    给页面的TextBox设置ReadOnly="True"时,在后台代码中不能赋值取值,下边几种方法可以避免: 1.不设置ReadOnly,设置οnfοcus=this.blur() ...

  6. 对象属性结构赋值_(六)面向对象-下

    (六)面向对象-下 关键字:static static 可以用来修饰的结构:主要用来修饰类的内部结构 属性.方法.代码块.内部类 static修饰属性:静态变量(类变量 属性,是否使用static修饰 ...

  7. iOS给readonly属性进行赋值

    我们都知道iOS中,是不能通过点方法或者[]方法进行readonly属性的赋值的.如果你执意要调用,那么Xcode会报错,readonly属性是不支持赋值操作的. 那么,我们有其他办法给这个reado ...

  8. MVC easyui-switchbutton 和 checkbox 、radio 使用和赋值 JQ select 取值

    初始化默认: <input id="cdt_level" class="easyui-switchbutton" data-options="o ...

  9. 定义一个 圆形 Circle类 , 定义其中的长度length属性,定义一个求面积getArea()的方法。 并编写一个测试类,进行长度的赋值和展示,并调用求面积方法展示面积值。

    定义一个 圆形 Circle类 , 定义其中的长度length属性,定义一个求面积getArea()的方法. 并编写一个测试类,进行长度的赋值和展示,并调用求面积方法展示面积值. public cla ...

最新文章

  1. Linux中断子系统-通用框架处理
  2. 逻辑回归 - sklearn (LR、LRCV、MLP、RLR)- Python代码实现
  3. python不能保存中文_Python 关于matplotlib无法显示中文字体的解决方法
  4. BlackBerry Key2 键盘扩展
  5. android 输入法 监听,android 监听 输入法
  6. CEPH LIO iSCSI Gateway
  7. Windows终端(WT)添加conda命令行
  8. 在云栖小镇,新华三呈现物联网金秋硕果
  9. Science杂志公布的机器学习资源
  10. Atitit Embed db use嵌入式数据库用途 目录 1.1. 从软件角度来说,数据库分类为两种: 1 2. 运维一体化,提升开发效率 1 2.1. 可以使用sql 这样的dsl 4gl来查
  11. 基于TencentOS-tiny实现甲醛传感器(英国达特 WZ-S)数据解析思路及实现
  12. 聊斋志异中的《陆判》
  13. Android Sprd省电管理(二)应用省电模式设置流程
  14. 魅族16spro锁回BL(Bootloader) 恢复微信指纹
  15. 为什么二分K-均值比K-均值的聚类效果更好?
  16. VICA 架构设计(1)
  17. canvas快速实现视频的一键截图功能
  18. 涂抹式水光针的使用方法你知道吗?
  19. Linux中国对话龙蜥社区4位理事:龙蜥操作系统捐赠的背后,是谁在推动?
  20. php7.1 phpize编译gd,centos 7 下用 phpize安装GD扩展库

热门文章

  1. Ubuntu18.04之boost警告报错
  2. android adb复制粘贴工具
  3. Webrtc半小时搭建视频通话 视频会议
  4. 世上最好的共享内存(Linux共享内存最透彻的一篇)上集
  5. Emacs启动时窗口全屏两种方法
  6. Android MediaRecorder调用AudioRecord流程
  7. adb命令启动activity、service,发送broadcast
  8. python一款神器:ptpython 安装
  9. Visual Assist X安装
  10. pycharm-实用教程和每日练习