String不可变的原因以及为什么要设计成不可变

964 阅读3分钟

我的理解

比如我自己写的类:如Person

public class Person {
    public static void main(String[] args) {
       	Person p1 = new Person();
        p1.setAge(10);
        Person p2 = p1;
        p2.setAge(12);
        System.out.println(p1.getAge());
    }
    
    private int age;
    
    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

结果是12,说明p1虽然没直接改变但是p1年龄还是变了,因为p2=p1使得他们指向同一个引用地址,p2改变(p2没有指向新的引用地址的话)p1也跟着变。

而如果是String类

public static void main(String[] args) {
    String s1="aaa";
    String s2=s1;
    s2="bbb";
    System.out.println(s1);

}

结果是"aaa",说明s2改变是指向了新的引用地址,不影响s1。

String真正不可变的原因

  • 保存字符串的数组value被private和final修饰,并且String类没有提供修改value的方法(比如Person类setAge()就是修改Person对象的方法),说明value不能被改变,String对象变的话(如s="ccc",s.replace("ccc","ddd"),s.substring(0,1))也是s指向了新的引用地址,而不是改变了value。
  • String类被final修饰,不能被继承,避免了子类破坏String的不可变特点

String真就不可改变吗

答案是否定的。

我们可以利用反射机制改变value

public static void main(String[] args) {
        String str = "aaa";
        System.out.println("str = " + str + "\t" + "hashcode=" + str.hashCode());
        try {
            Field valueField = String.class.getDeclaredField("value");
            //暴力反射!
            valueField.setAccessible(true);
            byte[] valueCharArr = (byte[]) valueField.get(str);
            //改变!
            valueCharArr[0] = 'b';
            System.out.println("str = " + str + "\t" + "hashcode=" + str.hashCode());
        } catch (NoSuchFieldException | SecurityException
                | IllegalArgumentException | IllegalAccessException e) {
            e.printStackTrace();
        }
    }

结果:我们改变了value!

image.png

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

  • 不可变对象天生就是线程安全的:因为不可变对象不能被改变,所以他们可以自由地在多个线程之间共享。不需要任何同步处理。

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

  • 避免安全问题:在网络连接和数据库连接中字符串常常作为参数,例如,网络连接地址URL,文件路径path,反射机制所需要的String参数。其不可变性可以保证连接的安全性。如果字符串是可变的,黑客就有可能改变字符串指向对象的值,那么会引起很严重的安全问题。

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

在String类的定义中有如下代码:

private int hash;//用来缓存HashCode

总体来说,String不可变的原因要包括 设计考虑,效率优化,以及安全性这三大方面。