zoukankan      html  css  js  c++  java
  • 为什么String类是不可变的?

    为什么String类是不可变的?
    #

    String类

    什么是不可变对象

      当满足以下条件时,对象才是不可变的:

      这是《Java并发编程实战》一书中的定义。在书中,说明并不是一定要将所有的域都设为final类型,比如String类就是这种情况,String会将散列值的计算推迟到第一次调用hashCode()时进行,并将计算得到的散列值缓存到非final类型的域中,但这种方式之所以可行,是因为在每次计算时都得到相同的结果,这基于一个不可变的状态(书中指出:自己编写代码时不要这么做,推荐全都设为final)。
      因此,不可变对象可以理解为:如果一个对象,在它正确创建完成之后,不能再改变它的状态(包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变),那么这个对象就是不可变的。

    “不可变的对象”与“不可变的对象引用”区别

    比如:

    String str = "test";
    str = "test1";
    

    我们从下图可以看到,当定义String str = "test1"时,其实不是真正改变了str的内容,而是改变了str的引用。

      那么何为"不可变的对象引用"呢?final只保证引用类型变量所引用的地址不会改变,即一直引用同一个对象,但是这个对象的内容(对象的非final成员变量的值可以改变)完全可以发生改变(比如final int[] intArray;,intArray不允许再引用其他对象,但是intArray内的int值却可以被修改)。

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

      要理解String的不可变性,首先看一下String类中都有哪些成员变量。 在JDK1.8中,String的成员变量有以下几个:

    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
    

      其中,成员变量hash并没有用final声明,但是由于第一次调用hashCode()会重新计算hash值,并且以后调用会使用已缓存的值,当然最关键的是每次计算时都得到相同的结果,所以也保证了对象的不可变。

        public int hashCode() {
            int h = hash;
            if (h == 0 && value.length > 0) {
                char val[] = value;
    
                for (int i = 0; i < value.length; i++) {
                    h = 31 * h + val[i];
                }
                hash = h;
            }
            return h;
        }
    

      在Java中,数组也是对象, 所以value也只是一个引用,它指向一个真正的数组对象。其实执行了String s = “ABCabc”; 这句代码之后,真正的内存布局应该是这样的:

      value是String封装的数组,value中的所有字符都是属于String这个对象的。由于value是private的,并且没有提供setValue等公共方法来修改这些值,所以在String类的外部无法修改String。也就是说一旦初始化就不能修改。此外,value变量是final的, 也就是说在String类内部,一旦这个值初始化了,value引用类型变量所引用的地址不会改变,即一直引用同一个对象。所以可以说String对象是不可变对象。但其实value所引用对象的内容完全可以发生改变(反射消除String类对象的不可变特性)。

    如何理解substring, replace, replaceAll, toLowerCase等方法

    比如:

    String a = "ABCabc";  
    System.out.println("a = " + a);  
    a = a.replace('A', 'a');  
    System.out.println("a = " + a);  
    

      a的值看似改变了,其实也是同样的误区。再次说明, a只是一个引用, 不是真正的字符串对象,在调用a.replace('A', 'a')时, 方法内部创建了一个新的String对象,并把这个新的对象重新赋给了引用a。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);
                }
            }
            return this;
        }
    

    String类不可变性的好处

    1. 只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多heap空间,因为不同的字符串引用可以指向池中的同一个字符串。但如果字符串是可变的,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变。
    2. 如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入数据库,以获得数据库的连接,或者在socket编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞。
    3. 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。
    4. 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载java.sql.Connection类,而这个值被改成了myhacked.Connection,那么会对你的数据库造成不可知的破坏。
    5. 因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算,这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串的原因。
  • 相关阅读:
    python数字
    python字符串方法
    python操作符与流程控制
    网络基础和python(二)
    网络基础和python
    Ubuntu源更新
    make和makefile介绍
    JavaScript
    redis mac安装配置
    网络编程之socket(TCP,UDP)
  • 原文地址:https://www.cnblogs.com/xzwblog/p/7230366.html
Copyright © 2011-2022 走看看