2012-09-06 73 views
40

我看,为了使Java中的class immutable,我们应该做到以下几点,为什么要在Java中声明一个不可变类final?

  1. 不提供任何setter方法
  2. 标记所有领域的私人
  3. 使该类最终

为什么需要步骤3?我为什么要标记类final

+0

'java.math.BigInteger'类就是例子,它的值是不可变的,但它不是最终的。 –

+0

@Nandkumar如果你有一个'BigInteger',你不知道它是否是不可变的。这是一个混乱的设计。 /'java.io.File'是一个更有趣的例子。 –

+1

不要让子类重写方法 - 最简单的方法是将类声明为final。更复杂的方法是使构造函数保持私有状态,并使用工厂方法构造实例 - 你可以从中解释(或者从https://docs.oracle.com/javase/tutorial/essential/concurrency/imstrat.html –

回答

75

如果您不标记类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行为读一个新的,易变的字段在我的子类中声明。因此,最初看起来不可变的你的课程确实不是不可变的。我可以通过这个Mutable对象无论哪里Immutable对象预计,可以做非常糟糕的事情来编码假设对象是真正不可变的。标记基类final可以防止发生这种情况。

希望这会有所帮助!

+4

如果我Mutable m = new Mutable(4); m.setValue(5); 在这里,我玩弄可变类对象,而不是不可变类对象。所以,我仍然困惑为什么不可变类不是不变的 – Anand

+8

@ anand-想象一下你有一个函数需要一个不可变参数。我可以传递一个'Mutable'对象给这个函数,因为'Mutable extends Immutable'。在那个函数里面,当你认为你的对象是不可变的时候,我可以有一个辅助线程,并在函数运行时改变它的值。我也可以给你一个函数存储的“可变”对象,然后在外部修改它的值。换句话说,如果你的函数假设这个值是不可变的,那么它可能很容易中断,因为我可以给你一个可变对象并稍后改变它。那有意义吗? – templatetypedef

+0

@ templatetypedef-它可能听起来很愚蠢,但我真的仍然不清楚..lets举一个例子说我有方法void fun(Immutable i)..我通过这个方法可变对象说m ..现在我该如何改变object..Can你可以参考代码示例来解释它,或者如果你可以举一些其他的例子,我也很好。 – Anand

4

这限制了其他班级扩展你的班级。

final类不能被其他类扩展。

如果一个类扩展你想作为不可变的类,它可能会由于继承原则而改变类的状态。

只是澄清“它可能会改变”。子类可以覆盖超类的行为,如使用方法覆盖(如templatetypedef/Ted Hop答案)

+2

这是真的,但为什么这里有必要? – templatetypedef

+1

是的,它为什么需要? – Anand

+0

@templatetypedef:你太快了。我正在编辑我的答案和支持点。 – kosa

5

如果它不是最终的,那么任何人都可以扩展类并做任何他们喜欢的事情,比如提供setter,隐藏你的私有变量,可变的。

+0

尽管你是对的,但如果基类字段都是私有的并且是最终的,那么为什么你可以添加可变行为的事实将会破坏事实。真正的危险是,子类可能通过覆盖行为将先前不可变的对象变成可变对象。 – templatetypedef

1

如果你没有完成它我可以扩展它,使它不可变。

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传递给期望不可变的任何类,并且它不会像预期的契约那样工作。

+2

我认为这与我的回答非常相似。 – templatetypedef

+0

是的,通过发帖检查一半,没有新的答案。然后发布时,在我之前的一分钟就有了你。至少我们没有对所有东西都使用相同的名字。 –

+0

One Correction:在FakeImmutable类中,构造函数的名字应该是FakeImmutable不可变 –

0

假设下面的类并不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类型的变量不再保证是mmutable。这可能会导致哈希,平等,并发等问题。

19

与许多人认为相反,使不可变类final不是必需的。

制定不可变类final的标准参数是如果你不这样做,那么子类可以添加可变性,从而违反超类的契约。班上的客户将承担不变性,但当他们下面发生变化时会感到惊讶。

如果你把这个参数其逻辑的极端,那么所有方法应作出final,否则子类可以覆盖在不符合其超合同的方式方法。有趣的是,大多数Java程序员都认为这很荒谬,但对于不可变类应该是final这个想法有点不错。我怀疑这与Java程序员通常不会完全适应不变性的概念有关,也可能与Java中final关键字的多重含义有关。

符合你的超类的合同是不是可以或应该总是由编译器执行。编译器可以强制执行合同的某些方面(例如:最少的一组方法及其类型签名),但是编译器无法强制实施典型合同的许多部分。

不变性是一个类的合同的一部分。这是从一些东西的人更习惯有点不同,因为它说什么类(及所有子类)不能做的,而我认为大多数Java(一般OOP)的程序员往往会去想合同的涉及什么样的可以做什么,而不是不能做什么。

不变性也影响不仅仅是一个单一的方法更—它会影响整个实例—但这并不比在Java中的工作方式equalshashCode真的太大的不同。这两种方法在Object中列出了具体的合同。这份合同非常仔细地列出了这些方法不能做的事情。这个合同在子类中更具体。以违反合同的方式覆盖equalshashCode是非常容易的。事实上,如果你只是忽略了这两种方法中的一种,那么很可能是你违反了合同。所以equalshashCode已被宣布finalObject以避免这种情况?我认为大多数人会认为他们不应该这样做。同样,没有必要制作不可变类final

这就是说,你的大部分类,不可变或不可能,可能应该final。见有效的Java第二版项目17:“继承或禁止它的设计和文档”。

因此,第3步的正确版本应该是:“让类最终完成,或者在为子类设计时明确记录所有子类必须继续为不可变的。”

+1

值得注意的是,在给定两个对象“X”和“Y”的情况下,Java“预期”但不强制执行,“X.equals(Y)”的值将是不可变的(只要'X'和' Y'继续引用相同的对象)。它对散列码有类似的期望。很显然,没有人应该期望编译器强化等价关系和散列码的不变性(因为它显然不能)。我认为没有理由人们期望它能够被强制执行于其他类型的方面。 – supercat

+1

另外,在许多情况下,有一个抽象类型的合约指定了不变性可能会有用。例如,可以有一个抽象的ImmutableMatrix类型,给定一个坐标对,返回一个“double”。可以派生出一个'GeneralImmutableMatrix',它使用一个数组作为后备存储,但也可能有例如'ImmutableDiagonalMatrix',它只是沿着对角线存储一系列项目(读取项目X,Y将产生Arr [X](如果X == y,否则为零)。 – supercat

+1

我最喜欢这个解释。总是让一个不可变的类“final”限制它的用处,特别是当你设计一个API来扩展时。为了线程安全性,尽可能使你的类不可变,但保持它的可扩展性是有意义的。你可以将字段保护为final,而不是private final。然后,明确建立一个关于遵守不变性和线程安全保证的子类的合同(在文档中)。 – curioustechizen

9

不要标记整个班最后。

有一些正确的理由让一个不可变的类可以像其他一些答案中所述的那样扩展,所以将类标记为final并不总是一个好主意。

最好是将您的物业标记为私人物品和最终物品,如果您希望保护“合同”将您的获得者标记为最终物品。

通过这种方式,您可以允许该类进行扩展(可能甚至可以通过可变类),但是类的不可变方面会受到保护。属性是私有的,不能访问,这些属性的获取者是最终的,不能被覆盖。

使用不可变类的实例的任何其他代码将能够依赖于类的不可变方面,即使它传递的子类在其他方面是可变的。当然,因为它需要你的班级的一个实例,它甚至不知道这些其他方面。

+0

我宁愿将所有不可变的方面封装在一个单独的类中,并通过构图来使用它。在单个代码单元中混合可变和不可变状态会比较容易推理。 – toniedzwiedz

+0

完全同意。如果您的可变类包含您的不可变类,构图将是实现此目的的一种非常好的方法。如果你的结构是另一种方式,这将是一个很好的部分解决方案。 –

1

对于创建不可变类,不强制将该类标记为final。

让我从java类本身的“BigInteger”类中的一个例子是不可变的,但它不是最终的。

实际上,不变性是一个概念,根据哪个对象创建的概念,它不能被修改。

让我们从JVM的角度来看,从JVM的角度来看,所有线程必须共享对象的同一副本,并且在任何线程访问它之前完全构造并且对象的状态在其构建后不会改变。

不变性意味着没有办法哟改变对象的状态,它一旦建立,这是由三个拇指规则,这使得编译器识别类是不可变的实现,有如下几点: -

  • 所有非私有字段应该被最终
  • 确保有在类没有方法可以改变直接或间接
  • 的对象的字段的类中定义的任何对象引用不能从外部修改

欲了解更多信息,请参阅以下网址

http://javaunturnedtopics.blogspot.in/2016/07/can-we-create-immutable-class-without.html

0

设计本身没有价值。设计总是用来实现一个目标。这里的目标是什么?我们是否想要减少代码中的惊喜数量?我们是否想要防止错误?我们是否盲目遵守规则?

另外,设计总是要付出代价的。 Every design that deserves the name means you have a conflict of goals。记住

有了这一点,你需要找到这些问题的答案:

  1. 多少明显的错误将在预防?
  2. 这可以防止多少微妙的错误?
  3. 这会使其他代码更复杂(=更容易出错)的频率如何?
  4. 这是否使测试更容易或更难?
  5. 您项目中的开发人员有多好?他们需要多少指导才能使用大锤?

假设您的团队中有许多初级开发人员。他们会拼命尝试任何愚蠢的事情,只是因为他们不知道解决问题的好方法。让类最终可以防止错误(好),但也可以让他们想出“聪明”的解决方案,例如在代码中将所有这些类复制到可变的类中。

。另一方面,这将是很难使一个类final它正在使用无处不在后,但它很容易使一个final类非final以后,如果你发现你需要扩展它。

如果您正确使用接口,您可以通过始终使用接口,然后在需要时添加可变实现来避免“我需要使这个可变”问题。

结论:这个答案没有“最佳”解决方案。这取决于你愿意支付哪个价格以及哪些价格。

相关问题