在java中final的用法String类为什么要设计成final

我个人的理解是因为String是immutable的,所鉯不能允许其他类继承String否则如下方法: 可以接收String的子类。而你可以轻易的把子类实现成mutable的这就违反了String immutable的约定。

因此String 干脆禁止被继承這样可以确保使用String的代码用的是没有被改变过的String。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


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

我要回帖

更多关于 java中final的用法 的文章

 

随机推荐