为什么最终关键字对于不可变类是必要的?

您能否澄清一下,为什么在课堂上我们将最终关键字作为不可变关键字时需要它。 我的意思是,如果我们将所有属性声明为private和final,那么它也是一个不可变类,不是吗?

对不起,如果问题看似简单,但我真的很困惑。 帮帮我。

编辑:我知道一个声明为final的类不能被子类化。但是如果每个属性都是私有的,那么最终会有什么区别呢?

正如堆栈器所说, final确保该类不是子类。 这一点非常重要,因为任何依赖其不变性的代码都可以安全地完成。

例如,不可变类型(其中每个字段也是不可变类型)可以在线程之间自由使用,而不必担心数据争用等。现在考虑:

 public class Person { private final String name; public Person(String name) { this.name = name; } public String getName() { return name; } } 

看起来你可以在线程之间自由地共享Person实例而没有任何问题。 但是,当您共享的对象实际上是一个可变的子类时:

 public class Employee extends Person { private String company; public Employee(String name, String company) { super(name); this.company = company; } public void setCompany(String company) { this.company = company; } public String getCompany() { return company; } } 

现在, Employee实例在线程之间共享不安全的,因为它们不是不可变的。 但是,进行共享的代码可能只知道它们是Person实例……导致它们陷入虚假的安全感。

缓存也是如此 – 缓存和重用不可变类型应该是安全的,对吧? 好吧,缓存真正属于不可变类型的实例安全的 – 但如果你正在处理一个本身不允许变异,但确实允许子类的类型,它就会突然变得不安全了。

想想java.lang.Object 。 它没有任何可变字段,但将每个Object引用视为对不可变类型的引用显然是一个坏主意。 基本上,这取决于您是否将不可变性视为类型或对象的属性。 一个真正不可变的类型声明“任何时候你看到这种类型的引用,你可以将它视为不可变的” – 而允许任意子类化的类型不能提出这种说法。

顺便说一句,有一个中途的房子:如果你可以将子类限制为只有“受信任”的地方,你可以确保一切都是不可变的,但仍然允许子类化。 Java中的访问使得这很棘手,但是在C#中你可以拥有一个只允许在同一个程序集中进行子类化的公共类 – 提供一个在不变性方面很好而且强大的公共API,同时仍然允许多态的好处。

声明为final的类不能被子类化。 另见http://docs.oracle.com/javase/tutorial/java/IandI/final.html

“ Java语言规范”中描述了final关键字的所有用法的不同语义

  • 4.12.4最终变量第80页
  • 8.1.1.2最终类Page 184
  • 8.3.1.2 final Fields Page 209
  • 8.4.3.3最终方法Page 223

‘final’作为关键字的名称建议意味着最终关键字所附加的属性不能更改(就值而言),换句话说,它的行为类似于常量。

根据你的问题,如果类的所有成员都是私有的和最终的,但是类不是最终的,那么同一个类可以inheritance,但是超类成员是不可变的,因为最终的关键字被附加到它们。

你并不需要 final来制作一个不可变的类。 也就是说,你可以创建一个不可变的类而不是最终的。

但是,如果你没有使它成为最终的,那么有人可以扩展一个类并创建一个可变的子类(通过添加新的可变字段,或以一种允许你改变受保护字段的方式重写方法)原始的不可变类)。 这是一个潜在的问题 – 它违反了Liskov替代原则 ,因为你会期望所有子类型都能保留不可分割性。

因此,通常最好使不可变类最终以避免这种风险。

不可变对象是一个对象,该状态保证在其整个生命周期内保持相同。 虽然完全可以在没有最终版本的情况下实现不变性,但是它的使用对于人类(软件开发人员)和机器(编译器)来说是明确的。

不可变对象具有一些非常理想的特征:

 they are simple to understand and easy to use they are inherently thread-safe: they require no synchronization they make great building blocks for other objects 

