简体   繁体   English

不可变类应该是最终的?

[英]immutable class should be final?

It says in this article that: 它在本文中说:

Making a class final because it is immutable is a good reason to do so. 让一个类成为最终因为它是不可变的是这样做的一个很好的理由。

I'm a bit puzzled by this... I understand that immutability is a good thing from the POV of thread-safety and simplicity, but it seems that these concerns are somewhat orthogonal to extensibility. 我对此感到有点困惑......我理解不变性是线程安全性和简单性的POV中的好东西,但似乎这些问题与可扩展性有些正交。 So, why is immutability a good reason for making a class final? 那么,为什么不变性成为最终上课的一个很好的理由呢?

The explanation for this is given in the book 'Effective Java' “有效Java”一书中给出了对此的解释。

Consider BigDecimal and BigInteger classes in Java . 考虑Java中的BigDecimalBigInteger类。

It was not widely understood that immutable classes had to be effectively final when BigInteger and BigDecimal were written, so all of their methods may be overridden. BigIntegerBigDecimal被编写时,不可变的类必须是有效的最终版本并没有被广泛理解,所以他们的所有方法都可能被覆盖。 Unfortunately, this could not be corrected after the fact while preserving backward compatibility. 不幸的是,在保留向后兼容性的同时,这无法得到纠正。

If you write a class whose security depends on the immutability of a BigInteger or BigDecimal argument from an un-trusted client, you must check to see that the argument is a “real” BigInteger or BigDecimal, rather than an instance of an un trusted subclass. 如果编写一个类,其安全性取决于来自不可信客户端的BigInteger或BigDecimal参数的不变性,则必须检查该参数是否为“真实”BigInteger或BigDecimal,而不是不受信任的子类的实例。 If it is the latter, you must defensively copy it under the assumption that it might be mutable. 如果是后者,则必须在假设它可能是可变的情况下进行防御性复制。

   public static BigInteger safeInstance(BigInteger val) {

   if (val.getClass() != BigInteger.class)

    return new BigInteger(val.toByteArray());


       return val;

   }

If you allow sub classing, it might break the "purity" of the immutable object. 如果允许子类,则可能会破坏不可变对象的“纯度”。

Following the Liskov Substitution Principle a subclass can extend but never redefine the contract of its parent. 根据Liskov替换原则,子类可以延伸但从不重新定义其父类的合同。 If the base class is immutable then its hard to find examples of where its functionality could be usefully extended without breaking the contract. 如果基类是不可变的,那么很难找到其功能可以在不违反合同的情况下有效扩展的示例。

Note that it is possible in principle to extend an immutable class and change the base fields eg if the base class contains a reference to an array the elements within the array cannot be declared final. 请注意,原则上可以扩展不可变类并更改基本字段,例如,如果基类包含对数组的引用,则不能将数组中的元素声明为final。 Obviously the semantics of methods can also be changed via overriding. 显然,方法的语义也可以通过覆盖来改变。

I suppose you could declare all the fields as private and all the methods as final, but then what would be the use of inheriting? 我想你可以将所有字段声明为私有,并将所有方法声明为final,但那么继承的用途是什么?

Because if the class is final you can't extend it and make it mutable. 因为如果这个类是最终的,你不能扩展它并使它变得可变。

Even if you make the fields final, that only means you cannot reassign the reference, it does not mean you cannot change the object that is referred to. 即使您将字段设为final,这只意味着您无法重新分配引用,也不意味着您无法更改引用的对象。

I don't see a lot of use in a design for an immutable class that also should be extended, so final helps keep the immutability intact. 我并没有在设计中看到很多用于不可变类的应该扩展的类,所以final有助于保持不变性。

Mainly security I'd think. 我认为主要是安全性。 For the same reason String is final, anything that any security-related code wants to treat as immutable must be final. 出于同样的原因,String是final,任何与安全相关的代码都希望将其视为不可变的东西必须是最终的。

Suppose you have a class defined to be immutable, call it MyUrlClass, but you don't mark it final. 假设您有一个定义为不可变的类,请将其命名为MyUrlClass,但不要将其标记为final。

Now, somebody might be tempted to write security manager code like this; 现在,有人可能会像这样编写安全管理器代码;

void checkUrl(MyUrlClass testurl) throws SecurityException {
    if (illegalDomains.contains(testurl.getDomain())) throw new SecurityException();
}

And here's what they'd put in their DoRequest(MyUrlClass url) method: 以下是他们在DoRequest(MyUrlClass url)方法中的内容:

securitymanager.checkUrl(urltoconnect);
Socket sckt = opensocket(urltoconnect);
sendrequest(sckt);
getresponse(sckt);

But they can't do this, because you didn't make MyUrlClass final. 但他们不能这样做,因为你没有让MyUrlClass最终成功。 The reason they can't do it is that if they did, code could avoid the security manager restrictions simply by overriding getDomain() to return "www.google.com" the first time it's called, and "www.evilhackers.org" the second, and passing an object of their class into DoRequest(). 他们不能这样做的原因是,如果他们这样做,代码可以简单地通过覆盖getDomain()在第一次调用时返回“www.google.com”来避免安全管理器限制,并且“www.evilhackers.org”第二个,并将其类的对象传递给DoRequest()。

