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的原生类型,或原生类型数组,是不可变的。之后所做的操作都会新生成一个对象来返回。那么无论你对传入参数做了什么操作,都不会影响原来的值。