MIT_6.031之可变性和不变性
MIT_6.031之可变性和不变性
可变性与不变性
- 可变性(Mutability):
- 可变对象允许在不创建新实例的情况下改变其值或状态。
- 对象的状态可以在多个地方进行更改,可能导致不可预见的结果。
- 不变性(Immutability):
- 不变对象的状态在创建后无法更改。
- 每次对不变对象的修改都会返回一个新的对象。
String
与
StringBuilder
String
:String
是不可变类型。创建一个String
对象后,内容不能被修改。任何对
String
的修改都会生成一个新的String
对象。示例:
1
2
3String s = "a";
s = s.concat("b"); // s 现在指向新的 String 对象 "ab"
// s += "b" 也会产生相同的效果
StringBuilder
:StringBuilder
是可变类型,提供修改对象内容的方法。对
StringBuilder
的操作不会创建新对象。示例:
1
2StringBuilder sb = new StringBuilder("a");
sb.append("b"); // sb 仍然指向同一个 StringBuilder 对象,现在内容为 "ab"
性能比较
不变性的问题:
使用
String
拼接字符串的时间复杂度为 O(n^2):1
2
3
4String s = "";
for (int i = 0; i < n; ++i) {
s = s + i; // 每次都创建新的 String 对象
}
可变性带来的优势:
使用
StringBuilder
拼接字符串的时间复杂度为 O(n):1
2
3
4
5StringBuilder 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
8List<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
7Iterator<String> iter = subjects.iterator();
while (iter.hasNext()) {
String subject = iter.next();
if (subject.startsWith("6.")) {
iter.remove(); // 安全删除
}
}
设计原则
- 使用不可变对象和引用的原则:
- 安全的错误:不可变对象防止了由于别名造成的错误。
- 容易理解:不可变对象的状态始终明确,便于推理。
- 准备改变:不必修改依赖于该对象的其他代码,易于扩展。
Java 的不可变类型
- 原始类型(如
int
、float
)及其包装器(如Integer
、Double
)是不可变的。 BigInteger
和BigDecimal
也是不可变类型。java.time
中的时间相关类是不可变的。- 集合类型一般是可变的,空集合使用
Collections.emptyList()
创建。
总结
- 可变性与不变性的选择:
- 在需要性能和便利性时,可以使用可变对象。
- 但需要小心可变性带来的错误和复杂性。
- 设计原则:
- 尽可能使用不可变对象和引用,以提高代码的安全性和可读性。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Totoroの旅!
评论