显然,final将帮助我们定义不可变对象。 首先将我们的对象标记为不可变,这使得其他程序员易于使用和理解。 其次是保证对象的状态永远不会改变,从而启用线程安全属性:当一个线程可以在另一个线程正在读取相同数据时更改数据时,线程并发问题是相关的。 由于不可变对象永远不会更改其数据,因此不需要同步对其的访问。

通过满足以下所有条件创建不可变类:

 Declare all fields private final. Set all fields in the constructor. Don't provide any methods that modify the state of the object; provide only getter methods (no setters). Declare the class final, so that no methods may be overridden. Ensure exclusive access to any mutable components, eg by returning copies. 

宣布为final的类不能被分类。 其他课程不能延长最终课程。 它为安全性和线程安全性提供了一些好处。

如果所有公共和受保护的方法都是最终的,并且它们都不允许修改私有字段,并且所有公共和受保护的字段都是最终的和不可变的,那么我想可以说类是半不可变的,或者是常量。

但是当你创建一个子类并且需要重写equals和hashcode时,事情就会崩溃。 并且不能因为你让它们成为最终的……所以整个事情都被打破了,所以只需让全class决定,以防止程序员偶然成为一个傻瓜。

作为这种混合版本不变性的替代方案,您有几种选择。

如果要将额外数据附加到不可变实例,请使用Map 。 就像你想要将年龄添加到名字一样,你不会做class NameAge extends String … 🙂

如果要添加方法,请创建一组静态实用程序函数。 这有点笨拙,但它是当前的Java方式,例如Apache公共充满了这样的类。

如果要添加额外的方法和数据,请使用委托方法创建一个包装类,以使用不可变类的方法。 无论如何,任何需要使用额外方法的人都需要知道它们,并且对于许多用例,在转换为派生的非不可变类或执行类似new MyWrapper(myImmutableObj)类的内容方面没有太大的实际区别。

当你真的必须引用原始的可变对象时(比如将它存储在现有的类中你无法改变),但是需要在某个地方需要额外的数据,你需要使用Map方法来保存额外的数据,或类似的东西。

如果一个不可变类Foo被密封(“final”),那么接收对Foo的引用的任何人都可以确信如果Foo被正确实现,那么引用的实例实际上将是不可变的。 如果不可变类没有被密封,那么接收对Foo的引用的人可以确信,如果引用对象的实际类(可能是Foo某些任意未知的人实现的某种衍生类型)被正确实现,实例将是不可变的。 保持Foo密封意味着任何依赖Foo人都是不可变的,必须相信每个编写源自Foo的类的人都会正确地实现它。 如果一个人想要确定每个对Foo引用实际上都是针对一个不可变的实例,而不必依赖衍生类的作者来遵守合同,那么使Foo最终可以帮助这样的保证。

另一方面,类可能派生于Foo但违反其不变性的可能性与源自任何其他类的类可能违反其父类的合同的可能性并没有太大差别。 任何接受可由外部代码子类化的任何类型的引用的代码都可能被赋予违反其父代合同的子类的实例。

决定是否应该密封不可变类别时的基本问题与任何其他类别相同:离开未密封类型的好处是否超过了这样做会带来的任何危险。 在某些情况下,拥有一个可扩展的不可变类,甚至是一个抽象的类或接口,其具体实现都是合约义务是不可变的,这是有意义的; 例如,绘图包可能有一个ImmutableShape类,其中包含一些用于定义2D转换的具体字段,属性和方法,但是一个抽象的Draw方法,允许定义衍生类型ImmutablePolygonImmutableTextObjectImmutableBezierCurve等。如果有人实现了ImmutableGradientFilledEllipse类但无法使该类型创建自己的可变GradientColorSelector副本,渐变填充多边形的颜色可能会意外更改,但这将是ImmutableGradientFilledEllipse类的错误,而不是消耗代码。 尽管实施失败的可能性未能维持“不变性”合同,但可扩展的ImmutableShape密封的类更加通用。