前言
我们很多人在接触到immutable这个概念的时候,应该是在学习到String这个部分。书上会反复提到,String是immutable的,所以对于它的使用要特别注意了等等。除了String的实现是immutable的,还有很多其他java类库里的class也实现了同样的特性。那么, immutable是基于一个什么样的设计思路呢?为什么要折腾出这么一个玩意来?它有什么好处呢?这里,我们结合一些具体的immutable类实例来讨论。
immutable现象
既然我们前面接触到immutable这个概念就是从String这里来的,我们就从这里开始讨论。假设我们有以下的代码:
String name = "abcdefg"; String newName = name.replace('a', 'h');
我们这里的String对象name调用了replace方法。并将结果赋值给另外一个newName对象。从我们一贯的理解方式来看,既然name.replace()方法是需要修改String内容的,是不是表示这个对象被改变了呢?如果我们分别打印name, newName的值会发现结果如下:
abcdefg hbcdefg
很显然,虽然调用了这么一个修改内容的方法,但是name对象本身其实还是没有被改变。从前面的结果里我们也可以推测到,这种修改了内容的方法实际上是返回了一个新的对象,这样使得原来的对象没有受到任何的影响。
前面这个方法的过程,对应到内存中对象的关系则如下图所示:
在开始的时候,只有name对象,其内容为"abcdefg"
而调用replace()方法之后,则变成如下:
这里的name对象通过调用replace方法之后创建了一个新的对象,只是这个对象里的内容变成了"hbcdefg"。这样,我们这里新建的这个对象并没有影响到原来的对象,原来的name里内容没有变化。从这里的讨论,我们可以理解到,immutable本质上就是要求一个对象状态不能被改变。
在前面我们举的这个示例里,用的是String对象。那么它本身是怎么保证做到immutable这么一个特性的呢?
String里immutable的实现
我们可以看看String这个类的详细定义实现:
public final class String implements java.io.Serializable, Comparable<String>, CharSequence { /** The value is used for character storage. */ private final char value[]; /** Cache the hash code for the string */ private int hash; // Default to 0 /** use serialVersionUID from JDK 1.0.2 for interoperability */ private static final long serialVersionUID = -6849794470754667710L; //... omitted }
这里我们可以看到,String类型是被定义成final的。这样它本身将不能被继承。我们也就不可能通过继承它来破坏这个immutable的特性。另外,还有两个比较有意思的成员属性分别是value和hash。value是一个char类型的数组,它的定义增加了final的修饰。对于final修饰的引用类型,我们都知道,它将在被构造函数初始化之后就不能再被修改为指向其他的引用了。hash则是一个普通的int类型。
我们来挑几个具体的方法实现看看其中的特点:
public char[] toCharArray() { // Cannot use Arrays.copyOf because of class initialization order issues char result[] = new char[value.length]; System.arraycopy(value, 0, result, 0, value.length); return result; }
这是toCharArray方法的实现。这里需要将String内容转换成一个字符数组。虽然String内部是用char[]来表示的,这里却是首先创建了一个同样长度的char[],然后再将里面value的值拷贝到新的数组里头。然后再将这个数组返回。试想一下,如果我们不通过复制里面的内容而是直接将value返回给用户会怎么样呢?
value虽然是final的,但是这只是保证value这个引用不能指向别的对象了,但是不能保证它目前所指向的对象本身不会发生变化。一旦我们使用value的客户端拥有了这个引用,我们可以通过value[x]的方式来访问甚至修改里面的内容。这样就破坏了原来对象要求的immutable特性。可见,这里做的这么一通复杂的拷贝操作就是出于这方面的考虑。
我们再看看其他的几个方法:
public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] val = value; /* avoid getfield opcode */ // 找到需要替换的字符索引位置 while (++i < len) { if (val[i] == oldChar) { break; } } if (i < len) { char buf[] = new char[len]; for (int j = 0; j < i; j++) { buf[j] = val[j]; } while (i < len) { char c = val[i]; buf[i] = (c == oldChar) ? newChar : c; i++; } return new String(buf, true); } } return this; }
这里replace的方法也和前面类似,首先找到需要替换字符的索引。然后再新建一个和value数组同样长度的数组,并在遍历value数组的时候比较,将修改后的数组构造成一个新的String对象返回。这里既然是新建立的一个对象,而且方法里也没有修改原来value的内容,显然,原来的对象是没有任何变化的。
String的具体实现里代码很多,对于大部分看似需要修改对象状态的方法,都是通过创建原有对象的拷贝,再针对拷贝进行修改,并返回新对象作为结束。有兴趣的朋友可以看看里面其他部分的代码。
设计immutable类型的思想
前面看了一下immutable的一些实现示例,那么,如果我们要实现一个immutable的类型,需要注意哪些地方呢?在Oracle的官方文档里列举了一些注意事项,这里简单的引用和讨论一下。
1. 不要提供"setter"方法,这里的setter方法指的是可以修改对象成员或者成员里包含的引用的方法。
这看来其实是想当然的,既然我们需要对象不能被改变,如果我们还定义一个方法可以修改对象里的成员,这不就直接打破了原来的设定吗?
2. 将所有成员都定义成final和private的。
这里其实结合不同的设定场景还是有一些差别的。比如说如果我们将类本身定义设定成final的,则它本身不能被继承了,在这样的情况下我们就可以不一定限制这些成员为private的。
3. 不要允许子类来覆写方法。 因为如果我们允许子类来覆写某些方法的话就不一定能保证这些子类里覆写的方法不改变对象的状态。
4. 如果对象实例的成员里包含指向可改变对象的引用,则不能让这些对象被改变。
我们可以不提供方法来修改这些可改变对象。同时,我们也不能将这些可改变对象的引用给暴露出来。以前面String类的实现为例,它里面有可改变对象char[] value。因为这个引用里的值是可以被改变的,但是我们只要保证没有任何方法会返回一个直接指向value的引用,也没有方法可以修改它,这样还是可以保证它不会被修改的。
看了前面这一大堆的讨论,其实对于immutable类型定义的要点,也就可以总结成这么几句话。一个是不要把可以修改的属性或者方法暴露出来,所有的值设定都尽量在构造函数里搞定。一个是所有提供状态变换的方法实际上都是对原有对象的拷贝修改。
immutable类型的作用
前面既然我们讨论了immutable类型的定义和实现要点,那么定义一个这样的类型有什么好处呢?我们可以看到,每次当我们定义一个要修改状态的方法,实际上是定义了一个新的对象,这个新的对象的属性设定成被修改后的样子。这样,如果一个对象比较大的话,实际上我们相当于每次要把原来的对象拷贝一遍出来。这样看起来即费空间又费时间。当然,这种拷贝的笨办法还是有一个好处的。
一个主要的好处是在多线程的并发执行环境下。作为一个immutable类型的对象,因为它本身是不会被修改的。所以在原来一些为了防止对象被多线程访问出现错误的情况在这里就不用担心了。反正对象都不会变,我们连线程间同步的事都不用担心,直接上就ok。在一些并行操作的集合里,immutable类型的特性也起到很好的作用。因为每次如果有线程要访问集合的时候,可能有一部分被修改了。而如果原来有在上面做其他操作的线程在操作,这里如果对于修改的变化只是额外创建一套新的拷贝,则不会有线程间的访问冲突了。这种思路的一个具体实现就是CopyOnWriteArrayList。在很多并行集合操作里都有用到它。有兴趣的可以去看看,这里就不再详述了。
总结
immutable和mutable对象不一样,它本身是不会改变的。每次我们调用的一些修改对象状态的方法实际上只是额外创造出来的对象或者部分成员的拷贝,原有的对象并没有改变。这就好像是一个对象总是创造出它的替身来,反正每次改变的或者修改的都是替身,它本身不会变。这样就不怕别人用流氓手段来改变它的真身了。果然很好很强大。
参考材料
http://stackoverflow.com/questions/5124012/examples-of-immutable-classes
http://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html
相关推荐
1.Overview of Docker the company and growth 2.Overview of Docker the latest stuff (DCUS announcements) & CaaS;...4.Docker and Immutable infrastructure/Microservices working together.
简单介绍Immutable.js的一个ppt,主要用于技术分享,内容为imuutabe常用的数据类型和常用的基础api,还有immutable的原理、优缺点的分析。
Mastering Immutable.js 英文epub 本资源转载自网络,如有侵权,请联系上传者或csdn删除 本资源转载自网络,如有侵权,请联系上传者或csdn删除
前端开源库-immutable-core不变的核心,不变的模块化框架
immutability-util:一个开源 immutable data 的轮子
前端开源库-immutable-ai不可变人工智能,不可变核心的可选接口
Immutable详解及React中实践.pdf
Immutable Collections Library for Kotlin Immutable collection interfaces and implementation prototypes for Kotlin. For further details see the proposal. Prototype implementation is based on ...
go-immutable-radix, 在Golang中,一个不可变的基数树实现 go-immutable-radix 提供实现不可变 radix的iradix 包。 包只提供单个 Tree 实现,针对稀疏节点优化。作为一个基数树,它提供以下内容:O(k) 操作。在许多...
Immutable List,顾名思义,就是,啥,不明白 Immutable 是什么意思?一成不变的意思,所以 Immutable List 就是一个不可变的 List 类,这意味着该 List 声明后,它的内容就是固定的,不可增删改的。 如果尝试对 ...
Mastering Immutable PDF Mastering Immutable epub Mastering Immutable azw3 示例源码
不可变的角度 支持在 Angular 1.x 中查看和枚举集合 ... import Immutable from 'immutable' ; class SomethingController { static get $inject ( ) { return [ '$scope' ] ; } constructor ( $
immutable - Javascript不可变的持久化数据集合
dot-prop-immutable, 点prop的不可变版本,带有一些扩展名 dot-prop-immutable 点prop的不可变版本,带有一些扩展名。npm install dot-prop-immutable这个模块的动机是在不改变普通JavaScript对象的现有状态的情况
Immutable 是 Facebook 开发的不可变数据集合。不可变数据一旦创建就不能被修改,是的应用开发更简单,允许使用函数式编程技术,比如惰性评估。Immutable JS 提供一个惰性 Sequence,允许高效的队列方法链,类似 map...
关于我厌倦了看到的React.PropTypes.instanceOf(Immutable.List)或React.PropTypes.instanceOf(Immutable.Map)作为PropTypes为应指定部件Immutable.List的东西,或者一个Immutable.Map包含一些按键。 除非您要使用...
使用第三方模块Underscore.js,Immutable.js,UUID(源代码+截图)使用第三方模块Underscore.js,Immutable.js,UUID(源代码+截图)使用第三方模块Underscore.js,Immutable.js,UUID(源代码+截图)使用第三方模块...
什么是Immutable Data Immutable Data是指一旦被创造后,就不可以被改变的数据。 通过使用Immutable Data,可以让我们更容易的去处理缓存、回退、数据变化检测等问题,简化我们的开发。 js中的Immutable Data 在...
本项目主要利用 React 技术,结合immutable.js 与 Redux 实现了简单列表的增加功能,帮助初学者了解并学会简单使用 React 结合 Redux 及 Reducer 实现某些功能 本项目在下载后直接用 VSCode 打开即可运行,如果出现...
kotlinx.collections.immutable, Kotlin的不可变集合 Prototype Kotlin的不可变集合库 Kotlin的不可变集合接口和实现 Prototype 。有关详细信息,请参阅建议列表。Prototype实现基于 pcollections ( 版权 2015的作者...