原文: https://howtodoinjava.com/java/basics/how-to-make-a-java-class-immutable/

不可变类是其状态一旦创建便无法更改的类。 有某些准则可以创建 Java 中不可变的类。

在这篇文章中,我们将重新审视这些准则。

  1. Table of Contents
  2. 1\. Rules to create immutable classes
  3. 2\. Java immutable class example
  4. 3\. Benefits of making a class immutable
  5. 5\. Summary

1. 创建不可变类的规则

Java 文档本身具有一些准则,此链接中的这些准则可以确定编写不可变类。 通过使用Date字段创建具有可变对象的不可变类,我们将理解这些准则的实际含义。

  1. 不要提供“设置器”方法 - 修改字段或字段引用的对象的方法。


该原则表明,对于您的类中的所有可变属性,请勿提供设置器方法。 设置器方法用于更改对象的状态,这是我们在此要避免的。

  1. 将所有字段定为最终和私有的


这是增加不变性的另一种方法。 声明为private的字段将无法在类之外访问,并且将它们设置为最终字段将确保即使您无意中也无法更改它们。

  1. 不允许子类覆盖方法


最简单的方法是将该类声明为final。 Java 中的final类无法扩展。

  1. 具有可变实例变量时要特别注意


始终记住,实例变量将是可变的不可变的。 标识它们并返回具有所有可变对象复制内容的新对象。 不可变的变量可以安全地返回而无需额外的努力。
一种更复杂的方法是使构造器private,并在工厂方法中构造实例。

2. Java 不可变类示例

让我们将以上所有规则应用于不可变类,并为 Java 中的不可变类实现具体的类实现

  1. import java.util.Date;
  2. /**
  3. * Always remember that your instance variables will be either mutable or immutable.
  4. * Identify them and return new objects with copied content for all mutable objects.
  5. * Immutable variables can be returned safely without extra effort.
  6. * */
  7. public final class ImmutableClass
  8. {
  9. /**
  10. * Integer class is immutable as it does not provide any setter to change its content
  11. * */
  12. private final Integer immutableField1;
  13. /**
  14. * String class is immutable as it also does not provide setter to change its content
  15. * */
  16. private final String immutableField2;
  17. /**
  18. * Date class is mutable as it provide setters to change various date/time parts
  19. * */
  20. private final Date mutableField;
  21. //Default private constructor will ensure no unplanned construction of class
  22. private ImmutableClass(Integer fld1, String fld2, Date date)
  23. {
  24. this.immutableField1 = fld1;
  25. this.immutableField2 = fld2;
  26. this.mutableField = new Date(date.getTime());
  27. }
  28. //Factory method to store object creation logic in single place
  29. public static ImmutableClass createNewInstance(Integer fld1, String fld2, Date date)
  30. {
  31. return new ImmutableClass(fld1, fld2, date);
  32. }
  33. //Provide no setter methods
  34. /**
  35. * Integer class is immutable so we can return the instance variable as it is
  36. * */
  37. public Integer getImmutableField1() {
  38. return immutableField1;
  39. }
  40. /**
  41. * String class is also immutable so we can return the instance variable as it is
  42. * */
  43. public String getImmutableField2() {
  44. return immutableField2;
  45. }
  46. /**
  47. * Date class is mutable so we need a little care here.
  48. * We should not return the reference of original instance variable.
  49. * Instead a new Date object, with content copied to it, should be returned.
  50. * */
  51. public Date getMutableField() {
  52. return new Date(mutableField.getTime());
  53. }
  54. @Override
  55. public String toString() {
  56. return immutableField1 +" - "+ immutableField2 +" - "+ mutableField;
  57. }
  58. }

现在该测试我们的类了:

  1. class TestMain
  2. {
  3. public static void main(String[] args)
  4. {
  5. ImmutableClass im = ImmutableClass.createNewInstance(100,"test", new Date());
  6. System.out.println(im);
  7. tryModification(im.getImmutableField1(),im.getImmutableField2(),im.getMutableField());
  8. System.out.println(im);
  9. }
  10. private static void tryModification(Integer immutableField1, String immutableField2, Date mutableField)
  11. {
  12. immutableField1 = 10000;
  13. immutableField2 = "test changed";
  14. mutableField.setDate(10);
  15. }
  16. }

程序输出:

  1. 100 - test - Tue Oct 30 21:34:08 IST 2012
  2. 100 - test - Tue Oct 30 21:34:08 IST 2012

可以看出,即使使用其引用更改实例变量也不会更改其值,因此该类是不可变的。

JDK 中的不可变类

除了您编写的类,JDK 本身还有很多不可变的类。 给出了这样的 Java 不可变类的列表。

  1. String
  2. 包装器类,例如IntegerLongDouble等。
  3. 不可变的集合类,例如Collections.singletonMap()等。
  4. java.lang.StackTraceElement
  5. Java 枚举(理想情况下应该如此)
  6. java.util.Locale
  7. java.util.UUID

3. 使类不可变的好处

首先让我们确定不可变类的优势。 在 Java 中,不可变类:

  1. 易于构建,测试和使用
  2. 自动是线程安全的,并且没有同步问题
  3. 不需要复制构造器
  4. 不需要克隆的实现
  5. 允许 hashCode() 使用延迟初始化,并缓存其返回值
  6. 用作字段时不需要防御性地复制
  7. 作为良好的映射的键和集合元素(在集合中这些对象不得更改状态)
  8. 在构造时就建立了其类不变式,因此无需再次检查
  9. 始终具有“失败原子性”(约书亚·布洛赫(Joshua Bloch)使用的术语):如果不可变对象引发异常,则它永远不会处于不希望或不确定的状态

4. 总结

在本教程中,我们学习了使用可变对象和不可变字段创建不可变的 Java 类。 我们还看到了不可变类在应用程序中带来的好处。

作为最佳设计实践,始终旨在使您的应用程序 Java 类不可变。 这样,您始终可以减少程序中与并发相关的缺陷的担心。

如何编写一个不可变的类? 这也可能是面试问题

学习愉快!

阅读更多:

为什么字符串类在 Java 中是不可变的?