如何优雅的创建一个Java不可变对象类,JDK源码中也是这么干的!

前面有篇文章当介绍了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类:
在这里插入图片描述

扫码关注
头条号:  微信公众号:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值