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

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

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

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

1. 什么是不可变?

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

2. String为什么不可变?

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

[java] view plain copy

  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本身数据不可变。看下面这个例子,

[java] view plain copy

  1. final int[] value={1,2,3}
  2. int[] another={4,5,6};
  3. value=another;    //编译器报错,final不可变

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

[java] view plain copy

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

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

[java] view plain copy

  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

[java] view plain copy

  1. package _12_01字符串;
  2. public class 为什么String要设计成不可变类你 {
  3. public static void main(String[] args) {
  4. String a, b, c;
  5. a = "test";
  6. b = a;
  7. c = b;
  8. String processA = processA(a);
  9. String processB = processB(b);
  10. String processC = processC(c);
  11. System.out.println(processA);
  12. System.out.println(processB);
  13. System.out.println(processC);
  14. }
  15. static String processA(String str){
  16. return str + "A";
  17. }
  18. static String processB(String str){
  19. return str + "B";
  20. }
  21. static String processC(String str){
  22. return str + "C";
  23. }
  24. }
  25. //OUTPUT
  26. // testA
  27. //testB
  28. //testC

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

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

[java] view plain copy

  1. package _12_01字符串;
  2. public class 为什么String要设计成不可变类2 {
  3. public static void main(String[] args) {
  4. StringBuffer a, b, c;
  5. a = new StringBuffer("test");
  6. b = a;
  7. c = b;
  8. String processA = processA(a);
  9. String processB = processB(b);
  10. String processC = processC(c);
  11. System.out.println(processA);
  12. System.out.println(processB);
  13. System.out.println(processC);
  14. }
  15. static String processA(StringBuffer str){
  16. return str.append("A").toString();
  17. }
  18. static String processB(StringBuffer str){
  19. return str.append("B").toString();
  20. }
  21. static String processC(StringBuffer str){
  22. return str.append("C").toString();
  23. }
  24. }
  25. //OUTPUT
  26. // testA
  27. //testAB
  28. //testABC

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

示例2

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

[java] view plain copy

  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. StringBuilder sb3=sb1;
  9. sb3.append("bbb");  //这时候HashSet里是{"aaabbb","aaabbb"}
  10. System.out.println(hs);
  11. }
  12. }
  13. //Output:
  14. //[aaabbb, aaabbb]

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

不可变性支持线程安全

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

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

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

[java] view plain copy

  1. String one = "someString";
  2. String two = "someString";

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

转自:http://blog.csdn.net/u013905744/article/details/52414111

时间: 2024-10-05 22:50:54

在java中String类为什么要设计成final?的相关文章

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

最近突然被问到String为什么被设计为不可变,当时有点懵,这个问题一直像bug一样存在,竟然没有发现,没有思考到,在此总结一下. 1.String的不可变String类被final修饰,是不可继承和修改的.当一个String变量被第二次赋值时,不是在原有内存地址上修改数据,而是在内存中重新开辟一块内存地址,并指向新地址. String类为什么要被设计为是final的? 1.不可变性支持线程安全. 2.不可变性支持字符串常量池,提升性能. 3.String字符串作为最常用数据类型之一,不可变防止

java里String类为何被设计为final

前些天面试遇到一个非常难的关于String的问题,"String为何被设计为不可变的"?类似的问题也有"String为何被设计为final?"个人认为还是前面一种问法更准确,设计成final仅仅保证了String类不能被继承,而Immutable相对于final要严格的多. 下文主要翻译自:http://java67.blogspot.sg/2014/01/why-string-class-has-made-immutable-or-final-java.html

java中String类为什么不可变?

在面试中经常遇到这样的问题:1.什么是不可变对象.不可变对象有什么好处.在什么情景下使用它,或者更具体一点,java的String类为什么要设置成不可变类型? 1.不可变对象,顾名思义就是创建后的对象不可以改变,典型的例子有java中的String类型. 2.相比于可变对象,不可变对象有很多优势: (1)不可变对象可以提高String Pool(字符串常量池)的效率和安全性.如果你知道一个对象是不可变动 ,那么需要拷贝的对象的内容时就不用复制它本身二只是复制它的地址,复制地址(通常一个指针的大小

hadoop中Text类 与 java中String类的区别

hadoop 中 的Text类与java中的String类感觉上用法是相似的,但两者在编码格式和访问方式上还是有些差别的,要说明这个问题,首先得了解几个概念: 字符集: 是一个系统支持的所有抽象字符的集合.字符是各种文字和符号的总称,包括各国家文字.标点符号.图形符号.数字等.例如 unicode就是一个字符集,它的目标是涵盖世界上所有国家的文字和符号: 字符编码:是一套法则,使用该法则能够对自然语言的字符的一个集合(如字母表或音节表),与其他东西的一个集合(如号码或电脉冲)进行配对.即在符号集

Java中String类学习总结

java中String类的使用频率非常高,本人在学习此模块时,认为下列几点知识值得注意: 一.String是不可变对象 java.lang.String类使用了final修饰,不能被继承.Java程序中的所有字面值,即双引号括起的字符串,如"abc",都是作为String类的实例实现的.String是常量,其对象一旦构造就不能再被改变.换句话说,String对象是不可变的,每一个看起来会修改String值的方法,实际上都是创造了一个全新的String对象,以包含修改后的字符串内容.而最

【转载】Java中String类的方法及说明

转载自:http://www.cnblogs.com/YSO1983/archive/2009/12/07/1618564.html String : 字符串类型 一.构造函数     String(byte[ ] bytes):通过byte数组构造字符串对象.     String(char[ ] value):通过char数组构造字符串对象.     String(Sting original):构造一个original的副本.即:拷贝一个original.     String(Strin

java中String类小结

构建一个字符串 1.用字符串直接量: String message = new String("Welcome to java"); 2.用字符串直接量: String message = "Welcome to java"; 3.用字符数组 Char[] charArray = {'m', 'y'}; String message = new String(charArray); 不可变字符与限定字符串 String对象是不可变的,内容不能改变 java虚拟机为了

Java中String类与Integer类的几个方法

计算诸如-123,456,789 + 123,123的值 import java.math.BigInteger; import java.util.Scanner; public class Main{ public static void main(String[] args){ Scanner cin = new Scanner(System.in); String str1, str2; BigInteger a, b; while(cin.hasNext()){ str1 = cin.

Java中String类的方法及说明

String : 字符串类型 一.构造函数     String(byte[ ] bytes):通过byte数组构造字符串对象.     String(char[ ] value):通过char数组构造字符串对象.     String(Sting original):构造一个original的副本.即:拷贝一个original.     String(StringBuffer buffer):通过StringBuffer数组构造字符串对象.  例如:      byte[] b = {'a',