Java---不可变对象String

  • 什么是不可变
    不可变对象是指一个对象的状态在对象被创建之后就不再变化,包括对象内的成员变量、基本数据类型的值等等。

  • String为什么不可变
    String类本质都是对字符数组的封装(而且value也只是一个引用,它指向一个真正的数组对象)。该数组value被声明为私有,外部便无法直接访问该成员,并且加以final修饰 , 源码中也没有提供value的set方法,因此只能对value赋值一次,所以String类一旦初始化,外部便无法修改,因为value被修饰为 private final,在String类内部也无法改变。所以String对象是不可变的。

    String的源码(JDK1.8)

public final class String  //将类声明为final,所以它不能被继承
    implements java.io.Serializable, Comparable<String>, CharSequence {

    /** 将value数组声明为private 和final的,这样内外均无法改变该成员 */
    private final char value[];

    /** 哈希值 */
    private int hash; // Default to 0
    .....
}
  • 看似改变String的方法
    在我们使用String对象提供的substring、replace、replaceAll、toLowerCase等方法的时候,给我们感觉确实改变了String的值,看一下以下代码
String s = "ABC";    
s = s.replace('B', 'A');    
System.out.println("s = " + s);  //输出AAC  

String s = "ABCD";    
s.replace('B', 'A');    
System.out.println("s = " + s);  //输出ABCD

从结果中可以看出执行方法后应该是创建了一个新的String对象并返回,所以第一个输出s已经replace方法执行后返回的新的String对象,在看一下replace的源码:

public String replace(char oldChar, char newChar) {
        if (oldChar != newChar) {
            int len = value.length;
            int i = -1;
            char[] val = value; /* avoid getfield opcode */

            while (++i < len) {
                if (val[i] == oldChar) {
                    break;
                }
            }
            if (i < len) {
                char buf[] = new char[len];
                for (int j = 0; j < i; j++) {
                    buf[j] = val[j];
                }
                while (i < len) {
                    char c = val[i];
                    buf[i] = (c == oldChar) ? newChar : c;
                    i++;
                }
                return new String(buf, true);//替换完成后返回一个新的String对象
            }
        }
        return this;
    }

String这些方法的内部其实在完成逻辑后创建了一个新的String对象并返回。这也是String,StringBuffer,StringBuilder,的区别,他们虽然都是final类,不允许被继承,在本质上都是字符数组,但是不同的是,String的长度是不可变的而后两者长度可变,在进行连接操作时,String每次返回一个新的String实例,而StringBuffer和StringBuilder的append方法直接返回this,所以当进行大量的字符串连接操作时,不推荐使用String,因为它会产生大量的中间String对象。

  • String类真的不可变吗
    从上文可知String的成员变量value是private final修饰的,初始化后不可再改变并且不能再指向其他数组对象。但是value本身是一个引用变量,而不是真正的对象,那么我能改变value指向的数组吗? 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。
    那么只要可以访问私有成员,我们就可以改变它。那么反射便可以解决, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构

下面是实例代码:

    //创建字符串"Hello World", 并赋给引用s  
    String s = "Hello World";   

    System.out.println("s = " + s); //Hello World  

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

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

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

    //改变value所引用的数组中的字符  
    value[5] = '_';  

    System.out.println("s = " + s);  //Hello_World  

猜你喜欢

转载自blog.csdn.net/yucdsn/article/details/79367679