的不变性
在Java中特别常用,相信很多人都看过他的源码,在JDK中,关于的类声明是这样的:
<pre class="syl-page-code">public final class String
implements java.io.Serializable, Comparable, CharSequence {
}
可以看到,类是final类型的,那么也就是说,是一个不可变对象。
不可变对象是在完全创建后其内部状态保持不变的对象。这意味着,一旦对象被赋值给变量,我们既不能更新引用,也不能通过任何方式改变内部状态。
可是有人会有疑惑,为什么不可变,我的代码中经常改变的值啊,如下:
String s = "abcd";
s = s.concat("ef");
这样,操作,不就将原本的"abcd"的字符串改变成""了么?
但是string类,虽然字符串内容看上去从"abcd"变成了"",但是实际上,我们得到的已经是一个新的字符串了。
如上图,在堆中重新创建了一个""字符串,和"abcd"并不是同一个对象。
所以,一旦一个对象在内存(堆)中被创建出来,他就无法被修改。而且,类的所有方法都没有改变字符串本身的值,都是返回了一个新的对象。
如果我们想要一个可修改的字符串,可以选择 或者 这两个代替。
为什么要设计成不可变
在知道了"是不可变"的之后,大家是不是一定都很疑惑:为什么要把设计成不可变的呢?有什么好处呢?
这个问题,困扰过很多人,甚至有人直接问过Java的创始人James 。
在一次采访中James 被问到什么时候应该使用不可变变量,他给出的回答是:
I would use an I can.
那么,他给出这个答案背后的原因是什么呢?是基于哪些思考的呢?
其实,主要是从缓存、安全性、线程安全和性能等角度出发的。
缓存
字符串是使用最广泛的数据结构。大量的字符串的创建是非常耗费资源的,所以,Java提供了对字符串的缓存功能,可以大大地节省堆空间。
JVM中专门开辟了一部分空间来存储Java字符串,那就是字符串池。
通过字符串池,两个内容相同的字符串变量,可以从池中指向同一个字符串对象,从而节省了关键的内存资源。
String s = "abcd";
String s2 = s;
对于这个例子,s和s2都表示"abcd",所以他们会指向字符串池中的同一个字符串对象:
但是,之所以可以这么做,主要是因为字符串的不变性。试想一下,如果字符串是可变的,我们一旦修改了s的内容,那必然导致s2的内容也被动地改变了,这显然不是我们想看到的。
安全性
字符串在Java应用程序中广泛用于存储敏感信息,如用户名、密码、连接url、网络连接等。JVM类加载器在加载类的时也广泛地使用它。
因此,保护类对于提升整个应用程序的安全性至关重要。
当我们在程序中传递一个字符串的时候,如果这个字符串的内容是不可变的,那么我们就可以相信这个字符串中的内容。
但是,如果是可变的,那么这个字符串内容就可能随时都被修改。那么这个字符串内容就完全可信了。这样整个系统就没有安全性可言了。
线程安全
不可变会自动使字符串成为线程安全的,因为当从多个线程访问它们时,它们不会被更改。
因此,一般来说,不可变对象可以在同时运行的多个线程之间共享。它们也是线程安全的,因为如果线程更改了值,那么将在字符串池中创建一个新的字符串,而不是修改相同的值。因此,字符串对于多线程来说是安全的。
缓存
由于字符串对象被广泛地用作数据结构,它们也被广泛地用于哈希实现,如、、等。在对这些散列实现进行操作时,经常调用()方法。
不可变性保证了字符串的值不会改变。因此,()方法在类中被重写,以方便缓存,这样在第一次()调用期间计算和缓存散列,并从那时起返回相同的值。
在类中,有以下代码:
private int hash;//this is used to cache hash code.
性能
前面提到了的字符串池、缓存等,都是提升性能的提现。
因为字符串不可变,所以可以用字符串池缓存,可以大大节省堆内存。而且还可以提前对进行缓存,更加高效
由于字符串是应用最广泛的数据结构,提高字符串的性能对提高整个应用程序的总体性能有相当大的影响。
总结
通过本文,我们可以得出这样的结论:字符串是不可变的,因此它们的引用可以被视为普通变量string类,可以在方法之间和线程之间传递它们,而不必担心它所指向的实际字符串对象是否会改变。
我们还了解了促使Java语言设计人员将该类设置为不可变类的其他原因。主要考虑的是缓存、安全性、线程安全和性能等方面
限时特惠:本站持续每日更新海量各大内部创业课程,一年会员仅需要98元,全站资源免费下载
点击查看详情
站长微信:Jiucxh