是否可变,String类能否被继承
- 重点,String是不可变的!String类不能被继承!
- String类是不可变的。一个String实例,创建完成后就无法改变。
- 能改变的只是String的引用,状态,对象内的成员变量都无法改变,无论是基本数据类型还是引用数据类型。
区分对象和对象引用
//一个字符串,s1这个是一个存放物理地址的引用对象,s1———》指向 "hello"
String s1 = "hello";
System.out.println(s1);
//这里实际情况是新创建了一个字符串“world”, s1————》指向改变"world"
s1 = "world"
System.out.println(s1);
结果:
hello
world
- 进一步说明时候String不可变
String s1 = "hello";
System.out.println(s1);
//将字符e替换成a,实现是替换之后,创建了一个新的String对象,并不影响s1
s1.repalce('e','a');
System.out.println(s1);
结果:
hello
hello
- 补充说明一点,引用数据类型的引用,根据底层实现,其实是一个引用–>另一个引用–>堆区内存中保存的‘对象’
String类的final
- String 类在 JDK 7 源码
//可以看出 String 是 final 类型的,表示该类不能被其他类继承。
//String 字符串是常量,其值在实例创建后就不能被修改,但字符串缓冲区支持字符串的引用的改变
public final class String implements java.io.Serializable, Comparable<String>, CharSequence{
...
}
- String的成员变量
- 这是一个字符数组,并且是 final 类型,用于存储字符串内容。 fianl 关键字修饰,一旦被创建,其值无法改变。
String 其实是基于字符数组 char[] 实现的。
/**
The value isused for character storage.
*/
private final char value[];
看上去像改变String的方法其实无法改变String
- String的replace(char oldChar, char newChar)方法。
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++;
}
//这行代码是关键,返回的是一个新的String。
return new String(buf, true);
}
}
return this;
}
- 其他方法的内部都是重新创建新的String对象,并且返回这个新的对象,原来的对象无法改变。这也是replace, substring,toLowerCase等方法都存在返回值的原因。
String对象真的不可变吗?
- 现在我知道String的成员变量是private final ,无法改变。那么在这几个成员中, value比较特殊,因为是一个引用变量,而不是真正的对象。value是final修饰的,也就是说final不能再指向其他数组对象,那么我能改变value指向的数组吗? 比如将数组中的某个位置上的字符变为下划线“_”。 至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个value引用,更不能通过这个引用去修改数组。
那么用什么方式可以访问私有成员呢? 没错,用反射, 可以反射出String对象中的value属性, 进而改变通过获得的value引用改变数组的结构。下面是实例代码:
public static void testReflection() throws Exception {
//创建字符串"Hello World", 并赋给引用s
String s = "Hello World";
System.out.println("s = " + s); //Hello World
//获取String类中的value字段
Field valueFieldOfString = String.class.getDeclaredField("value");
//改变value属性的访问权限
valueFieldOfString.setAccessible(true);
//获取s对象上的value属性的值
char[] value = (char[]) valueFieldOfString.get(s);
//改变value所引用的数组中的第5个字符
value[5] = '_';
System.out.println("s = " + s); //Hello_World
}
结果为:
s = Hello World
s = Hello_World
- 在这个过程中,s始终引用的同一个String对象,但是再反射前后,这个String对象发生了变化, 正常使用String的时候,是不可能会去用到映射的,所以,这只是知识体系中的知识点,并不是编程代码的应用。这个反射的实例还可以说明一个问题:如果一个对象,他组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个Car对象,它组合了一个Wheel对象,虽然这个Wheel对象声明成了private final 的,但是这个Wheel对象内部的状态可以改变, 那么就不能很好的保证Car对象不可变。