摘要: 从字节码和JVM的角度解析Java核心类String的不可变特性 前言 最近看到几个有趣的关于Java核心类String的问题。
翻阅了网上的一些博客和stackoverflow,结合自己的理解做一个汇总。 String类是如何实现不可变的 String类的一大特点,就是使用Final类修饰符。
Java SE 7 官方手册中的定义如上,如果你认为这个类已经定义完全并且不需要任何子类的话,可以将这个类声明为Final,Final类中的方法将永远不会被重写。 在Java中,String是被设计成一个不可变(immutable)类,一旦创建完后,字符串本身是无法通过正常手段被修改的。 选了substring方法来做一个代表,其他常见的涉及String操作的方法都是类似,如果你操作后的内容会和目前String中的内容不一致的话,那么都是重新创建一个新的String类返还,不会让你去修改内部的内容。 将String类设计成Final类,能够避免其方法被子类重写,从而破坏了它本身方法的实现,进而破坏了不可变的特性。 String类设计成不可变的好处 我们都不是Java语言的设计者,不知道其为何一定要设计成不可变,试着做一些猜想。
这是结合个人理解和stackoverflow上看的汇总,我们来看看Java语言的爸爸James Gosling是怎么说的。
这是James Gosling在2001年5月的一次访谈中,谈到了不可变类和String,大意就是 他会更倾向于使用不可变类,它能够缓存结果,当你在传参的时候,使用不可变类不需要去考虑谁可能会修改其内部的值,这个问题不存在的。如果使用可变类的话,可能需要每次记得重新拷贝出里面的值,性能会有一定的损失。 老爷子还说了,迫使String类设计成不可变的另一个原因是安全,当你在调用其他方法,比如调用一些系统级操作之前,可能会有一系列校验,如果是可变类的话,可能在你校验过后,其内部的值被改变了,可能引起严重的系统崩溃问题,这是迫使String类设计成不可变类的重要原因。 String Pool 上文说了,设计成不可变后,可以多个变量引用JVM上同一块地址,可以节省内存空间,相同的字符串不用重复占用Heap区域空间。 通常我们平时在使用字符串是,都是通过这种方式使用,那么JVM中的大致存储就是如下图所示。  两个变量同时引用了String Pool中的abc,如果String类是可变的话,也就不能存在String Pool这样的设计了。 在平时我们还会通过new关键字来生成String,那么新创建的String是否也会和上文中的示例一样共享同一个字符串地址呢。 答案是不会,使用new关键字会在堆区在创建出一个字符串,所以使用new来创建字符串还是很浪费内存的,内存结构如下图所示。  不推荐使用 来拼装字符串的原因 首先我们来看这一段代码,应该是之前写代码比较常见的。 test3通过test1和test2拼接而成,我们看一下这个过程中的字节码。
从以上图我们可以看到,目前的JDK7的做法是,会通过新建StringBuilder的方式来完成这个 号的操作。这是目前的一个底层字节码的实现,那么是不是没有使用StringBuilder或者StringBuffer的必要了呢。还是有的,看下一个例子。 在上述代码中,我们还是使用 号进行拼接,但这次我们加了一个循环,看一下字节码有什么变化。  每次循环都会创建一个StringBuilder,在末尾再调用toString返还回去,效率很低。继续看下一个例子,我们直接使用StringBuilder,来做拼接。  每次循环体中只会调用之前创建的StringBuilder的append方法进行拼接,效率大大提高。 至于StringBuilder 的内部实现,诸位有兴趣可以自己再去看一下,本质上也是一个char数组上的操作,和StringBuffer的区别在于,StringBuffer是有做同步处理的,而StringBuilder没有。 总结 本文主要探讨了String类设计为Final修饰和不可变类的原因,以及为何在日常工作中不推荐使用 号进行字符串拼接。
|
|
来自: Levy_X > 《JAVAWEB学习资料》