再谈不可变对象实现和优缺点
所谓对象的本质之一,就是一个装了状态的容器。
可以想想一个映射到现实世界中的订单对象。
如果这个容器的状态是可变的,就称之为可变对象,否则称之为不可变对象。之前的文章里反复强调了不可变对象的好处。
这里再做一个简单的总结。怎样实现一个不可变对象
不可变对象具体的实现方式也很简单。
如果用的是Java8这样的老版本,可以把每个字段都标记为final,或者干掉所有等价于set操作的公共方法。
如果用的是Java17这样的新版本,直接用档案类(record class)就可以了。 不可变对象的设计权衡
1. 优点
不可变对象可以被更随意的传递
在单线程的情况下,不用担心这个对象在经过某个方法之后,被莫名其妙的修改了某些属性。如果是可变对象,为了防止属性变化,需要对该对象做一次拷贝,然后把副本传进去。
在多线程的情况下,一旦对象构建完成,就不用再担心这个对象被多个线程同时处理时,遇到数据竞争(data racing)的问题。
想想synchronized或者ReentrantLock吧。如果是面试的时候,白板编程,可能大部分人连单词都拼不对,更别提针对它们的八股文了。
可变对象不宜作为HashMap/HashSet的Key
根据HashMap的原理,在计算哈希值的时候,会根据对象的状态(也就是某些或者所有字段)来计算。
因此,如果用一个可变对象作为key,一旦对象的状态被改变,相信的哈希值也肯定会跟着变,根据某个key就找不到原先的记录了。而不可变对象就不会有这个问题。
2. 缺点
对象会创建的特别多
比如:
String greeting = "Hello"
greeting += ", World!"
因为String类型在Java里是不可变的,因此底层会创建不止一个对象。
这个现象在Spark里特别明显,每一次针对RDD的操作,不论是filter、map还是group、join等,都会生成一个新的RDD。
多说一句:可以用设计模式里的享元模式(Flyweight Design Pattern),部分解决这个问题。
可能存在性能瓶颈
因为要频繁的拷贝对象,所以性能相对比直接update一个属性会差一点。
多说一句:如果是非中间件类的开发,这点额外的消耗,相对于一次RPC调用,完全可以忽略不计。