不可变类:当创建了这个类的实例后,就不允许修改它的属性值。在 JDK 的基本类库中,所有基本类型的包装类,如 Integer 和 Long 类,都是不可变类。String 也是不可变类。
用户在创建自己的不可变类时,可以考虑采用以下设计方法:
private final 类型。setXXX() 方法getXXX() 方法Object 类的 equals() 和 hashCode() 方法,在 equals() 方法中根据对象的属性值来比较两个对象是否相等,并且保证用 equals() 方法判断为相等的两个对象的 hashCode() 方法的返回值也相等,这可以保证这些对象能正确地放到 HashMap 或 HashSet 集合中。Java 中的 String 类是不可变的
String 类由 final 修饰,说明其不可继承。char value[] 是由 private final 修饰的,保证引用地址不可变。(不保证数组内容不可变)String 类中,没有公开修改内部成员字段的方法。value 数组时,采用了保护性拷贝 Arrays.copyof()实际上,可以通过反射修改 value 属性。
不可变类的实例在整个生命周期中永远保持初始化的状态,它没有任何状态变化,简化了与其他对象之间的关系。不可变类具有以下优点:
把不可变类实例属性的 final 修饰符去除,增加 public 类型的 set 方法就变成了可变类。使用可变类更容易使程序代码出错。因为随意改变一个可变类对象的状态,有可能会导致与之关联的其他对象的状态被错误地改变。
由此可见,应该优先考虑把类设计为不可变类。假设必须使用可变类,也应该把可变类的尽可能多的属性设计为不可变的,即用 final 修饰符来修饰,并且不对外公开用于改变这些属性的方法。
在创建不可变类时,假如它的属性所属的类是可变类,在必要的情况下,必须提供保护性复制,否则,这个不可变类的实例的属性仍然有可能被错误地修改。即使可变类属性已经用 final 修饰,也必须提供保护性复制。
保护性复制是一种防御性编程技术。
例子:
1 | public final class Schedule { |
假如某个类中被 final 修饰的属性所属的类是不可变类,就无须提供保护性复制,因为该属性所引用的实例的值永远不会被改变,这进一步体现了不可变类的优点。
保护性拷贝:通过创建副本对象来避免共享的手段。
推荐阅读:站内文章享元模式:共享实例 中 Java 的
Integer、String的带缓存的不可变类的实现。
不可变类的实例的状态不会变化,这样的实例可以安全地被其他与之关联的对象共享,还可以安全地被多个线程共享。为了节省内存空间,优化程序的性能,应该尽可能地重用不可变类的实例,避免重复创建具有相同属性值的不可变类的实例。
缓存并没有固定的实现方式,完善的缓存实现不仅要考虑何时把实例加入缓存,还要考虑何时把不再使用的实例从缓存中及时清除,以保证有效合理地利用内存空间。一种简单的实现是直接用 Java 集合来作为实例缓存。
另外要注意的是,没有必要为所有的不可变类提供实例缓存。随意创建大量实例缓存,反而会浪费内存空间,降低程序的运行性能。通常,只有满足以下条件的不可变类才需要实例缓存:
不变模式:一个对象的状态在对象创建之后不再改变。涉及的 Java 类即不变类(Immutable Class),如果是对象则为不变对象(Immutable Object)。
不变模式分为两类:
它们之间的关系类似于 站内文章深拷贝和浅拷贝 之间的关系。
不变模式使用到不可变类,而不可变类不存在线程问题,因此不变模式常用于多线程场景。所以,不变模式也常被归类为多线程设计模式。
不变集合是一种特殊的不变类。
Google Guava 针对集合类 Collection、List、Set、Map 等提供了对应的不变集合类 ImmutableCollection、ImmutableList、ImmutableSet、ImmutableMap……
Google Guava 提供的不变集合类属于普通不变模式,集合中的对象不会增删,但是对象的成员变量是可以改变的。
JDK 中也提供了类似的集合,只不过它提供的是一个集合的「不可修改的视图」,而不是不变集合。当原始集合被修改后,Collections.unmodifiableXXX 里面的元素会跟着发生变化。
1 | public static void main(String[] args) { |
JDK 类库中提供的 unmodifiableXXX 方法存在以下不足:
不可变类:当创建了这个类的实例后,就不允许修改它的属性值。在 JDK 的基本类库中,所有基本类型的包装类,如 Integer 和 Long 类,都是不可变类。String 也是不可变类。
用户在创建自己的不可变类时,可以考虑采用以下设计方法:
private final 类型。setXXX() 方法getXXX() 方法Object 类的 equals() 和 hashCode() 方法,在 equals() 方法中根据对象的属性值来比较两个对象是否相等,并且保证用 equals() 方法判断为相等的两个对象的 hashCode() 方法的返回值也相等,这可以保证这些对象能正确地放到 HashMap 或 HashSet 集合中。Java 中的 String 类是不可变的
String 类由 final 修饰,说明其不可继承。char value[] 是由 private final 修饰的,保证引用地址不可变。(不保证数组内容不可变)String 类中,没有公开修改内部成员字段的方法。value 数组时,采用了保护性拷贝 Arrays.copyof()实际上,可以通过反射修改 value 属性。
不可变类的实例在整个生命周期中永远保持初始化的状态,它没有任何状态变化,简化了与其他对象之间的关系。不可变类具有以下优点:
把不可变类实例属性的 final 修饰符去除,增加 public 类型的 set 方法就变成了可变类。使用可变类更容易使程序代码出错。因为随意改变一个可变类对象的状态,有可能会导致与之关联的其他对象的状态被错误地改变。
由此可见,应该优先考虑把类设计为不可变类。假设必须使用可变类,也应该把可变类的尽可能多的属性设计为不可变的,即用 final 修饰符来修饰,并且不对外公开用于改变这些属性的方法。
在创建不可变类时,假如它的属性所属的类是可变类,在必要的情况下,必须提供保护性复制,否则,这个不可变类的实例的属性仍然有可能被错误地修改。即使可变类属性已经用 final 修饰,也必须提供保护性复制。
保护性复制是一种防御性编程技术。
例子:
1 | public final class Schedule { |
假如某个类中被 final 修饰的属性所属的类是不可变类,就无须提供保护性复制,因为该属性所引用的实例的值永远不会被改变,这进一步体现了不可变类的优点。
保护性拷贝:通过创建副本对象来避免共享的手段。
推荐阅读:站内文章享元模式:共享实例 中 Java 的
Integer、String的带缓存的不可变类的实现。
不可变类的实例的状态不会变化,这样的实例可以安全地被其他与之关联的对象共享,还可以安全地被多个线程共享。为了节省内存空间,优化程序的性能,应该尽可能地重用不可变类的实例,避免重复创建具有相同属性值的不可变类的实例。
缓存并没有固定的实现方式,完善的缓存实现不仅要考虑何时把实例加入缓存,还要考虑何时把不再使用的实例从缓存中及时清除,以保证有效合理地利用内存空间。一种简单的实现是直接用 Java 集合来作为实例缓存。
另外要注意的是,没有必要为所有的不可变类提供实例缓存。随意创建大量实例缓存,反而会浪费内存空间,降低程序的运行性能。通常,只有满足以下条件的不可变类才需要实例缓存:
不变模式:一个对象的状态在对象创建之后不再改变。涉及的 Java 类即不变类(Immutable Class),如果是对象则为不变对象(Immutable Object)。
不变模式分为两类:
它们之间的关系类似于 站内文章深拷贝和浅拷贝 之间的关系。
不变模式使用到不可变类,而不可变类不存在线程问题,因此不变模式常用于多线程场景。所以,不变模式也常被归类为多线程设计模式。
不变集合是一种特殊的不变类。
Google Guava 针对集合类 Collection、List、Set、Map 等提供了对应的不变集合类 ImmutableCollection、ImmutableList、ImmutableSet、ImmutableMap……
Google Guava 提供的不变集合类属于普通不变模式,集合中的对象不会增删,但是对象的成员变量是可以改变的。
JDK 中也提供了类似的集合,只不过它提供的是一个集合的「不可修改的视图」,而不是不变集合。当原始集合被修改后,Collections.unmodifiableXXX 里面的元素会跟着发生变化。
1 | public static void main(String[] args) { |
JDK 类库中提供的 unmodifiableXXX 方法存在以下不足: