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

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

大神链接:在java中String类为什么要设计成final? - 程序员 - 知乎

我进行了重新排版,并且更换了其中的一个例子,让我们更好理解。

 

String很多实用的特性,比如说“不可变性”,是工程师精心设计的艺术品!艺术品易碎!用final就是拒绝继承,防止世界被熊孩子破坏,维护世界和平!

1. 什么是不可变?

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

\"\"

 

2. String为什么不可变?

 

翻开JDK源码,java.lang.String类起手前三行,是这样写的:


 
  1. public final class String implements java.io.Serializable, Comparable<String>, CharSequence {

  2. /** String本质是个char数组. 而且用final关键字修饰.*/

  3. private final char value[];

  4. ...

  5. ...

  6. }

首先String类是用final关键字修饰,这说明String不可继承。再看下面,String类的主力成员字段value是个char[ ]数组,而且是用final修饰的。final修饰的字段创建以后就不可改变。

有的人以为故事就这样完了,其实没有。因为虽然value是不可变,也只是value这个引用地址不可变。挡不住Array数组是可变的事实。Array的数据结构看下图

\"\"

也就是说Array变量只是stack上的一个引用,数组的本体结构在heap堆。String类里的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子,

 


 
  1. final int[] value={1,2,3}

  2. int[] another={4,5,6};

  3. value=another; //编译器报错,final不可变

value用final修饰,编译器不允许我把value指向堆区另一个地址。但如果我直接对数组元素动手,分分钟搞定。


 
  1. final int[] value={1,2,3};

  2. value[2]=100; //这时候数组里已经是{1,2,100}

或者更粗暴的反射直接改,也是可以的。


 
  1. final int[] array={1,2,3};

  2. Array.set(array,2,100); //数组也被改成{1,2,100}

所以String是不可变,关键是因为SUN公司的工程师,在后面所有String的方法里很小心的没有去动Array里的元素,没有暴露内部成员字段。

private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心地把整个String设成final禁止继承,避免被其他人继承后破坏。所以String是不可变的关键都在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。

 

3. 不可变有什么好处?

这个最简单的原因,就是为了安全

示例1


 
  1. package _12_01字符串;

  2.  
  3. public class 为什么String要设计成不可变类你 {

  4.  
  5. public static void main(String[] args) {

  6.  
  7. String a, b, c;

  8. a = \"test\";

  9. b = a;

  10. c = b;

  11. String processA = processA(a);

  12. String processB = processB(b);

  13. String processC = processC(c);

  14. System.out.println(processA);

  15. System.out.println(processB);

  16. System.out.println(processC);

  17. }

  18.  
  19. static String processA(String str){

  20. return str + \"A\";

  21. }

  22.  
  23. static String processB(String str){

  24. return str + \"B\";

  25. }

  26.  
  27. static String processC(String str){

  28. return str + \"C\";

  29. }

  30.  
  31. }

  32. //OUTPUT

  33. // testA

  34. //testB

  35. //testC

当String支持非可变性的时候,它们的值很好确定,不管调用哪个方法,都互不影响。

