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

1.什么是不可变?

在java中String类为什么要设计成final?
在这里插入图片描述

2.string为什么不可变?

private final char value[];

首先String类是用final关键字修饰,这说明string不可被继承。再看下面,stirng类的主力成员字段value是个char[]数组,而且是用final修饰的,final修饰的字段创建以后不可以改变。
虽然value不可变,也只是value这个引用地址不可变。挡不住array数组是可变的事实,array的数据结构看下图。
在这里插入图片描述
也就是说array变量只是stack上的引用,数组的本体结构在heap堆。string类的value用final修饰,只是说stack里的这个叫value的引用地址不可变。没有说堆里array本身数据不可变。看下面这个例子。

final int[] value = {1,2,3};
int[] another = {4,5,6};
value = another;

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

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

所以string是不可变,关键是因为sun公司的工程师,在后面所有string的方法里很小心的没有去动array里的元素,没有暴露内部成员字段。private final char value[]这一句里,private的私有访问权限的作用都比final大。而且设计师还很小心的把整个string设成final禁止继承,避免被其他人继承破坏。所以string是不可变的关键在底层的实现,而不是一个final。考验的是工程师构造数据类型,封装数据的功力。

3.不可变有什么好处?

为了安全。

class Test{

    public static String appendStr(String s){
        s +="bbb";
        return s;
    }

    public static StringBuilder appendStr(StringBuilder sb){
        return sb.append("bbb");
    }

    public static void main(String[] args) {
        String s = new String("aaa");
        String ns = Test.appendStr(s);
        System.out.println("string aaa>>> " + s.toString());

        StringBuilder sb = new StringBuilder("aaa");
        StringBuilder nsb = Test.appendStr(sb);
        System.out.println("stringBuilder aaa >>>" +sb.toString());
    }
}
// stringbuilder aaa >>> aaabbb

如果程序员不小心像上面例子里,直接在传进来的参数上加"bbb",因为Java对象参数传的是引用,所以可变的的StringBuffer参 数就被改变了。可以看到变量sb在Test.appendSb(sb)操作之后,就变成了"aaabb"。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。

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

class Test{

    public static void main(String[] args) {
        HashSet<StringBuilder> hs = new HashSet<>();
        StringBuilder sb1 = new StringBuilder("aaa");
        StringBuilder sb2 = new StringBuilder("aaabbb");
        hs.add(sb1);
        hs.add(sb2);
        StringBuilder sb3 = sb1;
        sb3.append("bbb");
        System.out.println(hs);
    }
}

StringBuilder型变量sb1和sb2分别指向了堆内的字面量”aaa"和" aaabbb"。把他们都插入一个HashSet。到这一步没问题。但如果后面我把变量sb3也指向sb 1的地址,再改变sb3的值,因为StringBuilder没有不可变性的保护,sb3直接在原先" aaa “的地址上改。导致sb1的值也变了。这时候,HashSet上就出现了两个相等的键值” aaabbb"。破坏了HashSet键值的唯一 性。所以千万不要用可变类型做HashMap和HashSet键值。
还有一个大家都知道,就是在并发场景下,多个线程同时读一个资源, 不会引发竟态条件的。只有对资源做写操作才有危险。可对象不能被写,所以线程安全。后别忘了String另外一个字符串常量池的属性。像下面这样字符串one和two都用字面量"something"赋值。它们其实都指向同一个内存地址。

String one = "someString"
String two = "someString"

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

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值