源码分析 - String对象真的不可变吗?

什么是不可变对象?

如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

String对象为什么不可变吗?

JDK1.7中String类的源码:

public final class String implements Serializable, Comparable<String>, CharSequence {
    private final char[] value;
    private int hash;

由以上的代码可以看出, 在Java中String类其实就是对字符数组的封装,在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。 除此之外还有一个hash成员变量,是该String对象的哈希值的缓存。value也只是一个引用,它指向一个真正的数组对象。value这个变量是private的,并且没有提供setValue公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改, 并且在String类的外部不能访问这个成员。此外,value变量是final的, 也就是说在String类内部,一旦这个值初始化了, 也不能被改变。所以可以认为String对象是不可变的了。

String对象真的不可变吗?

从上文可知String的成员变量是private final 的,也就是初始化之后不可改变。那么在这几个成员中, value比较特殊,因为他是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。
我们知道,用反射可以访问私有成员, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:

public class MyClass {
    public static void main(String[] args) {
        try {
            String str = "Owen Chan";
            System.out.println("str: " + str); //Owen Chan

            //获取String类中的value字段
            Field valueField = String.class.getDeclaredField("value");

            //改变value属性的访问权限
            valueField.setAccessible(true);

            //获取s对象上的value属性的值
            char[] chars = (char[]) valueField.get(str);

            //改变value所引用的数组中的第4个字符
            chars[4] = '_';

            System.out.println("str: " + str);

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

打印结果:
str: Owen Chan
str: Owen_Chan

在这个过程中,str 始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。

private final static int[] array = new int[] {1, 3, 3};
public static void main(String[] args) {
    array[1] = 4;
    System.out.println("array[1]:" + array[1]);
}
array[1]:4

问题:

public static void main(String[] args) {
    try {
        String str1 = "hello world";
        String str2 = "hello world";
        String strNew = new String("hello world");
        System.out.println("str1 == str2 : "+ (str1 == str2));
        System.out.println("str1 == strNew : "+ (str1 == strNew));
        System.out.println("str2 == strNew : "+ (str2 == strNew));

        //修改hello 的value
        Field hello_field = String.class.getDeclaredField("value");
        hello_field.setAccessible(true);
        char[] value=(char[])hello_field.get(str1);
        value[5]='_';

        System.out.println("str1: " + str1);
        System.out.println("str2: " + str2);
        System.out.println("strNew: " + strNew);

    } catch (Exception e) {
        e.printStackTrace();
    }
}
打印结果:
str1 == str2 : true
str1 == strNew : false
str2 == strNew : false
str1: hello_world
str2: hello_world
strNew: hello_world
分析:

String str1=”hello world”; 和str2都是在常量池里,指向同一个对象。所以str和str2是同一个对象,new String这个是在堆里。strNew与其他两个不是同一个对象。关于后面反射机制修改了str1的值,strNew和str2的值也同步修改了。不管修改哪一个都会全部修改,所以应该可能是new的时候,查找了常量池是否存在这个数据,如果存在new出来是一个指向指针的指针

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值