如果String是可变的,就可能如下例,我们使用StringBuffer来模拟String是可变的


 
  1. package _12_01字符串;

  2.  
  3. public class 为什么String要设计成不可变类2 {

  4.  
  5. public static void main(String[] args) {

  6.  
  7. StringBuffer a, b, c;

  8. a = new StringBuffer(\"test\");

  9. b = a;

  10. c = b;

  11. String processA = processA(a);

  12. String processB = processB(b);

  13. String processC = processC(c);

  14. System.out.println(processA);

  15. System.out.println(processB);

  16. System.out.println(processC);

  17. }

  18.  
  19. static String processA(StringBuffer str){

  20. return str.append(\"A\").toString();

  21. }

  22.  
  23. static String processB(StringBuffer str){

  24. return str.append(\"B\").toString();

  25. }

  26.  
  27. static String processC(StringBuffer str){

  28. return str.append(\"C\").toString();

  29. }

  30.  
  31. }

  32. //OUTPUT

  33. // testA

  34. //testAB

  35. //testABC

能看出b=a,c=b;程序员的本意是希望变量是不变的。所以String不可变的安全性就体现在这里。实际上StringBuffer的作用就是起到了String的可变配套类角色。

示例2

再看下面这个HashSet用StringBuilder做元素的场景,问题就更严重了,而且更隐蔽。


 
  1. class Test{

  2. public static void main(String[] args){

  3. HashSet<StringBuilder> hs=new HashSet<StringBuilder>();

  4. StringBuilder sb1=new StringBuilder(\"aaa\");

  5. StringBuilder sb2=new StringBuilder(\"aaabbb\");

  6. hs.add(sb1);

  7. hs.add(sb2); //这时候HashSet里是{\"aaa\",\"aaabbb\"}

  8.  
  9. StringBuilder sb3=sb1;

  10. sb3.append(\"bbb\"); //这时候HashSet里是{\"aaabbb\",\"aaabbb\"}

  11. System.out.println(hs);

  12. }

  13. }

  14. //Output:

  15. //[aaabbb, aaabbb]

StringBuilder型变量sb1和sb2分别指向了堆内的字面量\"aaa\"和\"aaabbb\"。把他们都插入一个HashSet。到这一步没问题。但如果后面我把变量sb3也指向sb1的地址,再改变sb3的值,因为StringBuilder没有不可变性的保护,sb3直接在原先\"aaa\"的地址上改。导致sb1的值也变了。这时候,HashSet上就出现了两个相等的键值\"aaabbb\"。破坏了HashSet键值的唯一性。所以千万不要用可变类型做HashMap和HashSet键值。

 

不可变性支持线程安全

 

还有一个大家都知道,就是在并发场景下,多个线程同时读一个资源,是不会引发竟态条件的。只有对资源做写操作才有危险。不可变对象不能被写,所以线程安全。
 

不可变性支持字符串常量池

最后别忘了String另外一个字符串常量池的属性。像下面这样字符串onetwo都用字面量\"something\"赋值。它们其实都指向同一个内存地址。
 

 


 
  1. String one = \"someString\";

  2. String two = \"someString\";


\"\"

这样在大量使用字符串的情况下,可以节省内存空间,提高效率。但之所以能实现这个特性,String的不可变性是最基本的一个必要条件。要是内存里字符串内容能改来改去,这么做就完全没有意义了。

版权声明

本文仅代表作者观点,不代表百度立场。
本文系作者授权百度百家发表,未经许可,不得转载。

上一篇:Database operations of Mysql 下一篇:Zabbix
热门文章
  • Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering

    Sequential Monte Carlo Methods (SMC) 序列蒙特卡洛/粒子滤波/Bootstrap Filtering
    Problem Statement 我们考虑一个具有马尔可夫性质、非线性、非高斯的状态空间模型(State Space Model):对于一个时间序列上的观测结果{yt,t∈N}\\{ y_t , t \\in N \\}{yt​,t∈N},我们认为每个观测结果yty_tyt​的生成依赖于一个无法直接观察的隐变量xt∈{xt,t∈N}x_t \\in \\{x_t , t \\in N \\}xt​∈{xt​,t∈N},即:p(...
  • 机房智能化温湿度解决方式之POE供电以太网温湿度传感器

    机房智能化温湿度解决方式之POE供电以太网温湿度传感器
    机房智能化温湿度解决方式之POE供电以太网温湿度传感器 北京盈创力和电子科技有限公司 智能型TCP网口温湿度记录仪 北京IP网络温湿度记录仪厂家,北京盈创力和 北京智能型TCP网口温湿度记录仪IP网络温湿度记录仪是一种新型的基于TCP/IP协议双绞线以太网标准温湿度采集模块,利用它可以实现现场温度值、相对湿度值的采集,同时利用其自身的RJ45通信接口可以方便地和机房监控主机或交换机集线器进行联网。 工作于-40℃~85℃工业级带...
  • Hive 系统函数及示例

    Hive 系统函数及示例
    查看所有系统函数 show functions; 函数分类 内置函数【系统函数】 数学函数: floor、round、ceil、cos、log2等 字符串函数: length、reverse、trim、lower、get_json_object、repeat等 收集函数: size 转换函数: cast 日期函数: year、month、datediff、date、date_add等 条件函数: coalesce、case…w...
  • CSRF的原理和防范措施

    CSRF的原理和防范措施
    a)攻击原理:i.用户C访问正常网站A时进行登录,浏览器保存A的cookieii.用户C再访问攻击网站B,网站B上有某个隐藏的链接或者图片标签会自动请求网站A的URL地址,例如表单提交,传指定的参数iii.而攻击网站B在访问网站A的时候,浏览器会自动带上网站A的cookieiv.所以网站A在接收到请求之后可判断当前用户是登录状态,所以...
  • HTTP状态保持的原理

    HTTP状态保持的原理
    a)在用户登录之后,浏览器返回响应的时候会在响应中添加上cookieb)浏览器接收到cookie之后会自动保存c)当用户再次请求同一服务器中的其他网页的时候,浏览器会自动带上之前保存的cookied)服务接收到请求之后可以请 request 对象中取到cookie 判断当前用户是否登录  Http是无状态的,就是连接时数据互通,关闭后...
标签列表