要真正理解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
/** use serialVersionUID from JDK 1.0.2 for interoperability */
private static final long serialVersionUID = -6849794470754667710L;
/**
* Class String is special cased within the Serialization Stream Protocol.
*
* A String instance is written into an ObjectOutputStream according to
* <a href="{@docRoot}/../platform/serialization/spec/output.html">
* Object Serialization Specification, Section 6.2, "Stream Elements"</a>
*/
private static final ObjectStreamField[] serialPersistentFields =
new ObjectStreamField[0];
- 上面是String源码里的属性,首先可以看到,String类是被final修饰的,意味着它不可被继承
- 它的几个属性都是私有的,且没有提供setter和getter方法
- 它的属性除了hash都是常量(这并不影响它的不可变性,字符串的hashcode方法被重写过,它的无参构造会创建一个空字符串,此时hash为默认值0,它的有参构造会将参数的hash值赋值给此对象,但其实此对象直接指向字符串常量池里参数所在的位置)
其实String类真正的核心属性只有一个,那就是长度不可变的字符数组value,再看下源码里的构造方法
public String() {
this.value = "".value;
}
/**
* 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);
}
/**
* Allocates a new {@code String} that contains characters from a subarray
* of the character array argument. The {@code offset} argument is the
* index of the first character of the subarray and the {@code count}
* argument specifies the length of the subarray. The contents of the
* subarray are copied; subsequent modification of the character array does
* not affect the newly created string.
*
* @param value
* Array that is the source of characters
*
* @param offset
* The initial offset
*
* @param count
* The length
*
* @throws IndexOutOfBoundsException
* If the {@code offset} and {@code count} arguments index
* characters outside the bounds of the {@code value} array
*/
public String(char value[], int offset, int count) {
if (offset < 0) {
throw new StringIndexOutOfBoundsException(offset);
}
if (count <= 0) {
if (count < 0) {
throw new StringIndexOutOfBoundsException(count);
}
if (offset <= value.length) {
this.value = "".value;
return;
}
}
// Note: offset or count might be near -1>>>1.
if (offset > value.length - count) {
throw new StringIndexOutOfBoundsException(offset + count);
}
this.value = Arrays.copyOfRange(value, offset, offset+count);
}
- String的构造方法很多,但每次都被初始化的属性就是我们上面说的核心属性value,可以说String类和这个字符数组息息相关
- 感兴趣的可以去看String的其他常用方法,底层也是围绕value这个数组来操作的
可以说String类的不可变性就是因为这个value被final修饰了
首先value是长度不可变的数组
它是一个引用数据类型,被final修饰的引用,引用的地址不可被更改
指向的地址不可被更改,长度不可变,更改数组的元素又没有提供对应的setter和getter方法
所以注定了String类的不可变