String 的不可变性

什么是不可变对象?

如果一个对象,在它创建完成之后,不能再改变它的状态,那么这个对象就是不可变的。不能改变状态的意思是,不能改变对象内的成员变量,包括基本数据类型的值不能改变,引用类型的变量不能指向其他的对象,引用类型指向的对象的状态也不能改变

定义一个字符串

String s = "abcd";

s 中保存了 String 对象的引用。

使用变量来赋值变量

String s2 = s;

s2 保存了相同的引用值,因为他们代表同一个对象。

字符串连接

s = s.concat("ef");

s 中保存的是一个重新创建出来的 String 对象的引用。

String 总结

一旦一个 String 对象在内存(堆)中被创建出来,它就无法被修改(因为 String 类的所有成员变量都是 private,并且没有提供 public 的 set 方法来修改这些值。此外成员变量都是 final 的,这就意味着一旦初始化就无法修改)。特别要注意的是,String 类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象

如果需要一个可修改的字符串,应该使用 StringBuffer 或者 StringBuilder。否则会有大量时间浪费在垃圾回收上,因为每次试图修改都有新的 String 对象被创建出来

String 类不可变性的好处

  1. 只有当字符串是不可变的,字符串池才有可能实现。字符串池的实现可以在运行时节约很多 heap 空间,因为不同的字符串变量都指向池中的同一个字符串。但如果字符串是可变的,那么将不能实现 String interning(指对不同的字符串仅仅只保存一个,即不会保存多个相同的字符串),因为这样的话,如果变量改变了它的值,那么其它指向这个值的变量的值也会一起改变

  2. 如果字符串是可变的,那么会引起很严重的安全问题。譬如,数据库的用户名、密码都是以字符串的形式传入来获得数据库的连接,或者在 socket 编程中,主机名和端口都是以字符串的形式传入。因为字符串是不可变的,所以它的值是不可改变的,否则黑客们可以钻到空子,改变字符串指向的对象的值,造成安全漏洞

  3. 因为字符串是不可变的,所以是多线程安全的,同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的

  4. 类加载器要用到字符串,不可变性提供了安全性,以便正确的类被加载。譬如你想加载 java.sql.Connection 类,而这个值被改成了 myhacked.Connection,那么会对你的数据库造成不可知的破坏

  5. 因为字符串是不可变的,所以在它创建的时候 hashcode 就被缓存了,不需要重新计算。这就使得字符串很适合作为 Map 中的键,字符串的处理速度要快过其它的键对象。这就是为什么 HashMap 中的键往往都使用字符串

String 对象真的不可变吗

JDK 6 String 源码:

public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence  
{  
    /** The value is used for character storage. */  
    private final char value[];  
  
    /** The offset is the first index of the storage that is used. */  
    private final int offset;  
  
    /** The count is the number of characters in the String. */  
    private final int count;  
  
    /** Cache the hash code for the string */  
    private int hash; // Default to 0  

JDK 7 String 源码:

public final class String  
    implements java.io.Serializable, Comparable<String>, CharSequence {  
    /** The value is used for character storage. */  
    private final char value[];  
  
    /** Cache the hash code for the string */  
    private int hash; // Default to 0 

String 的成员变量是 private final 的,也就是初始化之后不可改变。但是在这几个成员中, value 比较特殊,因为他是一个引用变量,而不是真正的对象。value 是 final 修饰的,也就是说 final 不能再指向其他数组对象,那么能改变 value 指向的数组吗? 比如将数组中的某个位置上的字符变为下划线 “_”。至少在我们自己写的普通代码中不能够做到,因为我们根本不能够访问到这个 value 引用,更不能通过这个引用去修改数组

那么用什么方式可以访问私有成员呢? 没错,用反射,可以得到 String 对象中的 value 属性, 进而改变通过获得的 value 引用改变数组的结构

public 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 对象发生了变化,也就是说,通过反射是可以修改所谓的“不可变”对象的。但是一般我们不这么做。这个反射的实例还可以说明一个问题:如果一个对象,它组合的其他对象的状态是可以改变的,那么这个对象很可能不是不可变对象。例如一个 Car 对象,它组合了一个 Wheel 对象,虽然这个 Wheel 对象声明成了 private final 的,但是这个 Wheel 对象内部的状态可以改变, 那么就不能很好的保证 Car 对象不可变

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 136,465评论 19 139
  • 《裕语言》速成开发手册3.0 官方用户交流:iApp开发交流(1) 239547050iApp开发交流(2) 10...
    叶染柒丶阅读 28,689评论 5 20
  • 1. Java基础部分 基础部分的顺序:基本语法,类相关的语法,内部类的语法,继承相关的语法,异常的语法,线程的语...
    子非鱼_t_阅读 34,552评论 18 399
  • 最近看了两本书, 第一本是Objc.io出的Advanced Swift和王巍大大出的Swifter tips. ...
    MD5Ryan阅读 1,617评论 0 1
  • 冬深浑噩噩,堪岁怎重来?萧瑟的风,又吹散了一年的风花雪月,而我站在这暮岁的黑夜里,寻找着那些被寒风苍白的岁月,和那...
    苦涩玛奇朵阅读 335评论 0 1

友情链接更多精彩内容