不变性与final字段
如果某个对象在被创建后其状态就不能被修改,那么这个对象就被成为不可变对象。
在Java中,final类型的域是不能修改的。
不可变性不等于将对象中的所有域都声明为final类型,即使对象中的所有域都是final类型的,这个对象仍然是可变的,因为在final类型的域中保存对可变对象的引用。当满足以下条件时,对象才是不可变的:
- 对象创建以后其状态就不能修改
- 对象所有的域都是final类型
- 对象是正确创建的(在对象创建期间,this引用没有逸出)
final表示无法将对象的引用更改为指向另一个引用或另一个对象,但仍然可以改变它的状态(例如使用setter方法)。不可变对象表示对象的实际值无法更改,但可以将其引用更改为另一个。
final修饰符适用于变量但不适用于对象,而不可变性适用于对象但不适用于变量。
声明final的类不可被继承。
下面举个例子:
final StringBuffer sb = new StringBuffer("hello");
sb.append("test"); // success
sb = new StringBuffer("hello world"); // failed
可以看到,sb虽然声明了final,但是仍然可以改变其值,但是不能将其指向另一个对象。
所以有以下结论:
- 将引用变量声明为final,并不意味着该对象是不可变的。如果对象是不可变的,则无法完成上述追加操作。
- final意味着无法分配新的内容给该引用变量,即前面提到的将对象的引用更改为指向另一个引用或另一个对象
线程安全
不可变对象一定是线程安全的。因为得到失效数据、丢失更新操作或者观察到某个对象处于不一致的状态等等问题,都与多线程试图同时访问同一个可变的状态相关。如果对象状态不会改变,那么问题就自然消失了。
在并发场景下,多个线程同时读一个资源,是不会引发竞态条件的。只有对资源做写操作时才有危险。不可变对象不能被写,所以线程安全。
final域能确保初始化过程的安全性,从而可以不受限制地访问不可变对象,并在共享这些对象时无需同步。
正如“除非需要更高的可见性,否则应将所有域都声明为私有域”是一个良好的编程习惯,“除非某个域是可变的,否则将其声明为final域”也是一个良好的编程习惯。
常见的不可变类比如String,是一个常见的面试问题,后面深入学习后再做介绍。