Fork me on GitHub

关于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。

 

posted @ 2021-06-19 20:17  牺牲的钢铁侠  阅读(732)  评论(0)    收藏  举报