I have nothing against evilhackers.org, by the way, if it even exists... 顺便说一句,如果它存在的话,我没有反对evilhackers.org的内容......

In the absence of security concerns it's all about avoiding programming errors, and it is of course up to you how you do that. 在没有安全问题的情况下,一切都是为了避免编程错误,当然由你来决定如何做到这一点。 Subclasses have to keep their parent's contract, and immutability is just a part of the contract. 子类必须保持父母的合同,不变性只是合同的一部分。 But if instances of a class are supposed to be immutable, then making it final is one good way of making sure they really are all immutable (ie that there aren't mutable instances of subclasses kicking around, which can be used anywhere that the parent class is called for). 但是如果一个类的实例应该是不可变的,那么将它作为final是一种很好的方法来确保它们确实都是不可变的(即没有可变的子类实例,它们可以在父类的任何地方使用类被称为)。

I don't think the article you referenced should be taken as an instruction that "all immutable classes must be final", especially if you have a positive reason to design your immutable class for inheritance. 我不认为你引用的文章应该被视为“所有不可变类必须是最终的”的指令,特别是如果你有充分的理由设计你的不可变类继承。 What it was saying is that protecting immutability is a valid reason for final, where imaginary performance concerns (which is what it's really talking about at that point) are not valid. 它所说的是保护不变性是最终的一个正当理由,其中虚构的性能问题(这是它在那时真正谈论的)是无效的。 Note that it gave "a complex class not designed for inheritance" as an equally valid reason. 请注意,它给出了“一个不是为继承而设计的复杂类”作为同样有效的理由。 It can fairly be argued that failing to account for inheritance in your complex classes is something to avoid, just as failing to account for inheritance in your immutable classes is. 可以肯定的是,在复杂类中没有考虑继承是一件可以避免的事情,就像在不可变类中没有考虑继承一样。 But if you can't account for it, you can at least signal this fact by preventing it. 但如果你不能解释它,你至少可以通过阻止它来表明这一事实。

Its a good idea to make a class immutable for performance reasons too. 由于性能原因,使类成为不可变是一个好主意。 Take Integer.valueOf for example. 以Integer.valueOf为例。 When you call this static method it does not have to return a new Integer instance. 当您调用此静态方法时,它不必返回新的Integer实例。 It can return a previously created instance safe in the knowledge that when it passed you a reference to that instance last time you didn't modify it (I guess this is also good reasoning from a security reason perspective too). 它可以返回一个先前创建的实例安全,因为它知道上次当你没有修改它时它传递了你对该实例的引用(我想这也是一个很好的推理,从安全理由的角度来看)。

I agree with the standpoint taken in Effective Java on these matters -that you should either design your classes for extensibility or make them non-extensible. 我同意Effective Java中关于这些问题的观点 - 你应该设计你的类以实现可扩展性,或者使它们不可扩展。 If its your intention to make something extensible perhaps consider an interface or abstract class. 如果你打算做一些可扩展的东西,也许可以考虑一个接口或抽象类。

Also, you don't have to make the class final. 此外,您不必将课程定稿。 You can make the constructors private. 您可以将构造函数设为私有。

So, why is immutability a good reason for making a class final? 那么,为什么不变性成为最终上课的一个很好的理由呢?

As stated in oracle docs there are basically 4 steps to make a class immutable. 正如oracle文档中所述,基本上有4个步骤使类不可变。

So one of the point states that 所以其中一点就是

to make a class Immutable class should be marked as either final or have private constructor 创建一个类Immutable类应该标记为final或具有私有构造函数

Below are the 4 steps to make a class immutable (straight from the oracle docs) 下面是使类不可变的4个步骤(直接来自oracle文档)

  1. Don't provide "setter" methods — methods that modify fields or objects referred to by fields. 不要提供“setter”方法 - 修改字段引用的字段或对象的方法。

  2. Make all fields final and private. 使所有字段成为最终和私有。

  3. Don't allow subclasses to override methods. 不允许子类重写方法。 The simplest way to do this is to declare the class as final. 最简单的方法是将类声明为final。 A more sophisticated approach is to make the constructor private and construct instances in factory methods. 更复杂的方法是使构造函数私有并在工厂方法中构造实例。

  4. If the instance fields include references to mutable objects, don't allow those objects to be changed: 如果实例字段包含对可变对象的引用,则不允许更改这些对象:

    • Don't provide methods that modify the mutable objects. 不要提供修改可变对象的方法。
    • Don't share references to the mutable objects. 不要共享对可变对象的引用。 Never store references to external, mutable objects passed to the constructor; 永远不要存储对传递给构造函数的外部可变对象的引用; if necessary, create copies, and store references to the copies. 如有必要,创建副本并存储对副本的引用。 Similarly, create copies of your internal mutable objects when necessary to avoid returning the originals in your methods. 同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象。

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

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