大家好,欢迎来到程序视点
!我是小二哥。今天我们来来聊聊String类型对象不可变的问题。
String是Java中一个不可变的类,所以String对象一旦被实例化就无法被修改。我们知道Java中就是这样定义的。但是,为什么要这样设计呢?
从字面意思也能够理解,也就是我们的创建的对象不可改变。即,不可变类的实例一旦创建,其成员变量的值就不能被修改。为了实现创建的对象不可变,java语言要求我们需要遵守以下5条规则:
(1)类内部所有的字段都是final修饰的。 (2)类内部所有的字段都是私有的,也就是被private修饰。 (3)类不能够被集成和拓展。 (4)类不能够对外提供哪些能够修改内部状态的方法,setter方法也不行。 (5)类内部的字段如果是引用,也就是说可以指向可变对象,那我们程序员不能获取这个引用。
如果我们查看 String 类的源码,我们会发现它全部满足上面这5个规则。
/**(3)*/
public final class String
implements java.io.Serializable, Comparable<String>, CharSequence {
/** (1)(2)*/
private final char value[];
/**(1)(2)*/
private int hash; // Default to 0
......
/**(4)(5) 没有setter和getter*/
}
String 设计成不可变,主要是从性能和安全两方面考虑。
性能优化,主要体现在字符串常量池的设计和使用上。
字符串常量池(String pool)是 Java 堆内存中一个特殊的存储区域,当创建一个 String 对象时,假如此字符串已经存在于常量池中,则不会创建新的对象,而是直接引用已经存在的对象
。这样做能够减少 JVM 的内存开销,提高效率。
String s1 = "abc";
String s2 = "abc";
比如引用 s1和 s2 都是指向常量池的同一个对象 "abc",如果 String 是可变类,引用 s1 对 String 对象的修改,会直接导致引用 s2 获取错误的值。
其次是允许String对象缓存HashCode
String字符串不可变,所以在它创建的时候 hashcode就固定了,且被缓存了起来,之后不需要重新计算。这就使得字符串很适合作为 HashMap 中的 key,效率大大提高。
private int hash;//this is used to cache hash code.
大家在 String 类的源码中能看到这个成员变量。把String实例设计为不可变的,那么该实例的成员变量hash也是不会变的。
再者就是安全性上的考虑我们常用 String 字符串在其他Java类中充当参数,比如网络连接地址URL
,文件路径path
等。假若String不是固定不变的,将会引起各种安全隐患,例如,你本想访问百度,结果输入“www.baidu.com”后变成了你银行卡的转账地址;就问你心里慌不慌😓
还有就是多线程中的安全问题。多线程下,变量在多个线程之间共享。如果是可变对象,那么多线程下,它的值很可能被其他线程改变,造成不可预期的结果。而不可变的 String 可以自由在多个线程之间共享,不需要同步处理。
ps:要是多细细思量,你会发现这个String类在开发中使用的地方真的是十分频繁!
既然有这个标题。那答案肯定就是可变
。别忘了我们的反射机制。反射可是能拿到private私有的变量的。看看下面这个例子:
public class Test {
public static void main(String[] args) {
String str = "小二哥";
System.out.println(str);
try {
//我们通过反射获取内部的value字符数组
Field field = String.class.getDeclaredField("value");
field.setAccessible(true);
char[] value;
value = (char[]) field.get(str);
//把字符串第一个字符变成”大“
value[0] = '大';
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
小二哥
大二哥
我们这不也把字符串”小二哥“改成了”大二哥“了。这就是我们反射机制的使用。但我们还是常说字符串是不可变的!
(谁没事儿专门用反射去修改字符串呢?除非你心里有其他想法😓)。
好啦!今天的分享就到这里了!我们下期见。