龙空技术网

Java冷知识:String为什么要设置成不可变类型?

西二旗老四 570

前言:

眼前朋友们对“java不可变类型有哪些种类”都比较关注,你们都需要了解一些“java不可变类型有哪些种类”的相关资讯。那么小编同时在网摘上汇集了一些有关“java不可变类型有哪些种类””的相关资讯,希望咱们能喜欢,你们快快来学习一下吧!

什么是不可变对象?

《Effective Java》这本书对于不可变对象做了如下说明:

不可变对象(Immutable Object):对象一旦被创建后,对象所有的状态及属性在其生命周期内不会发生任何变化。

以上比较直白的表达了不可变对象的含义,我们常见的包装类都是不可变类型,如String、Integer、Long等。

以下举例说明

从输出结果可以看出,在对str进行了字符串替换之后,即将'x'替换为'X',str1指向的字符串对象是没有发生变化的。这也佐证了以上String为不可变类型这一说法。

String的不可变性如何保证?

String对象在堆内存(jdk1.7后字符串常量池也被划分到了堆内存中)创建后就不可改变,我们通过源码来看下Java语言的设计者是如何保证String类型的不可变性的。

可以看到如下设计细节:

String类被final关键字修饰,说明String不可被继承。String内部所有成员属性都设置为私有的。不存在成员属性的setter方法。并将value、offset和count设置为final。当传入可变数组value[]时,进行数组的深拷贝而不是直接将value[]赋值给value。获取value时不是直接返回对象的引用,而是返回对象的深拷贝副本。

以上六个方面共同保证了String类型不可变的特性。

String的不可变性有何优点?

提高运行效率,降低系统开销。主要体现在字符串常量池的设计上,字符串常量池可以将一些字符常量放在常量池中重复使用,避免每次都重新创建相同的对象、节省存储空间。但如果字符串是可变的,此时相同内容的String还指向常量池的同一个内存空间,当某个变量改变了该内存的值时,其他变量的值也会发生改变。所以不符合常量池设计的初衷。

线程安全考虑。同一个字符串实例可以被多个线程共享。这样便不用因为线程安全问题而使用同步。字符串自己便是线程安全的(不可变、无状态)。

Java语言基础设施的安全性。类加载器要用到字符串,根据类的全限定名来加载指定类到内存。全限定名就是用字符串表示的,String的不可变性提供了安全性,以便正确的类被加载。设想下如果你想加载java.sql.Connection类,而这个值被改成了hacked.Connection,那么类的加载岂不是安全乱了套了。

容器使用过程的安全和效率考虑。因为字符串是不可变的,所以在它创建的时候hashcode就被缓存了,不需要重新计算。这就使得字符串很适合作为Map中的键,字符串的处理速度要快过其它的键对象。这就是HashMap中的键往往都使用字符串。

String的不可变性有何缺点?

可能创建大量的String对象。如果有对String对象值频繁改变的需求,那么会创建大量的String对象,此时会占用较多的堆内存空间,大家在开发过程中可以使用StringBuffer和StringBuild来代替String来实现某些场景的功能,以避免创建较多的String对象。

String真的"完全不可改变"吗?

String作为不可变类型虽然具备不可变性,但是也并不是完全不可变的,通过反射就可以打破这种不可变性。举例如下:

大家看到这里可能就犯嘀咕了,既然String对象能够改变,为什么还叫不可变类型呢?这里大家可能对不可变类型有些误解,从其含义可以看出来String的不可变性是针对Java语言开发者在String的使用上的。目的是来帮助大家更简单地去编写代码,减少程序编写过程中出错的概率,这是不可变对象的初衷。而反射是Java语言另一高级特性,而非针对String的。所以就String自身而言,其不可变性的定位和表述是完全没有问题的。另外如果真要靠通过反射来改变一个对象的状态,此时编写代码的人也应该会意识到此类在设计的时候就不希望其状态被更改,从而引起编写代码的人的注意。

如果你觉得本文对你有些许帮助,欢迎关注、收藏和转发

标签: #java不可变类型有哪些种类