MIT_6.031之可变性和不变性

可变性与不变性

  • 可变性(Mutability)
    • 可变对象允许在不创建新实例的情况下改变其值或状态。
    • 对象的状态可以在多个地方进行更改,可能导致不可预见的结果。
  • 不变性(Immutability)
    • 不变对象的状态在创建后无法更改。
    • 每次对不变对象的修改都会返回一个新的对象。

StringStringBuilder

  • String

    • String 是不可变类型。创建一个 String 对象后,内容不能被修改。

    • 任何对 String 的修改都会生成一个新的 String 对象。

    • 示例:

      1
      2
      3
      String s = "a";
      s = s.concat("b"); // s 现在指向新的 String 对象 "ab"
      // s += "b" 也会产生相同的效果
  • StringBuilder

    • StringBuilder 是可变类型,提供修改对象内容的方法。

    • StringBuilder 的操作不会创建新对象。

    • 示例:

      1
      2
      StringBuilder sb = new StringBuilder("a");
      sb.append("b"); // sb 仍然指向同一个 StringBuilder 对象,现在内容为 "ab"

性能比较

  • 不变性的问题

    • 使用 String 拼接字符串的时间复杂度为 O(n^2):

      1
      2
      3
      4
      String s = "";
      for (int i = 0; i < n; ++i) {
      s = s + i; // 每次都创建新的 String 对象
      }
  • 可变性带来的优势

    • 使用 StringBuilder 拼接字符串的时间复杂度为 O(n):

      1
      2
      3
      4
      5
      StringBuilder sb = new StringBuilder();
      for (int i = 0; i < n; ++i) {
      sb.append(String.valueOf(i)); // 直接在原对象上修改
      }
      String s = sb.toString(); // 最后转换为 String

使用可变性与不变性的权衡

  • 可变性带来的风险
    • 代码的可读性和可维护性下降。程序员可能难以追踪可变对象的状态变化。
    • 函数传递可变对象可能导致意外修改,造成错误。
  • 不变性的优点
    • 更安全,易于理解和维护。
    • 避免了别名问题,确保一个对象只有一个状态。

迭代器与可变性

  • 迭代器的设计

    • 迭代器提供了一种统一的方式来访问不同内部表示的集合数据结构。
    • 可变对象的修改可能破坏迭代器的状态。
  • 示例

    1
    2
    3
    4
    5
    6
    7
    8
    List<String> subjects = new ArrayList<>(Arrays.asList("6.045", "6.005", "6.813"));
    Iterator<String> iter = subjects.iterator();
    while (iter.hasNext()) {
    String subject = iter.next();
    if (subject.startsWith("6.")) {
    subjects.remove(subject); // 会导致 ConcurrentModificationException
    }
    }
  • 解决方法

    • 使用 iter.remove() 来安全地从集合中删除元素:

      1
      2
      3
      4
      5
      6
      7
      Iterator<String> iter = subjects.iterator();
      while (iter.hasNext()) {
      String subject = iter.next();
      if (subject.startsWith("6.")) {
      iter.remove(); // 安全删除
      }
      }

设计原则

  • 使用不可变对象和引用的原则
    1. 安全的错误:不可变对象防止了由于别名造成的错误。
    2. 容易理解:不可变对象的状态始终明确,便于推理。
    3. 准备改变:不必修改依赖于该对象的其他代码,易于扩展。

Java 的不可变类型

  • 原始类型(如 intfloat)及其包装器(如 IntegerDouble)是不可变的。
  • BigIntegerBigDecimal 也是不可变类型。
  • java.time 中的时间相关类是不可变的。
  • 集合类型一般是可变的,空集合使用 Collections.emptyList() 创建。

总结

  • 可变性与不变性的选择
    • 在需要性能和便利性时,可以使用可变对象。
    • 但需要小心可变性带来的错误和复杂性。
  • 设计原则
    • 尽可能使用不可变对象和引用,以提高代码的安全性和可读性。