String 为什么是不可变的?

我们知道被 final 关键字修饰的类不能被继承,修饰的方法不能被重写,修饰的变量是基本数据类型则值不能改变,修饰的变量是引用类型则不能再指向其他对象。因此,final 关键字修饰的数组保存字符串并不是 String 不可变的根本原因,因为这个数组保存的字符串是可变的(final 修饰引用类型变量的情况)。

String 真正不可变有下面几点原因:

  1. 线程安全:由于 String 的不可变性,它自动就是线程安全的。这意味着你可以在多线程环境中自由地共享 String 对象,而不需要进行额外的同步处理。不可变性消除了并发修改数据的风险,从而简化了多线程编程。

  2. 缓存和字符串常量池:Java 虚拟机(JVM)为了提高性能和减少内存消耗,会对 String 对象进行缓存。特别是,JVM 会维护一个字符串常量池(String Constant Pool),它存储了所有独特的字符串字面量。当你创建一个字符串字面量时,JVM 首先会检查常量池中是否已存在该字符串。如果存在,则直接返回常量池中该字符串的引用,而不是创建一个新的对象。由于 String 是不可变的,这种缓存机制才能安全地工作。

  3. 类加载器:Java 类加载器使用字符串来作为查找类的名称。由于 String 的不可变性,这保证了类名称的一致性和安全性。如果 String 是可变的,那么一个类名可能在运行时被意外地修改,从而导致类加载失败或安全漏洞。

  4. 安全性:String 的不可变性有助于防止一些基于修改字符串的攻击,如利用字符串的哈希码进行攻击。如果 String 是可变的,那么攻击者可能会修改字符串的内容,从而影响其哈希码,进而影响到基于哈希码的安全机制,如 HashMap 的行为。

  5. 设计决策:Java 的设计者选择将 String 设计为不可变的,也是基于一种“简单即是美”的哲学。不可变性减少了类的复杂性,使得 String 类更加简洁和易于理解。同时,它也鼓励了更加清晰和安全的编程实践。

总结: 

        在某些情况下,如果你需要频繁地修改字符串,那么使用 StringBuffer 或 StringBuilder 可能会更加高效。这两个类都是可变的,并且提供了丰富的字符串修改方法。然而注意,在多线程环境中,应该使用 StringBuffer 来确保线程安全,或者如果确定代码是单线程的,那么使用 StringBuilder 能够获得更好的性能。

举一个使用String的不可变性的例子,同时也展示了字符串常量池的工作原理:


	public class StringImmutabilityExample { 

	public static void main(String[] args) { 

	// 创建两个字符串字面量,它们实际上会指向字符串常量池中的同一个对象 

	String str1 = "Hello"; 

	String str2 = "Hello"; 

	


	// 比较两个字符串的引用是否相同 

	System.out.println(str1 == str2); // 输出: true,因为它们指向常量池中的同一个对象 

	


	// 使用new关键字创建字符串对象,它不会检查字符串常量池 

	String str3 = new String("Hello"); 


	// 比较str1和str3的引用是否相同 

	System.out.println(str1 == str3); // 输出: false,因为str3是在堆上创建的新对象 

	// 尽管str3的内容与str1相同,但由于它们是不同的对象,所以不能直接通过==比较内容是否相等 

	// 但可以使用equals()方法来比较字符串的内容 

	System.out.println(str1.equals(str3)); // 输出: true,因为它们的内容相同 

	// 尝试修改str1的内容(注意:这实际上会创建一个新的String对象) 

	String str4 = str1 + " World"; 


	// str1的内容并没有改变 

	System.out.println(str1); // 输出: Hello 

	


	// str4是新的String对象,包含了"Hello World" 

	System.out.println(str4); // 输出: Hello World 

	


	// 再次验证str1和str4的引用不同 

	System.out.println(str1 == str4); // 输出: false 

	} 

	}

在这个例子中,首先创建了两个字符串字面量str1str2,都指向字符串常量池中的同一个"Hello"对象。然后,使用new String("Hello")创建了一个新的String对象str3,它不在常量池中,而是在堆上。尽管str3的内容与str1相同,但它们是两个不同的对象,因此str1 == str3的结果是false。接下来,尝试通过str1 + " World"来“修改”str1的内容,但实际上这创建了一个新的String对象str4,其内容为"Hello World"。注意,原始的str1对象并没有被修改;它仍然指向常量池中的"Hello"对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值