1. 前言:
(1) 改变一个变量、改变一个变量的值,二者有何区别?
改变一个变量:将该变量指向另一个值的存储空间。
改变一个变量的值:将该变量当前指向的值的存储空间中写入一个新的值。
(2)“Change is a necessary evil.”变化是“罪恶”,但程序不能没有变化,
要尽量避免变化,来避免程序中的副作用
(3) 下面介绍java数据类型的Mutability与Immutability。
2. 不变性(Immutability):
(1)不变性是一个重要的设计原则
(2)不变数据类型:一旦被创建,其值不能改变。
(3)如果是引用类型,也可以是不变的:一旦确定其指向的对象,就不能再被改变
我们可以使用final关键字来实现这一点:
a. 如果编译器无法确定final变量不会改变,就提示错误,这也是静态类型检查的一
部分。
b. 所以,尽量使用 final变量作为方法的输入参数、作为局部变量。
c. final表明了程序员的一种“设计决策”。
需要注意的点:
- final类无法派生子类
- final类无法改变值/引用
- final方法无法被子类重写
3. 不变性与可变性:
满足不变性的对象:一旦被创建,始终指向同一个值/引用
满足可变性的对象:拥有方法可以修改自己的值/引用
(1) 例子:String与StringBuilder
a. String即为一个不可变的数据类型,一但创建,其值无法更改。
b. 为了在一个String后面添加字符,需要新建一个String对象。
例如:concat方法执行过程:
c. 而StringBuilder是一个可变类型,其具有修改内部值的方法。
例如:append方法执行过程:
d. 其之间的差异在只有一个引用指向该值时,没有区别。
然而,在多个引用的时候,差异就出现了。
t并不能改变s指向的值,而tb是可以改变sb指向的值的。
(2) 可变数据类型的优点与潜在风险:
优点:
a. 使用 不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收) ,而可变类型
最少化拷贝以提高效率。
b. 使用可变数据类型,可获得更好的性能,也适合于在多个模块之间共享数据。
但可变类型潜在风险,而不可变类型更“安全”,在其他质量指标上表现更好。
选取时,要进行折中,看你看中哪个质量指标。
风险举例:
a. 传递可变类型值时:
该函数超出了spec范畴,因为其改变了输入参数的值。且这种错误非常难以跟踪和发现。
对于其他程序员来说,也难以理解。
b. 返回可变类型值时:
在这种情况中,使用的Date是mutable的,因此对其返回值进行更改会导致其对象的值发生变化。
要避免这点,就应该使用java.time中的其他immutable的类,如:LocalDateTime与Instant
或者在返回date类型时,进行防御性拷贝:
(3)防御性拷贝
接上面的例子2,通过防御性拷贝,给客户端返回一个全新的Date对象,这样就避免了表
示泄露,即避免了对原数据的修改。
但是:大部分时候拷贝不会被客户端修改, 可能造成大量的内存浪费。
如果使用不可变类型,则节省了频繁复制的代价。
(4)安全地使用可变类型:局部变量,不涉及共享,只有一个引用。
如果有多个引用(别名),使用可变类型就非常不安全。