zoukankan      html  css  js  c++  java
  • Java中的String为什么是不可变的? -- String源码分析

    众所周知, 在Java中, String类是不可变的。那么到底什么是不可变的对象呢? 可以这样认为:如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变。

    区分对象和对象的引用

    对于Java初学者, 对于String是不可变对象总是存有疑惑。看下面代码:

    1 String s = "ABCabc";
    2         System.out.println("s = " + s);
    3         
    4         s = "123456";
    5         System.out.println("s = " + s);

    输出结果:

    s = ABCabc
    s = 123456

    从打印结果可以看出,s的值确实改变了。那么怎么还说String对象是不可变的呢? 其实这里存在一个误区: s只是一个String对象的引用,并不是对象本身。对象在内存中是一块内存区,成员变量越多,这块内存区占的空间越大。引用只是一个4字节的数据,里面存放了它所指向的对象的地址,通过这个地址可以访问对象。
      也就是说,s只是一个引用,它指向了一个具体的对象,当s=“123456”; 这句代码执行过之后,又创建了一个新的对象“123456”, 而引用s重新指向了这个新的对象,原来的对象“ABCabc”还在内存中存在,并没有改变。内存结构如下图所示:

    为什么String对象是不可变的?

    JDK1.7中String类的主要成员变量就剩下了两个:

    1 public final class String
    2     implements java.io.Serializable, Comparable<String>, CharSequence {
    3     /** The value is used for character storage. */
    4     private final char value[];
    5  
    6     /** Cache the hash code for the string */
    7     private int hash; //

    由以上的代码可以看出, 在Java中String类其实就是对字符数组的封装。JDK6中, value是String封装的数组,offset是String在这个value数组中的起始位置,count是String所占的字符的个数。在JDK7中,只有一个value变量,也就是value中的所有字符都是属于String这个对象的。这个改变不影响本文的讨论。 除此之外还有一个hash成员变量,是该String对象的哈希值的缓存,这个成员变量也和本文的讨论无关。在Java中,数组也是对象(可以参考我之前的文章java中数组的特性)。 所以value也只是一个引用,它指向一个真正的数组对象。其实执行了String s = “ABCabc”; 这句代码之后,真正的内存布局应该是这样的:

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

     1 public static void testReflection() throws Exception {
     2         
     3         //创建字符串"Hello World", 并赋给引用s
     4         String s = "Hello World"; 
     5         
     6         System.out.println("s = " + s);    //Hello World
     7         
     8         //获取String类中的value字段
     9         Field valueFieldOfString = String.class.getDeclaredField("value");
    10         
    11         //改变value属性的访问权限
    12         valueFieldOfString.setAccessible(true);
    13         
    14         //获取s对象上的value属性的值
    15         char[] value = (char[]) valueFieldOfString.get(s);
    16         
    17         //改变value所引用的数组中的第5个字符
    18         value[5] = '_';
    19         
    20         System.out.println("s = " + s);  //Hello_World
    21     }
    打印结果为:
    s = Hello World
    s = Hello_World

        在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 也就是说,通过反射是可以修改所谓的“不可变”对象的。   

        这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能也是可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。

  • 相关阅读:
    UIStepper
    UIProgressView
    Python——GUI编程 利息计算器 作业9(python programming)
    Python——GUI编程(python programming)
    麻烦的聚餐(最长递增子序列+二分优化)
    奶牛渡河(dp)
    POJ 1182-食物链 [并查集] 《挑战程序设计竞赛》2.4
    [2013百度软件研发笔试题] 求字符串中连续出现同样字符的最大值
    Java OCR tesseract 图像智能字符识别技术
    ORA-00904:&quot;T1&quot;.&quot;AREA_ID&quot; :标识符无效
  • 原文地址:https://www.cnblogs.com/smellpawn/p/10789935.html
Copyright © 2011-2022 走看看