String那些事

257 查看

1. String的不可变性

众所周知,String是常量,不可变,这一点很重要。
其底层的实现是char[]

    /** The value is used for character storage. */
    private final char value[];

而且,它是final的。

看两个构造函数:

    /**
     * Initializes a newly created {@code String} object so that it represents
     * the same sequence of characters as the argument; in other words, the
     * newly created string is a copy of the argument string. Unless an
     * explicit copy of {@code original} is needed, use of this constructor is
     * unnecessary since Strings are immutable.
     *
     * @param  original
     *         A {@code String}
     */
    public String(String original) {
        this.value = original.value;
        this.hash = original.hash;
    }

    /**
     * Allocates a new {@code String} so that it represents the sequence of
     * characters currently contained in the character array argument. The
     * contents of the character array are copied; subsequent modification of
     * the character array does not affect the newly created string.
     *
     * @param  value
     *         The initial value of the string
     */
    public String(char value[]) {
        this.value = Arrays.copyOf(value, value.length);
    }

其内部的很多方法,也是会新生成一个char[]来构造一个新的String对象返回。
例如replace:

    /**
     * Returns a new string resulting from replacing all occurrences of
     * <code>oldChar</code> in this string with <code>newChar</code>.
     */
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;
    }

doc中写的很明白,这个方法,会新生成一个String对象返回。

所以类似

str = str.replace('a','b');
str = str.substring(2);
str = str.toLowerCase();

都会生成新的对象。原来的对象还是存在的,只是没有引用指向它,等待被垃圾回收。

2.传递String类型参数

看一个例子:

public class StringTest {
    public static void main(String[] args) {
        StringTest stringTest = new StringTest();
        
        String string = "abc";
        stringTest.replace(string);
        System.out.println(string);

        Integer i = 1;
        stringTest.add(i);
        System.out.println(i);
    }

    public void replace(String string) {
        string = string.replace('a', 'b');
        string = string.toUpperCase();
        System.out.println("inner:" + string);
    }

    public void add(Integer integer) {
        integer++;
        System.out.println("inner:" + integer);

    }
}

Stirng内部实现时,是用char[] 来存储字符串的,所以String相当于char[]的包装类。那java中,包装类的一个特质就是值操作时体现对应的基本类型的特质。同样的,Integer、Float等这些包装类和String在这种情况下的表现是相同的。

分析一下,主要是还是由于包装类内部实现方式导致的。
包装类内部存储结构是final的原生类型,或原生类型数组,是不可变的。之后所做的操作都会新生成一个对象来返回。那么无论你对传入参数做了什么操作,都不会影响原来的值。