-
改变变量:使变量指向存储着另一个值的空间
-
改变变量的值:变量指向的空间不变,变化的是存储的内容。
Immutability:不变性,一个重要的设计原则,设计ADT时尽量保证这个原则。
Immutable types:不可变的数据类型,当实例对象被创建以后,该对象的值就不可变化了,也就是该ADT中不能有mutator方法。
在编写程序的时候使用final关键字可以保证该变量不可再被改变,但不能保证该变量的值不变。所以,尽量使用final变量作为方法的输入参数、作为局部变量。
final类无法派生子类
final变量无法改变值/引用
final方无法被子类override(重写)
- 比较immutable和mutable
- 不变对象:一旦被创建,始终指向同一个值/引用
- 可变对象:拥有方法可以修改自己的值/引用
一个例子:比较String和StringBuilder
在这个例子中,String和StringBuilder所达到的效果是相同的
/* String部分 */
String s = "a"; //开辟一个存储空间,里面存着字符a,s指向这块空间
s = s.concat("b"); //把字符a和字符b连接,然后把“ab”放在一个新的存储空间,最后让s指向这块空间
/* StringBuilder部分 */
StringBuilder sb = new StringBuilder("a"); //开辟一个存储空间,里面存着字符a
sb.append("b"); //取出a,然后与字符b连接,然后把“ab”仍然放在这块空间内,把原来的“a”覆盖了,sb的指向没变
在这个例子中,String和StringBuilder所达到的效果出现了差别
/* String部分 */
String s = "a"; //开辟一个存储空间,里面存着字符a,s指向这块空间,记为space1
String t = s; //让t指向s所指向的空间即space1
s = s.concat("b"); //把字符a和字符b连接,然后把“ab”放在一个新的存储空间,记为space2,最后让s指向这块空间
//我们可以看到,现在s和t所指向的是两块不同的空间,空间中的内容也不一样,因此s和t的效果是不一样的
/* StringBuilder部分 */
StringBuilder sb = new StringBuilder("a"); //开辟一个存储空间,里面存着字符a
StringBuilder tb = sb; //开辟一个存储空间,里面存着字符a
sb.append("b"); //取出a,然后与字符b连接,然后把“ab”仍然放在这块空间内,把原来的“a”覆盖了,sb的指向没变
//在这个情况下,由于从始至终只用到了一块存储空间,所以sb和tb的效果实际上是相同的
可变对象的优点:虽然mutable类型由于指向的是同一个存储区域,所以更改对象的内容后会在意想不到的位置产生意想不到的变化,所以更推荐使用Imutable的数据类型,但是使用不可变类型,对其频繁修改会产生大量的临时拷贝(需要垃圾回收),比如依次将 ‘a’~‘z’ 连接到一个空字符串上,就会产生25个临时拷贝,而使用可变类型则最少化拷贝以提高效率。
使用可变数据类型,可获得更好的性能。但是在质量指标中,性能的优先级较低,所以即使mutable类型有这个优点也更倾向于选择imutable的类型。
也适合于在多个模块之间共享数据。在这里强烈不推荐使用Global variables。
例子: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 sumo), 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);
}
//client
public static void main(String[] args) {
List<Integer> my Data Arrays asList(-5, -3,-2);
System.out.println(sumAbsolute(myData)); //期望值10,实际值10
System.out.println(sum(myData)); //期望值-10,实际值10
}
解决的办法:通过防御式拷贝,给客户端一个副本,客户端即使对数据做了更改,也不会影响到自己。我们解决了外部对内部的无意改动,但为此付出的代价就是空间的浪费。而如果使用immutale类型的数据,就不存在这种风险。同时,我们编程的时候也要注意避免出现一个对象的多个引用,也就是说尽量不要让一个对象出现别名。
关于不可变类型的注意点:一般来说 immutable类的rep是不可更改的,但这也并非是绝对的。只要保证更改后它所表达的抽象含义没有更改, 它的数据域就可以被更改。这种更改一般在 observer等方法中完成,ADT中还是不能加入 mutator方法。例:一个表示分数的类,rep里有两个int,表示分子分母,创建时被初始化为了2和4,但是在调用Observer观察时被化简成了1和2,在外界看来它所表达的都是1/2,没有区别。