Java中的String为什么要设计成不可变的

一、不可变类和不可变对象

创建一个一旦其内容就不能在改变的对象,称其为一个不可变对象(immutable object),而它的类称为不可变类(immutable class)。

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    /** The value is used for character storage. */
    private final char value[];

    /** Cache the hash code for the string */
    private int hash; // Default to 0

可以看到String的本质是一个char数组,是对字符串数组的封装,并且是被final修饰的,创建后不可改变。

二、String类不可变性的好处

Java中将String设计成不可变的是综合考虑到各种因素的结果,想要理解这个问题,需要综合内存,同步,数据结构以及安全等方面的考虑

1、字符串常量池

在Java中,由于会大量的使用String常量,如果每一次声明一个String都创建一个String对象,那将会造成极大的空间资源的浪费。Java提出了String pool的概念,在堆内存中开辟一块存储空间String pool,当初始化一个String变量时,如果该字符串已经存在了,就不会去创建一个新的字符串变量,而是会返回已经存在了的字符串的引用。

		String str1 = "abc";
        String str2 = "abc";
        String a = "ab";
        String b = "c";
        String str3 = a+b;
        String str4 = "abcd";
        System.out.println("str1地址:"+System.identityHashCode(str1));
        System.out.println("str2地址:"+System.identityHashCode(str2));
        System.out.println("str3地址:"+System.identityHashCode(str3));
        System.out.println("str4地址:"+System.identityHashCode(str4));
        System.out.println("str1.hashCode():"+str1.hashCode());
        System.out.println("str2.hashCode():"+str2.hashCode());
        System.out.println("str3.hashCode():"+str3.hashCode());
        System.out.println("a.hashCode():"+a.hashCode());
        System.out.println("str1 == str2:"+(str1 == str2));
        System.out.println("str1.equals(str2):"+str1.equals(str2));
        System.out.println("str3 == str1:"+(str3 == str1));
        System.out.println("str3.equals(str1):"+str3.equals(str1));
        str1 = "abcd";
        System.out.println("更改后的str1地址:"+System.identityHashCode(str1));
        System.out.println("更改后的str1.hashCode():"+str1.hashCode());
        System.out.println("str1 == str4:"+(str1 == str4));
        System.out.println("str1.equals(str4):"+str1.equals(str4));
        str2 += "d";
        System.out.println("更改后的str2地址:"+System.identityHashCode(str2));
        System.out.println("更改后的str2.hashCode():"+str2.hashCode());
        System.out.println("str1 == str2:"+(str1 == str2));
        System.out.println("str1.equals(str2):"+str1.equals(str2));

输出:

str1地址:23934342
str2地址:23934342
str3地址:22307196
str4地址:10568834
str1.hashCode():96354
str2.hashCode():96354
str3.hashCode():96354
a.hashCode():3105
str1 == str2:true
str1.equals(str2):true
str3 == str1:false
str3.equals(str1):true
更改后的str1地址:10568834
更改后的str1.hashCode():2987074
str1 == str4:true
str1.equals(str4):true
更改后的str2地址:21029277
更改后的str2.hashCode():2987074
str1 == str2:false
str1.equals(str2):true

这个例子可以看到我们想要的结果:如果字符串是可变的,某一个字符串变量改变了其值,那么其指向的变量的值也会改变,String pool将不能够实现!
在这里插入图片描述

str1和str2初始化都是"abc",abc在堆中存在,所以地址是相同的,他们的hashcode()也是相同的。所以str1 == str2:true ;str1.equals(str2):true他们的比较都是true;

- “==” 是判断两个变量是否指向同一个地方,即存储位置。也就是说是否引用同一个变量。
- equals() 是判断两个String类型字符串的内容是否一样。

但是当str3为字符串“ab”+“c”时,是一个新的地址,所以str3 == str1:false,(虽然str3.hashCode()和str1.hashCode()相同,但是地址不同),下面同理。

2、使多线程安全
public class Stringtest {
    public String appendStr(String s){
        s += "efg";
        return s;
    }
    public StringBuilder appendSb(StringBuilder sb){
        sb.append("efg");
        return sb;
    }

    public static void main(String[] args) {
        Stringtest stringtest = new Stringtest();
        String str = "abcefg";
        System.out.println("str地址:"+System.identityHashCode(str));
        String str1 = new String("abc");
        StringBuilder  str2 = new StringBuilder("abc");
        System.out.println("str1地址:"+System.identityHashCode(str1));
        System.out.println("str2地址:"+System.identityHashCode(str2));

        System.out.println("str1 == str2:"+(str1 == str2.toString()));
        System.out.println("str1.equals(str2.toString()):"+str1.equals(str2.toString()));

        String newstr1 = stringtest.appendStr(str1);
        System.out.println("newstr1地址:"+System.identityHashCode(newstr1));


        StringBuilder newstr2 = stringtest.appendSb(str2);
        System.out.println("newstr2地址:"+System.identityHashCode(newstr2));
    }
}

输出:

str地址:23934342
str1地址:22307196
str2地址:10568834
str1 == str2:false
str1.equals(str2.toString()):true
newstr1地址:21029277
newstr2地址:10568834

发现当对String字符串进行修改,地址改变,但是当对StringBuilder字符串进行修改,字符串的地址并没有改变。因为Java对象参数传的是引用,所有可变的StringBuffer参数就被改变了。可以看到变量str2在appendSb(str2)操作之后,就变成了"abcefg"。有的时候这可能不是程序员的本意。所以String不可变的安全性就体现在这里。

3、避免安全问题

在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。
因为String是不可变的,所以它的值是不可改变的。但由于String不可变,也就没有任何方式能修改字符串的值,每一次修改都将产生新的字符串,如果使用char[]来保存密码,仍然能够将其中所有的元素设置为空和清零,也不会被放入字符串缓存池中,用字符串数组来保存密码会更好。

4、加快字符串处理速度

由于String是不可变的,保证了hashcode的唯一性,于是在创建对象时其hashcode就可以放心的缓存了,不需要重新计算。这也就是Map喜欢将String作为Key的原因,处理速度要快过其它的键对象。所以HashMap中的键往往都使用String。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值