前面有篇文章当介绍了Java的不可变对象的一些特性,以及它的一些好处,但是并没有介绍如何实现一个不可变对象类。今天就来看看如何实现一个不可变对象类。
Java中常用的不可变对象类
String类应该是我们最常使用的不可变对象类。其实除了String以外,在JDK中还定义了很多不可变对象类,比如:基础类型的封装类(Integer、Long等)、时间相关的类(LocalDate、LocalTime等)、不可修改集合(UnmodifiableMap、UnmodifiableList)。
如果我们想要实现一个自定义的不可变对象类,那么这些JDK提供的不可变对象类就是我们的基石,自定义的类需要基于这些基础类来实现。
首先我们需要分析一下JDK中的不可变对象类的特点,然后才好实现我们自己的不可变对象类。
- 所有的class都被final修饰,防止被继承。
- 所有的字段都被final修饰,防止被内部逻辑修改,或者被直接反射修改(String有一点点特别,不过不关键)。
- 所有的字段都没有setter方法,任何涉及到对当前对象进行修改的操作都会重新创建一个新对象,原来的对象不会进行修改(用过String相关操作的朋友肯定很清楚)。
- 字段的getter根据实际情况会选择直接返回字段的值或者拷贝一份进行返回(JDK中基本上都是直接返回字段值的)。
上面提到的前3点基本上都没有任何疑问了,都是用来防止类字段被外部修改的。第四点提到了有时候可以直接返回字段的值,而有时候就只能拷贝一份数据进行返回就比较难以理解了。那么什么时候可以直接返回,什么时候需要进行拷贝以后才能返回呢?看完这篇文章基本就能弄明白了。
定义一个简单的不可变对象类
上面这个就是一个非常简单的不可变对象类。类被final修饰,字段被final修饰,没有setter方法,getter直接返回字段的值。它完全满足上面提到的不可变对象类的设计原则,也确实是一个不可变的对象类。
再加一点料
这种简单场景显然不能满足我们探索的欲望,接下来我们分析一个稍微复杂一点的场景。我们给Student类增加一个字段Age,Age的定义如下:
按照上面的不可变对象设计原则,我们可以把Student类就变成如下所示的了:
那么新的Student类是一个不可变对象类吗?它并不是!下面的代码就说明了这一点:
最后你会发现张三的年龄被修改了,这显然不是我们想要的。所以我们需要对Student类进行改造,改造后的Student类如下:
新的Student类显然不会出现上面的问题了,那么它已经是一个不可变对象类了吗?仍旧不是!下面的代码说明了这一点:
运行完这段代码后,你会发现张三的年龄还是被修改了,这显然也不是我们想要的效果。所以我们需要进一步的完善:
终于Student类切断了age字段与外部的联系了,马马虎虎算一个不可变对象类了(在万能的反射面前什么都不是)。
经过几次修改以后,终于出现了上面我们提到的getter方法需要进行拷贝后才能返回的情形了。当字段的类型(Age)不是不可变对象类的时候getter方法是不能直接返回字段值的,需要进行拷贝后返回。由于字段Age是可变的,所以在一般的反射面前是毫无招架之力的,同时由于每次获取获取的时候都会创建新对象,在高并发的时候内存GC也会成为问题(肯定有人和你说过不要在for循环中直接拼接String,而要用StringBuilder这种类来完成—不过String拼接现在已经会被编译器优化了,可以放心大胆的拼了)。
所以:不可变对象类的字段尽量不要是可变对象!
真正的不可变对象
上面实现了一个勉强算是不可变对象类,这显然还是无法满足我们探索的欲望的,我们希望的是实现一个真正的不可变对象类,想JDK里的类那么的严谨。所以我们需要进一步的优化:
这样我们的Student类就是一个完善的不可变对象类了,普通的反射是无法改变其不可变性,只有变态的二次反射(Field也是一个对象,它的final修饰可以通过反射干掉)才能修改它的值了。
需要使用到List、Map等集合时如何实现不可变?
上面实现的不可变对象类的字段都是普通的对象,那么如果一个不可变对象字段是List或者是Map时该如何处理呢?这种情况其实很好解决,JDK实际上是为我们提供了很多Unmodifiable开头的集合类的(jdk1.2的时候就提供了,不过八成很多人都不知道),顾名思义就是不能被修改的集合,当发生add、insert、remove的时候会直接抛出异常!这些类基本上都定义在Collections类中:
然而这些类在使用的时候是有一些坑的(具体什么坑?感兴趣的可以分析朋友可以分析它们的源码看看)。Google提供的Guava工具包为我们提供了更加好用的不可变集合类,我们可以选择使用它们,感兴趣的朋友可以去看看Guava的相关文档。
JDK中典型的不可变对象实现
LocalDateTime、LocalDate、LocateTime(它是Java8才有的类哦)是一组和上面示例很像的不可变对象类,他们的主要实现代码(已经删除了各种注释和无关的方法):
LocalDate类:
LocalTime类:
LocalDateTime类:
扫码关注
头条号: 微信公众号: