[英]Immutable Java class with non-final member
在掌握Java不變性的想法時,我仍然遇到一些問題。 我了解到,它與C ++中的const
-ness不同,並且final
類僅具有自身不可變的類的final
成員是不可變的。 例如,以下類是不可變的:
public final class A {
final String x;
final int y;
public A(String x, String y) {
this.x = x;
this.y = y;
}
}
除了此處提供的指南以及其他地方的類似內容外,還有其他正式定義嗎?
考慮以下示例。 Person
是一成不變的嗎? 有沒有一種方法,使之不可改變除了使成員mother
和father
final
。 我無法使它們final
因為我必須使用任意排序從輸入文件中構建People
對象的列表,並且不想對該輸入執行拓撲排序。 同樣,周期的情況應該可以表示出來。
public final class Person {
Person father = null;
Person mother = null;
public final String name;
Person(String name) { this.name = name; }
public Person getFather() { return father; }
public Person getMother() { return mother; }
}
// in the same package
public class TrioBuilder {
// build trio of child, mother, and father
public static ArrayList<Person> build(String c, String m, String f) {
Person child = new Person(c);
Person mother = new Person(m);
Person father = new Person(f);
child.father = father;
child.mother = mother;
ArrayList<Person> result = new ArrayList<Person>();
result.add(child);
result.add(mother);
result.add(father);
return result;
}
}
這非常簡單:只要您創建類的實例,該類都是不可變的,
您無法更改該實例的內部狀態/數據。 是否實施
使用final
機制或其他機制是另一個問題。
摘自Effective Java 2nd edition, by Joshua Bloch
的Effective Java 2nd edition, by Joshua Bloch
:
要使類不可變,請遵循以下五個規則:
不要提供任何修改對象狀態的方法(稱為mutators)。
確保不能擴展該類 。 這樣可以防止粗心或惡意的子類通過改變對象的狀態來損害類的不變行為。 防止子類化通常是通過將類定型來完成的,但是還有另一種選擇。
將所有字段定為最終值。
將所有字段設為私有。
確保獨家訪問任何可變組件。
點1
和3
至5
是不言自明的。 點2
解釋了為什么允許類擴展會影響您的可變性(或不是)。 FWIW,他在2
建議的替代方法是將構造函數設為private
,因此沒有類可以對其進行擴展(對於要擴展另一個類的類,應進行對super
構造函數的調用,在這種情況下不能進行此操作)使其有效不可擴展的。
人是一成不變的嗎?
不,不是。
不變類是final
,只有final
成員。
在您的情況下,您要使用的是一個構建器類:
final Person person = new PersonBuilder().withFather(xx).withMother(xx).build();
這樣,您可以使Person
所有成員成為final
,並且由於Person
本身是final,您將獲得一個真正的不變類。
定義不可變對象Java Doc的策略
不允許子類覆蓋方法。 最簡單的方法是將類聲明為final。 一種更復雜的方法是使構造函數私有,並在工廠方法中構造實例。
Java本身定義了必須使類為final
才能創建不可變的類。
在Java中, final
關鍵字可防止此類的任何子類。 因此,子類不可能自己定義可變字段。 您的第一個示例是不可變的,因為沒有方法可以修改字段的值。
除此之外,在Java中,使用Unsafe
可能性甚至可以修改final字段,因此沒有像其他語言那樣編譯器具有不可改變性。
Person
類的不可變版本可能如下所示:
public final class Person {
public final Person father;
public final Person mother;
public final String name;
Person(final String name) {
this(name, null, null);
}
Person(final String name, final Person father, final Person mother) {
this.name = name;
this.father = father;
this.mother = mother;
}
public Person setFather(final Person father) {
return new Person(name, father, this.mother);
}
public Person getFather() {
return father;
}
public Person setMother(final Person mother) {
return new Person(name, father, mother);
}
public Person getMother() {
return mother;
}
public static void main(final String... args) {
final Person p1 = new Person("Foo").setMother(new Person("Bar")).setFather(new Person("Baz"));
System.out.println(p1);
}
@Override
public String toString() {
return "Person{" +
"father=" + father +
", mother=" + mother +
", name='" + name + '\'' +
'}';
}
}
一個類可以是不可改變的屬性是否被聲明為final,如果你不提供修改的屬性施工后,則類是不可變的任何方法。 好吧,通常來說,但是要注意一些問題:
顯然,屬性必須是私有的,以防止通過直接訪問或通過子類化受保護字段來更改它們。
根據您是否激活了安全管理器,仍然可能可以使用反射包來修改屬性以直接訪問它們。 序列化/反序列化的一些技巧也可能使意外的事情發生。
未聲明為final的屬性可能存在並發問題-Java不保證創建對象的線程以外的其他線程將看到非final屬性的正確值,除非使用了同步。
如果該屬性引用了另一個對象,則當然可能會改變被引用對象的內部狀態,除非那也是不可改變的。
如果未將類聲明為final,則可以將其子類化,並且getters會被覆蓋,這可能會使屬性看起來已更改,盡管原始屬性保留了其值。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.