我的 class 中的代码是否会阻止不变性?

Does code in my class prevent immutability?

我写了 classes,我被告知它们不是不可变的,但应该是。

public class Author {
 private String name;
 private String publisher;
 public Author(String name, String publisher) {
  this.name = name;
  this.publisher = publisher;
 }
 public String getName() { return name; }
 public void setName(String name) { this.name = name; }
 public String getPublisher() { return publisher; }
 public void setPublisher(String publisher) { this.publisher = publisher; }
}

还有我的第二个 class。

public final class Book {

 private final String title;
 private final Author author;
 private final Date datePublished;

 public Book(String title, Author author, Date datePublished) { 
   this.title = title;
   this.author = author;
   this.datePublished = datePublished;
 }

 public String getTitle() { return title; }
 public void setTitle(String title) { this.title = title; }
 public Author getAuthor() { return author; }
 public Date getDatePublished() { return datePublished; }
}

我相信 set 方法可以防止不可变性,但我是否遗漏了其他东西? get 方法也能破坏不变性吗?如果是这样,是否意味着 this.title 等 setter 也可以阻止它?

Book class 不是不可变的,因为这个方法:

 public void setTitle(String title) { this.title = title; }

它也不会编译,因为不允许赋值。您不能分配给方法中的 final 字段。

Author class 也不是一成不变的。它具有设置器和可变(不是 final)字段,并且不是 final class.


Can the get methods break immutability too?

嗯...这取决于他们做什么。例如,返回对(私有)数组字段的引用的 getter 使调用者可以改变对象的(有效)状态。而“getter”可以更新缓存或访问计数器,这可以被视为突变。

但在你的情况下,不。 (IMO)


Doesn't the getAuthor() break immutability? I can't understand why it wouldn't now since Author is public.

实际上,可变性在 Java 中是一个相当复杂的 属性,根据不可变的实际含义,您可以得到不同的“答案”。

例如,您询问 Author 是可变的这一事实是否也会使 Book 也可变。

  • 如果从建模的角度来看,答案很可能是否定的。更正作者姓名的拼写不会改变他们所写的书。 Author 不是 Book 部分。 (大概)。

  • 但是如果你从(比如说)序列化一个Book的角度来看,并且Author包含在序列化中,那么改变Author 改变序列化形式。

在这种情况下,我倾向于说它不会使 Book 可变。但在其他情况下,答案可能会有所不同。

'immutability' 有多种含义,这是一个模糊定义的术语。最常见的意思是 'its state cannot be directly changed'.

getAuthor() 不会改变任何状态 - 它只是让您观察它。是的,它是 public,但这不是 'immutable' 的意思。不可变的,如 'not capable of being mutated',如 'unchanging'.

java 中的字符串不变。鉴于:

String x = "Hello";
someMethod(x);

您在 someMethod 中没有任何内容可以使 x 成为其他任何内容。例如,x.toLowerCase() 不会这样做:这不会更改 x 所指的对象 - 这会生成一个 new 对象。

另一方面,给定:

Book b = new Book("Gulliver's Travels");
someMethod(b);
System.out.println(b.getTitle());

如果 someMethod 是:

,则可以打印“Hitchhiker's guide”
public void someMethod(Book b) {
    b.setTitle("Hitchhiker's guide");
}

这就是为什么你的书是可变的。

当状态在别处时,可变性变得有点模糊。例如,java.io.File 只有 final 字段,没有 setters,并且没有方法改变它的任何内部状态(它拥有的字段)。但是,您仍然可以 'modify its state and then observe this modification': someFileObj.delete(); 确实改变了一些东西,而且这种改变是显而易见的。 j.i.File 是否不可变取决于你问谁(这取决于你如何定义不可变)。您还可以使用静态 IdentityHashMaps 进行一些疯狂的恶作剧,但不要太过分——在大多数情况下,不变性归结为非常非常简单的事情:

  • 使所有字段final.

瞧。而已。你会发现根本不可能写那个 set 方法。 set 方法从根本上与不变性的符号不兼容 - 充其量,您可以拥有制作新修改克隆的方法,就像 "Hello".toLowerCase() 不是 setter - 它通过克隆制作一个新的字符串对象这个,但是小写了其中的每个字符。这些方法按照惯例称为 'with' 方法:

public class Book {
    private final String title, author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    public String getTitle() { return title; }
    public String getAuthor() { return author; }

    public Book withAuthor(String newAuthor) {
        return new Book(this.title, newAuthor);
    }
}

此方法 (withAuthor) 不会更改您的图书。它制作了一本新书,与本书同名,但注明作者。

注意:另一种常见的进入模糊术语的方法是,如果你有一个可变类型的最终字段。想象一下:

public class Book {
    // books can actually have multiple authors...
    private List<String> authors = new ArrayList<String>();

    public List<String> getAuthors() { return authors; }
}

这个 class 是不可变的吗?对于大多数意图和目的来说,它不是一成不变的:

book.getAuthors().add("Reinier Zwitserloot");

解决方法是确保该列表也是不可变的。 List.of 制作不可变列表,或者您可以通过包装它使其有效地不可变:list = Collections.unmodifiableList(someArrayList); 将完成工作。

这对于第一步 java 来说相当复杂 material。让我们坚持:请注意,如果字段类型可以包含可变内容,那么您无能为力 - 没有 setter 并且拥有 final 字段不再有帮助。字符串和所有原语——不过都是不可变的。还好。