为什么要在Java中声明一个不可变的类final?
我读到,要在Java中创build一个不可变的类 ,我们应该执行以下操作,
- 不要提供任何setter
- 将所有字段标记为私有
- 使课程最终
为什么需要步骤3? 我为什么要把课final
标记?
如果你不把这个类标记为final
,那么我可能会突然让你看起来不可变的类实际上变为可变的。 例如,考虑这个代码:
public class Immutable { private final int value; public Immutable(int value) { this.value = value; } public int getValue() { return value; } }
现在,假设我做了以下事情:
public class Mutable extends Immutable { private int realValue; public Mutable(int value) { super(value); realValue = value; } public int getValue() { return realValue; } public void setValue(int newValue) { realValue = newValue; } public static void main(String[] arg){ Mutable obj = new Mutable(4); Immutable immObj = (Immutable)obj; System.out.println(immObj.getValue()); obj.setValue(8); System.out.println(immObj.getValue()); } }
注意,在我的Mutable
子类中,我重写了getValue
的行为来读取在我的子类中声明的新的可变字段。 因此,最初看起来不可变的你的类实际上并不是不变的。 我可以在需要Immutable
对象的地方传递这个Mutable
对象,假如对象是真正不可变的,那么可以做非常糟糕的事情。 标记基类final
防止这种情况发生。
希望这可以帮助!
与许多人认为相反, 不要求做一个不可改变的类final
。
使不可变类final
的标准参数是,如果你不这样做,那么子类可以添加可变性,从而违反了超类的契约。 class上的客户将承担不变的责任,但当他们下面发生变化时,他们会感到惊讶。
如果你把这个论点看作是逻辑的极端,那么所有的方法都应该是final
,否则一个子类可能以一种不符合超类契约的方式重写一个方法。 有趣的是,大多数Java程序员认为这很荒谬,但对于不可变类应该是final
的想法来说还是可以的。 我怀疑这与Java程序员总的来说并不完全适应不变性的概念,可能是某种与Java中final
关键字的多重含义有关的模糊思维。
符合你的超类的契约不是总是可以或者应该总是由编译器强制执行的。 编译器可以执行你的契约的某些方面(例如:一组最小的方法和他们的types签名),但是编译器不能强制执行的典型契约有很多部分。
不变性是一个类的合同的一部分。 这与人们习惯的一些东西有些不同,因为它说明了类(以及所有子类) 不能做什么,而我认为大多数Java(和通常是OOP)程序员倾向于将合同视为与一个class级可以做什么, 而不是做什么。
不变性也影响的不仅仅是单一的方法 – 它会影响整个实例 – 但这与Java中equals
和hashCode
的工作方式并没有太大的不同。 这两种方法在Object
都有一个具体的合同。 这份合同非常仔细地列出了这些方法无法做到的事情。 这个合同在子类中更具体。 以违反合约的方式覆盖equals
或hashCode
是很容易的。 事实上,如果你只重写这两种方法中的一种,而没有其他方法,那么很有可能是你违反了合同。 所以应该equals
和hashCode
已经在Object
声明为final
来避免这种情况? 我想大多数人会认为他们不应该这样做。 同样,没有必要使不可变的类final
。
也就是说,你的大多数类,不可改变或不可能, 应该是final
。 参见有效的Java第二版第17项:“devise和文档inheritance或者禁止它”。
因此,第3步的正确版本应该是:“让类成为最终的,或者在为子类devise时明确logging所有子类必须继续是不变的。
不要标记整个class级的最后。
有一些正确的理由让一个不可改变的类被扩展,如其他一些答案中所述,所以将这个类标记为final并不总是一个好主意。
最好将自己的房产标记为私人的和最终的,如果你想保护“合同”,将你的获得者标记为最终的。
通过这种方式,你可以允许这个类被扩展(是的,甚至可以通过一个可变类),然而你的类的不可改变的方面是受保护的。 属性是私有的,不能被访问,这些属性的获取者是最终的,不能被覆盖。
任何使用不可变类的实例的其他代码都可以依赖类的不可变方面,即使它传递的子类在其他方面也是可变的。 当然,因为它需要你的class级的一个实例,它甚至不知道这些其他方面。
如果不是最终的,那么任何人都可以扩展这个类并做任何他们喜欢的事情,比如提供setter,隐藏你的私有variables,并且基本上使其变得可变。
这限制了其他class级扩展你的class级。
最后一堂课不能被其他课程延续。
如果一个类扩展你想要作为不可变的类,它可能会由于inheritance原则而改变类的状态。
只是澄清“可能会改变”。 子类可以重写超类的行为,如使用方法重写(如templatetypedef / Ted Hop答案)
如果你不做最后的决定,我可以扩展它,使它不可变。
public class Immutable { privat final int val; public Immutable(int val) { this.val = val; } public int getVal() { return val; } } public class FakeImmutable extends Immutable { privat int val2; public FakeImmutable(int val) { super(val); } public int getVal() { return val2; } public void setVal(int val2) { this.val2 = val2; } }
现在,我可以将FakeImmutable传递给期望不可变的任何类,并且不会像预期的合同那样行事。
为了创build不可变类,并不强制将类标记为final。
让我从java类本身的“BigInteger”类的例子是不可变的,但它不是最终的。
实际上,不变性是一个根据哪个对象创build的概念,不能被修改的概念。
我们从JVM的angular度来看,从JVM的angular度来看,所有线程必须共享对象的同一副本,并且在任何线程访问它之前完全构造,并且在构造之后对象的状态不会改变。
不变性意味着一旦创build对象就无法改变对象的状态,这是通过三个拇指规则来实现的,这使得编译器能够识别该类是不可变的,它们如下所示:
- 所有非私人领域应该是最终的
- 确保类中没有可以直接或间接更改对象字段的方法
- 在类中定义的任何对象引用都不能在类之外修改
有关更多信息,请参阅下面的URL
http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html
假设以下class级不是final
:
public class Foo { private int mThing; public Foo(int thing) { mThing = thing; } public int doSomething() { /* doesn't change mThing */ } }
它显然是不可变的,因为即使是子类也不能修改mThing
。 但是,一个子类可以是可变的:
public class Bar extends Foo { private int mValue; public Bar(int thing, int value) { super(thing); mValue = value; } public int getValue() { return mValue; } public void setValue(int value) { mValue = value; } }
现在可以赋值给Foo
types的variables的对象不再保证是可变的。 这可能会导致哈希,平等,并发等问题。
devise本身没有价值。 devise总是用来实现一个目标。 这里的目标是什么? 我们是否想要减less代码中的惊喜数量? 我们想要防止错误? 我们是否盲目遵守规则?
而且,devise总是要付出代价的。 每一个值得名字的devise都意味着你有目标的冲突 。
考虑到这一点,您需要find这些问题的答案:
- 有多less明显的错误会阻止?
- 这可以防止多less微妙的错误?
- 这样多久会使其他代码更复杂(=更容易出错)?
- 这是否使testing更容易或更难?
- 你的项目中的开发人员有多好? 用大锤需要多less指导?
假设你的团队中有许多初级开发人员。 他们会拼命尝试任何愚蠢的事情,只是因为他们不知道解决问题的好办法。 让类最终可以防止错误(好),但也可以让他们想出“聪明”的解决scheme,如在代码中将所有这些类复制到可变的类中。
另一方面,在任何地方使用它之后,很难做出一个类的final
,但如果你发现需要扩展它,很容易在final
类没有final
。
如果你正确地使用接口,你可以通过总是使用接口避免“我需要做这个可变的”问题,然后在需要的时候添加一个可变的实现。
结论:这个答案没有“最好的”解决scheme。 这取决于你愿意支付哪个价格以及哪个价格。