简体   繁体   English

每个不可变的班级应该是最终的吗?

[英]Should every immutable class be final?

I was designing a Card class to be used in a Blackjack game. 我正在设计一个用于二十一点游戏的Card类。

My design was to make a Card class with a getValue() that returns, for example, 11 for J, 12 for Q and 13 for K, and then extend it with a BlackjackCard class to override that method so that those cards return 10. 我的设计是使用getValue()创建一个Card类,例如,返回11表示J,12表示Q,13表示K,然后使用BlackjackCard类扩展它以覆盖该方法,以便这些卡返回10。

Then something hit me: objects of the Card class should be immutable. 然后有些东西击中了我:Card类的对象应该是不可变的。 So I re-read Effective Java 2nd Edition to see what to do and I there I found that immutable classes need to be final, to avoid a subclass to break the immutability. 所以我重新阅读了Effective Java 2nd Edition,看看该做什么,我发现不可变类需要是最终的,以避免子类打破不变性。

I also looked in Internet and everyone seems to agree in that point. 我也看过互联网,每个人似乎都同意这一点。

So should the Card class be final? Card类应该是最终的吗?

How can you break the immutability of this class, be extending it: 你如何打破这个类的不变性,扩展它:

class Card {
  private final Rank rank;
  private final Suit suit;
  public Card(Rank rank, Suit suit) {
    this.rank = rank;
    this.suit = suit;
  }
  public Rank getRank() {
    return rank;
  }
  public Suit getSuit() {
    return suit;
  }
  public int getValue() {
    return rank.getValue();
  }
}

Thanks. 谢谢。

A subclass cannot actually modify the values of private final properties in its parent, but it could behave as though it has, which is what Effective Java warns against : 子类实际上不能修改其父级中的private final属性的值,但它可能表现得好像,这就是Effective Java警告的

Ensure that the class can't be extended. 确保无法扩展该类。 This prevents careless or malicious subclasses from compromising the immutable behavior of the class by behaving as if the object's state has changed. 这可以防止粗心或恶意子类通过表现为对象的状态已更改来破坏类的不可变行为。

You can do this: 你可以这样做:

class MyCard extends Card {

  public MyCard(Rank rank, Suit suit) {
    super(rank, suit);
  }

  @Override
  public Rank getRank() {
    // return whatever Rank you want
    return null;
  }

  @Override
  public Suit getSuit() {
    // return whatever Suit you want
    return null;
  }

  @Override
  public int getValue() {
    // return whatever value you want
    return 4711;
  }

} }

The extending class does even not have to declare the same constructor as the parent class. 扩展类甚至不必声明与父类相同的构造函数。 It can have a default constructor and does not care anything about the final members of the parent class. 它可以有一个默认的构造函数,并不关心父类的最终成员。 [ That statement is wrong - see the comments ]. [ 该陈述是错误的 - 见评论 ]。

Answer is yes, Card needs to be final. 答案是肯定的,卡需要是最终的。

Combining the responses of K. Claszen and lwburk, see the following: 结合K. Claszen和lwburk的回应,请参阅以下内容:

public class MyCard extends Card {
    private Rank myRank;
    private Suit mySuit;

    public MyCard(Rank rank, Suit suit) {
        this.myRank = rank;
        this.mySuit = suit;
    }

    @Override public Rank getRank() { return myRank; }

    public void setRank(Rank rank) { this.myRank = rank; }

    @Override public Suit getSuit() { return mySuit; }

    public void setSuit(Suit suit) { this.mySuit = suit; }

    @Override public int getValue() { return myRank.getValue(); }
}

This extension completely ignores the parent state and replaces it with its own, mutable state. 此扩展完全忽略父状态,并将其替换为自己的可变状态。 Now classes that use Card in polymorphic contexts can't depend upon its being immutable. 现在,在多态上下文中使用Card的类不能依赖于它是不可变的。

When they say immutable classes should be final they are referring to how you can ensure immutability not that because something is immutable it has to be final. 当他们说不可变类应该是最终的时,他们指的是你如何确保不变性而不是因为某些东西是不可变的它必须是最终的。 Its a minor distinction. 它是一个次要的区别。 If you don't want your class ever extended it should be final. 如果你不希望你的课程延长,那么它应该是最终的。

In general, this is a good recommendation. 一般来说,这是一个很好的建议。 however, if you control all the code, then sometimes it is useful to be able to extend an immutable class (possibly to create another immutable class with additional info). 但是,如果您控制所有代码,那么有时能够扩展不可变类(可能创建另一个具有附加信息的不可变类)是有用的。 as with most recommendations, you have to make intelligent choices as to when they make sense and when they may not. 与大多数建议一样,您必须做出明智的选择,以确定它们何时有意义,何时可能没有。

If arbitrary code can extend an immutable class, then arbitrary code can produce objects that behave a lot like the immutable class, but aren't immutable. 如果任意代码可以扩展不可变类,那么任意代码都可以生成与不可变类很相似的对象,但不是不可变的。 If people who write such code would be unable to damage anything but themselves, then such a situation may be tolerable. 如果编写此类代码的人除了自己之外不会损坏任何东西,那么这种情况可能是可以容忍的。 If someone could use such a class to bypass security measures, then it should not be tolerated. 如果有人可以使用这样的类来绕过安全措施,那么就不应该容忍它。

Note that it can at times be useful to have a class which is extensible, but which promises immutability on behalf of all derived classes. 请注意,有时可以使用一个可扩展的类,但是它承诺代表所有派生类的不变性。 Such a class and its consumers would, of course, have to rely upon inheritors of the class not to do anything weird and wacky, but sometimes such an approach may still be better than any alternative. 当然,这样一个阶级及其消费者必须依靠班级的继承者不做任何奇怪和古怪的事情,但有时这种方法可能仍然比任何替代方法都要好。 For example, one might have a class which is supposed to perform some action at some particular time, with the proviso that objects of the class may be arbitrarily aliased. 例如,一个人可能有一个应该在某个特定时间执行某些操作的类,条件是该类的对象可能被任意别名。 Such a class would not be terribly useful if neither the class, nor any of its fields, nor any fields in any of its derived classes, etc. could use derived types, since the definition of a class would restrict what types of actions could be performed. 如果类,任何字段,任何派生类中的任何字段等都不能使用派生类型,那么这样的类就不会非常有用,因为类的定义会限制什么类型的操作可能是执行。 The best approach would probably be to have the class not be declared final, but make very clear in the documentation that the behavior of mutable subclasses would not be consistent or predictable. 最好的方法可能是让类不被声明为final,但在文档中明确指出可变子类的行为不一致或不可预测。

You could have a Valuator that's tied to the game and remove the getValue from the class, this way Card can be final : 你可以有一个与游戏绑定的Valuator并从类中删除getValue ,这样Card可以是final

final class Card {
  private final Rank rank;
  private final Suit suit;
  public Card(Rank rank, Suit suit) {
    this.rank = rank;
    this.suit = suit;
  }
  public Rank getRank() {
    return rank;
  }
  public Suit getSuit() {
    return suit;
  }
}

And valuators work like this: 估值师的工作方式如下:

interface CardValuator {
  int getValue(Card card);
}

class StandardValuator implements CardValuator {
  @Override
  public int getValue(Card card) {
    return card.getRank().getValue();
  }
}

class BlackjackValuator implements CardValuator {
  @Override
  public int getValue(Card card) {
    ...
  }
}

Now, if you still want to keep your Card hierarchy, marking the Card 's methods final will prevent a child class from overriding them. 现在,如果您仍想保留您的Card层次结构,则将Card方法标记为final将阻止子类覆盖它们。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM