关于Java中的Mutable 和 Immutable类型
在软件构造这门课程当中,老师最先强调的就是Java中的Mutable类型的变量和Immutable类型的变量。但是因为没有教材,所以大多数同学在这方面的知识可能仅仅停留在课堂上,所以这篇blog打算总结一下这两种变量以便更好地学习。
Mutable类型变量
概念:
mutable : When you assign to a variable or a field, you’re changing where the variable’s arrow points. You can point it to a different value. When you assign to the contents of a mutable value – such as an array or list – you’re changing references inside that value.
可变类型的对象:提供了可以改变其内部数据值的操作,其内部的值可以被重新更改。
图形化解释:

关于Mutable变量的优缺点:
StringBuilder类型是mutable的,创建成功后,可以利用一些方法来改变它指向地址中的内容。
1 StringBuilder sb = new StringBuilder("a"); 2 sb.append("b");

可以发现,sb指向的内存地址没变,只是地址内的内容改变了。之后,再来参照如下的代码:
String t = s; t = t + "c"; StringBuilder tb = sb; tb.append("c");
可以发现,当有变量t指向s,tb指向sb时,问题就呈现出来了,当改变t的值时,s的值并没有改变;但当改变tb的值时,sb的值跟着改变,这就会造成一些可怕的后果。既然可变类型会产生一些意外的后果,而且我们已经有了Immutable的String类型,那么要StringBuilder类型做什么呢?参考下面代码:
String s = ""; for (int i = 0; i < n; ++i) { s = s + i; }
如果s被设为String类型,那么,每次在s后面接字符串的时候,都会将之前s中的内容复制一份再加上n,然后放到另一块内存区域,由于要更改s的指向,所以每次都需要复制之前的字符串,“0”被复制了n次,这样本来增加了n个字符,结果却需要O(n2);然而使用StringBuilder就可以直接在原字符串后增加,效率提高了很多,当需要转成字符串时,只需利用toString()方法。
StringBuilder sb = new StringBuilder(); for (int i = 0; i < n; ++i) { sb.append(String.valueOf(i)); } String s = sb.toString();
但是Mutable类型的缺点也是显而易见的。参照下面的几个方法:
/** @return the sum of the numbers in the list */ public static int sum(List<Integer> list) { int sum = 0; for (int x : list) sum += x; return sum; } /** @return the sum of the absolute values of the numbers in the list */ public static int sumAbsolute(List<Integer> list) { // let's reuse sum(), because DRY, so first we take absolute values for (int i = 0; i < list.size(); ++i) list.set(i, Math.abs(list.get(i))); return sum(list); } public static void main(String[] args) { // ... List<Integer> myData = Arrays.asList(-5, -3, -2); System.out.println(sumAbsolute(myData)); System.out.println(sum(myData)); }
首先写一个sum方法,计算参数列表中元素之和,然后再写一个计算列表元素绝对值之和的方法,最后在main方法中查看最后的输出:会发现,输出的都是10,而不是我们预期那样,可见,可变数据类型发生了参数的更改,这在我们写程序的时候会发生致命的问题。
如果使用mutable,不仅函数的参数会发生改变,函数返回值也会发生变化,这个时候,为了不让客户端程序员更改,我们可以使用防御性复制的方法,即返回复制的副本。如果使用mutable,不仅函数的参数会发生改变,函数返回值也会发生变化,这个时候,为了不让客户端程序员更改,我们可以使用防御性复制的方法,即返回复制的副本。
Immutable类型变量
概念:
Immutable : variables that are assigned once and never reassigned.
不可变数据类型:其内部的操作不会改变内部的值,一旦试图更改其内部值,将会构造一个新的对象而非对原来的值进行更改。
图形化解释:

关于Immutable变量的优缺点:
不可变对象有很多优点:构造、测试和使用都很简单、 线程安全且没有同步问题,不需要担心数据会被其它线程修改。因为在多线程同时进行的情况下,一个可变对象的值很可能被其他进程改变,这样会造成不可预期的结果,而使用不可变对象就可以避免这种情况、 当用作类的属性时不需要保护性拷贝、 可以很好的用作Map键值和Set元素。
真如上一节中所描述的那样:不可变对象最大的缺点就是创建对象的开销,因为每一步操作都会产生一个新的对象。
总结对比:
| mutable | immutable | |
| 优点 | 可变类型会减少数据的拷贝次数,从而其效率 要高于immutable | 由于内部数据不可变,所以对其频发修改会产生大量的临时拷贝,浪费空间 |
| 缺点 | 可变类型由于其内部数据可变,所以其风险更大 | 内部数据的不可变导致其更加安全,可以用作多线程的共享对象而不必考虑同步问题 |
如何维护一个Immutable类型的类:
1)确保fileds中的成员都被private final修饰:private保证内部成员不会被外部直接访问;final确保在成员被初始化之后不会被重新assigned。
2)不提供改变成员的方法,例如set()方法。
3)使用final修饰自定义类,确保类中的所有方法不会被重写。
4)如果类中的某成员为mutable类型,那么在初始化该成员或者企图使用get方法从外部对其进行观察的时候,应该使用深度拷贝,确保类immutable。

浙公网安备 33010602011771号