不可变类应该是最终的吗?

发布于 2024-07-06 03:21:41 字数 277 浏览 7 评论 0 原文

本文中指出:

将一个类设为 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. So, why is immutability a good reason for making a class final?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

北斗星光 2024-07-13 03:21:41

一书中给出

对此的解释在《Effective Java》 《考虑 Java 中的 BigDecimal 和 BigInteger 类》

。 人们并没有广泛理解不可变类必须是有效的最终类
当编写 BigIntegerBigDecimal 时,因此它们的所有方法都可能是
被覆盖。 不幸的是,在保留向后兼容性的同时,无法在事后纠正这一问题。

如果您编写的类的安全性取决于来自不受信任的客户端的 BigInteger 或 BigDecimal 参数的不变性,则必须检查该参数是否是“真正的”BigInteger 或 BigDecimal,而不是不受信任的子类。 如果是后者,您必须在假设它可能可变的情况下防御性地复制它。

public static BigInteger safeInstance(BigInteger val) {
    if (val.getClass() != BigInteger.class)
        return new BigInteger(val.toByteArray());

    return val;
}

如果您允许子类化,则可能会破坏不可变对象的“纯度”。

The explanation for this is given in the book 'Effective Java'

Consider BigDecimal and BigInteger classes in Java .

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. 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. 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.

坦然微笑 2024-07-13 03:21:41

因为如果该类是最终类,您就无法扩展它并使其可变。

即使您将字段设置为最终字段,也仅意味着您无法重新分配引用,并不意味着您无法更改所引用的对象。

我没有看到在设计中大量使用也应该扩展的不可变类,因此 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.

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.

雨巷深深 2024-07-13 03:21:41

遵循里氏替换原则,子类可以扩展但永远不会重新定义其父类的契约。 如果基类是不可变的,那么很难找到可以在不违反契约的情况下有效扩展其功能的示例。

请注意,原则上可以扩展不可变类并更改基字段,例如,如果基类包含对数组的引用,则数组中的元素不能声明为最终的。 显然,方法的语义也可以通过重写来改变。

我想你可以将所有字段声明为私有,将所有方法声明为final,但是继承有什么用呢?

Following the Liskov Substitution Principle a subclass can extend but never redefine the contract of its parent. 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 e.g. if the base class contains a reference to an array the elements within the array cannot be declared 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?

清泪尽 2024-07-13 03:21:41

我认为主要是安全。 出于同样的原因,String 是最终的,任何与安全相关的代码想要视为不可变的任何内容都必须是最终的。

假设您有一个定义为不可变的类,将其命名为 MyUrlClass,但您没有将其标记为 Final。

现在,有人可能会想编写这样的安全管理器代码;

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

以下是他们在 DoRequest(MyUrlClass url) 方法中放入的内容:

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

但他们不能这样做,因为您没有将 MyUrlClass 设置为最终的。 他们不能这样做的原因是,如果他们这样做了,代码只需覆盖 getDomain() 以在第一次调用时返回“www.google.com”和“www.evilhackers.org”即可避免安全管理器限制第二个,并将其类的对象传递给 DoRequest()。

顺便说一句,我对邪恶黑客组织没有任何反对意见,如果它真的存在的话……

在没有安全问题的情况下,这一切都是为了避免编程错误,当然这取决于你如何做到这一点。 子类必须遵守父类的契约,而不变性只是契约的一部分。 但是,如果一个类的实例应该是不可变的,那么将其设为 Final 是确保它们确实都是不可变的一种好方法(即不存在子类的可变实例,可以在父类的任何地方使用)需要类)。

我不认为您引用的文章应该被视为“所有不可变类必须是最终的”的指令,特别是如果您有积极的理由来设计用于继承的不可变类。 它所说的是,保护不变性是 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.

Suppose you have a class defined to be immutable, call it MyUrlClass, but you don't mark it 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:

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

But they can't do this, because you didn't make MyUrlClass final. 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().

I have nothing against evilhackers.org, by the way, if it even exists...

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 (i.e. that there aren't mutable instances of subclasses kicking around, which can be used anywhere that the parent class is called for).

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.

无声无音无过去 2024-07-13 03:21:41

那么,为什么不变性是使类成为最终类的一个很好的理由?

oracle 文档 使类不可变基本上有 4 个步骤。

因此,其中一点是

要使类不可变,类应该标记为final或具有私有构造函数

下面是使类不可变的4个步骤(直接来自oracle文档)

  1. 不要提供“setter”方法 - 修改字段或字段引用的对象的方法。

  2. 将所有字段设为最终且私有。

  3. 不允许子类重写方法。 最简单的方法是将类声明为final。 更复杂的方法是使构造函数私有并在工厂方法中构造实例。

  4. 如果实例字段包含对可变对象的引用,则不允许更改这些对象:

    • 不要提供修改可变对象的方法。
    • 不要共享对可变对象的引用。 切勿存储对传递给构造函数的外部可变对象的引用; 如有必要,创建副本并存储对副本的引用。 同样,必要时创建内部可变对象的副本,以避免在方法中返回原始对象。

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.

So one of the point states that

to make a class Immutable class should be marked as either final or have private constructor

Below are the 4 steps to make a class immutable (straight from the oracle docs)

  1. Don't provide "setter" methods — methods that modify fields or objects referred to by fields.

  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. 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.
土豪我们做朋友吧 2024-07-13 03:21:41

出于性能原因,使类不可变也是一个好主意。 以 Integer.valueOf 为例。 当您调用此静态方法时,它不必返回新的 Integer 实例。 它可以安全地返回先前创建的实例,因为它知道上次它向您传递对该实例的引用时您没有修改它(我想从安全原因的角度来看这也是很好的推理)。

我同意《Effective Java》在这些问题上所采取的观点——您应该设计您的类以实现可扩展性,或者使它们不可扩展。 如果您打算使某些东西可扩展,也许可以考虑接口或抽象类。

此外,您不必将课程定为期末考试。 您可以将构造函数设为私有。

Its a good idea to make a class immutable for performance reasons too. Take Integer.valueOf for example. When you call this static method it does not have to return a new Integer instance. 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. 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.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文