为什么不可变 class 不能有 setter 方法?
Why can't an immutable class have setter methods?
我正在阅读有关不可变 classes 的内容,据说使 class 不可变的方法是:
- 1 - 使 class 最终化以防止继承
- 2 - 使可变变量成为最终变量
- 3 - 不提供
setter
方法。
我认为第三个条件是不必要的。当我们将变量设置为 final 并为其提供任何值时,即使通过 setter
方法也无法为其分配新值(因为一旦为其分配了值,便无法更改 final 变量) .那么为什么我们需要没有setter方法的第三个条件呢?
我是不是理解错了?
你说的有点对。根据定义,setter 用给定值替换字段。如果所有字段都是最终字段,那么您无论如何都不可能提供 setter。
我对如何编写不可变 class 的描述是:
- 使所有字段最终
- 确保每个字段的类型本身是不可变的
如果您非常小心确保它们永远不会更改,则可以使用可变字段编写不可变 class,但在这种情况下您需要非常小心。
是的,可以减少到
- 将 class 设置为 final 以防止继承;
- 使可变变量成为最终变量,因此不必费心提供任何设置器;
然而,出于教育目的,较短的要点可能效果更好 - 即使有点多余。
public class Person{
private String name;
public Person(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
现在,Person 显然不是不可变的 class。这并不意味着,Person 的一个实例不能是另一个 class 的成员(据推测)是不可变的。
public final class MyImmutableClass {
// p is final, so it can't be re-referenced
private final Person p;
public MyImmutableClass(Person p) {
this.p = p;
}
// it can be altered, though
public void setPersonName(String name) {
this.p.setName(name);
}
public String toString() {
return "Person: " + p.getName();
}
}
现在,我们有一个不可变的 class,但是,它确实包含一个 setter。这 setter 主动更改(最终)字段 p.
的成员
public static void main(String[] args) {
MyImmutableClass c = new MyImmutableClass(new Person("OriginalName"));
System.out.println(c);
c.setPersonName("AlteredName");
System.out.println(c);
}
还有 .. 给你了。通过 setter 更改成员(即使变量是最终变量)。请理解,'final variable' 不一定是常量,在大多数情况下它的状态可以改变。 final 变量的要点在于它不能是 re-referenced。注意,我们也可以有这样的方法:
public void setPerson(Person p) {
this.p.setName(p.getName());
}
final 变量本身只是一个常量,以防类型本身是不可变类型,或者如果它是原始类型,但您应该了解大多数类型都是可变的。
类型是不可变的还是原始类型并且声明为最终类型?当然,添加一个setter。但是,为了什么目的?误导使用你的人class?
第 2 项中提到的变量可以是一个引用,它可以是可变的(如列表或集合),即使变量本身是 final
.
这就是为什么我们有像 Collections.unmodifiableList
这样的实用程序来使可变 类 几乎不可变。
所以禁止设置器是为了防止意外更改最终变量的状态。
将字段设置为 final 不足以(甚至不需要)保证不变性。您需要在不可变 class.
中制作可变对象的防御副本
class Foo {
private String str;
public Foo(String str) {
this.str = str;
}
public String getString() {
return str;
}
}
上面的 class 是不可变的,因为:
- 字符串是不可变的。
- 字段 'str' 是私有的,不能更改。
现考虑以下MyDate
class.
public class MyDate {
private Date; // date is not immutable
public MyDate(Date date) {
this.date = date;
}
public Date getDate() {
return date;
}
}
上面的 MyDate class 不是不可变的,因为用户可以执行以下操作:
Date d = new Date(<someDate>);
MyDate md = new MyDate(d);
d.set(<someDate>); // oops, just changed value in MyDate via external reference.
也可以通过 getDate()
完成同样的操作。
要使 MyClass
不可变,请在构造函数中制作 Date
和 getter 的防御性副本。这些可以防止 class 的用户更改 date
字段:
- 使用对构造函数参数的引用。
- 通过 getter
检索日期字段
public class MyDate {
private Date; // date is not immutable
public MyDate(Date date) {
this.date = new Date(date);
}
public Date getDate() {
return new Date(date);
}
}
我正在阅读有关不可变 classes 的内容,据说使 class 不可变的方法是:
- 1 - 使 class 最终化以防止继承
- 2 - 使可变变量成为最终变量
- 3 - 不提供
setter
方法。
我认为第三个条件是不必要的。当我们将变量设置为 final 并为其提供任何值时,即使通过 setter
方法也无法为其分配新值(因为一旦为其分配了值,便无法更改 final 变量) .那么为什么我们需要没有setter方法的第三个条件呢?
我是不是理解错了?
你说的有点对。根据定义,setter 用给定值替换字段。如果所有字段都是最终字段,那么您无论如何都不可能提供 setter。
我对如何编写不可变 class 的描述是:
- 使所有字段最终
- 确保每个字段的类型本身是不可变的
如果您非常小心确保它们永远不会更改,则可以使用可变字段编写不可变 class,但在这种情况下您需要非常小心。
是的,可以减少到
- 将 class 设置为 final 以防止继承;
- 使可变变量成为最终变量,因此不必费心提供任何设置器;
然而,出于教育目的,较短的要点可能效果更好 - 即使有点多余。
public class Person{
private String name;
public Person(String name){
this.name = name;
}
public String getName(){
return this.name;
}
public void setName(String name) {
this.name = name;
}
}
现在,Person 显然不是不可变的 class。这并不意味着,Person 的一个实例不能是另一个 class 的成员(据推测)是不可变的。
public final class MyImmutableClass {
// p is final, so it can't be re-referenced
private final Person p;
public MyImmutableClass(Person p) {
this.p = p;
}
// it can be altered, though
public void setPersonName(String name) {
this.p.setName(name);
}
public String toString() {
return "Person: " + p.getName();
}
}
现在,我们有一个不可变的 class,但是,它确实包含一个 setter。这 setter 主动更改(最终)字段 p.
的成员public static void main(String[] args) {
MyImmutableClass c = new MyImmutableClass(new Person("OriginalName"));
System.out.println(c);
c.setPersonName("AlteredName");
System.out.println(c);
}
还有 .. 给你了。通过 setter 更改成员(即使变量是最终变量)。请理解,'final variable' 不一定是常量,在大多数情况下它的状态可以改变。 final 变量的要点在于它不能是 re-referenced。注意,我们也可以有这样的方法:
public void setPerson(Person p) {
this.p.setName(p.getName());
}
final 变量本身只是一个常量,以防类型本身是不可变类型,或者如果它是原始类型,但您应该了解大多数类型都是可变的。 类型是不可变的还是原始类型并且声明为最终类型?当然,添加一个setter。但是,为了什么目的?误导使用你的人class?
第 2 项中提到的变量可以是一个引用,它可以是可变的(如列表或集合),即使变量本身是 final
.
这就是为什么我们有像 Collections.unmodifiableList
这样的实用程序来使可变 类 几乎不可变。
所以禁止设置器是为了防止意外更改最终变量的状态。
将字段设置为 final 不足以(甚至不需要)保证不变性。您需要在不可变 class.
中制作可变对象的防御副本class Foo {
private String str;
public Foo(String str) {
this.str = str;
}
public String getString() {
return str;
}
}
上面的 class 是不可变的,因为:
- 字符串是不可变的。
- 字段 'str' 是私有的,不能更改。
现考虑以下MyDate
class.
public class MyDate {
private Date; // date is not immutable
public MyDate(Date date) {
this.date = date;
}
public Date getDate() {
return date;
}
}
上面的 MyDate class 不是不可变的,因为用户可以执行以下操作:
Date d = new Date(<someDate>);
MyDate md = new MyDate(d);
d.set(<someDate>); // oops, just changed value in MyDate via external reference.
也可以通过 getDate()
完成同样的操作。
要使 MyClass
不可变,请在构造函数中制作 Date
和 getter 的防御性副本。这些可以防止 class 的用户更改 date
字段:
- 使用对构造函数参数的引用。
- 通过 getter 检索日期字段
public class MyDate {
private Date; // date is not immutable
public MyDate(Date date) {
this.date = new Date(date);
}
public Date getDate() {
return new Date(date);
}
}