面试题答案
一键面试原理
- String的不可变性:在Java中,String类被设计为不可变的。这意味着一旦一个String对象被创建,其内部字符序列就不能被改变。它的底层实现是通过一个
final char[]
数组来存储字符序列。例如:
由于public final class String { private final char value[]; // 其他代码 }
value
数组是final
的,所以不能重新分配内存指向新的数组,并且数组内容也不能被修改(因为外部无法直接访问value
数组)。 - 反射修改尝试:反射机制可以绕过Java语言的访问修饰符限制,访问对象的私有字段。当试图通过反射修改String对象的
value
数组时,例如:
这段代码试图获取import java.lang.reflect.Field; public class StringReflectionExample { public static void main(String[] args) throws Exception { String str = "Hello"; Field field = String.class.getDeclaredField("value"); field.setAccessible(true); char[] value = (char[]) field.get(str); value[0] = 'J'; } }
String
类的value
字段并修改其内容。
影响
- 运行时异常:在Java 9及以后的版本中,上述代码会抛出
java.lang.reflect.InaccessibleObjectException
异常,因为Java 9对反射访问进行了增强的安全控制,限制对java.lang
包下核心类的私有字段的反射访问。 - 在旧版本(Java 8及之前)的行为:在Java 8及之前,代码看似可以修改
value
数组的内容,但是这会破坏String
的不可变特性。例如,上述代码执行后,str
的值看似变为了Jello
。然而,这种修改会影响到所有引用该字符串字面量的地方。因为字符串常量池的存在,多个相同的字符串字面量在常量池中共享同一个对象。比如:
这里String str1 = "Hello"; String str2 = "Hello"; // 通过反射修改str1的value数组 Field field = String.class.getDeclaredField("value"); field.setAccessible(true); char[] value = (char[]) field.get(str1); value[0] = 'J'; System.out.println(str2); // 输出Jello
str2
原本指向常量池中的"Hello"
字符串,由于反射修改了str1
所指向对象的value
数组,str2
的值也被改变了,这会导致程序逻辑出现混乱。
安全问题
- 安全漏洞:如果一个程序依赖于
String
的不可变性来保证安全性,例如在密码验证、权限控制等场景中,通过反射修改String
对象的值可能会绕过安全检查。比如,一个系统使用String
存储密码,并依赖其不可变性来确保密码的完整性。如果攻击者可以通过反射修改密码字符串,就可能导致非法访问。 - 数据一致性问题:在多线程环境下,
String
的不可变性有助于保证数据的一致性。如果通过反射破坏了这种不可变性,可能会导致线程间数据不一致的问题。例如,一个线程正在使用某个String
对象进行操作,另一个线程通过反射修改了该String
对象的值,会使第一个线程的操作结果不可预测。