面试必问:Java中String类型为什么是不可变的?
引言:Java面试八股文里,String的不可变性堪称经典送分(或送命)题。你以为你懂了?来,先看段代码:
问题:代码里a = "什么是牛马"
,String对象真被修改了吗?错!这好比把杯子里的可乐倒掉换成雪碧,杯子还是那个杯子,但饮料早就换了——a只是个引用,真正的String对象(可乐/雪碧)在堆里纹丝不动。
深入:String的不可变,本质是它的value[]
数组被final+private双锁封印,还没钥匙(set方法)。你以为final锁的是数组内容?天真!它锁的是数组的地址,至于数组里的字符,想改?门儿都没有——除非你祭出反射这把“物理外挂”。
脑洞时刻:为啥Java要这么设计?举个栗子:假设String可变,你往HashSet里丢了个"java",然后偷偷把它改成"PHP",结果哈希值突变,Set直接原地爆炸——找不着对象了!再比如多线程下,String不可变才能安心当个“共享渣男”,否则分分钟数据错乱。
终极骚操作:用反射强行改value[]?行啊!但这就好比用锤子修瑞士手表,改完的String会让JVM字符串池里的所有相同字面量集体躺枪,堪称“一人作死,全网陪葬”。面试时说完这句,记得补一句:“当然,这么干会被同事祭天”——格局瞬间打开!
面试必问:Java中的String为什么是不可变的?
面试必问:Java中String类型为什么设计成不可变的?
省流:String是一个类,通过value[]存储字符串,但是value被private和final修饰,也没有set方法,所以字符串无法修改,硬要修改的话,可以利用反射机制修改value数组元素
明明可以修改字符串,为什么就说是不能修改呢?
根据下面实例,我发现了String对象
明明可以修改!请问你真的修改的了String对象
吗?
public class Test{public static void main(String[] args){String a = "牛马问题";System.out.println(a);a = "什么是牛马";System.out.println(a);}
}
输出:
牛马问题
什么是牛马
其实我在上面已经两次对String对象
进行标注了,那你觉得上述示例中哪个才是String对象
呢?是a?还是“牛马问题”?还是“什么是牛马”?下面我来给你们再细化一下这段代码。
public class Test{public static void main(String[] args){String a = new String("牛马问题");System.out.println(a);a = new String("什么是牛马");System.out.println(a);}
}
输出:
牛马问题
什么是牛马
有的童鞋在心中有些思路了,但也有可能还存在疑虑。其实String a不是String 对象
,真正的String对象
是通过new String()
在JVM的堆中创建出来的,String a只是String对象
的引用。所以不要再纠结为什么String a可以改变了。
那么我们就讨论一下String对象为什么不能改变
String对象为什么不能改变,顾名思义,就是String对象的值不能改变。我们先来看看String的值是如何存储的,再看看为什么不能改变存储的值。
String 对象的值一般通过value数组变量存储,我们可以看到这个数组被final
修饰,那就是说无法改变value数组的地址,从一而终。
/ The value is used for character storage. */private final char value[];
那有同学就问了,我们可以改变value数组里面的值呀?真是个大聪明,你看看String类里面这个value数组变量是不是被private
修饰了,那我们只能通过set()方法来改变value数组的元素。可是String类没有提供value数组变量的set()方法!
这下就给你讲明白了为什么String类型是不可变的
什么?它不给你改就是不改了?我非要改!
面试的时候我们只是解释上面的原因其实不是那么尽善尽美,想要更好的去加薪去装逼,我们还需更进一步回答。
别忘了我们的反射机制,在通常情况下,他可以做出一些违反语言设计原则的事情。这也是一个技巧,每当面试官问一些违反语言设计原则的问题,你就可以拿反射来反驳他。
public class Test{public static void main(String[] args){String str = "牛马问题";System.out.println(str);//通过发射获取String内部value数组变量Field field = String.class.getDeclareField("value");//作用就是能够正常的方位私有属性field.setAccessible(true);char[] value = (char[])field.get(str);//把字符串第一个字符改变value[0] = '神';}