Java中的“最终类”有什么意义?


569

我正在读一本关于Java的书,它说您可以将整个类声明为final。我想不出我会在哪里使用它。

我只是编程的新手,我想知道程序员是否真的在他们的程序中使用了它。如果他们这样做了,他们什么时候使用它,以便我可以更好地理解它并知道何时使用它。

如果Java是面向对象的,并且您声明了一个类final,它是否会停止具有对象特征的类的想法?

Answers:


531

首先,我推荐这篇文章:Java:何时创建最终类


如果他们这样做了,他们什么时候使用它,以便我可以更好地理解它并知道何时使用它。

一个final类就是一个不能扩展的类

(这并不意味着所有对该类对象的引用都将如同声明为一样起作用final。)

当将一个类声明为final有用时,请参见此问题的答案:

如果Java是面向对象的,并且您声明了一个类final,它是否会停止具有对象特征的类的想法?

从某种意义上说是的。

通过将一个类标记为final,可以禁用该部分代码的强大而灵活的语言功能。有些类然而,不应该(在某些情况下可以不)被设计充分继承考虑的一个好办法。在这些情况下,将类标记为最终类是有意义的,即使它限制了OOP。(但是请记住,最后一堂课仍然可以扩展另一个非最后一课。)


39
为了增加答案,Effective Java的原则之一是支持组合而不是继承。使用final关键字也有助于实施该原则。
Riggy 2011年

9
“您这样做主要是出于效率和安全性的考虑。” 我经常听到这个话(甚至维基百科都这样说),但我仍然不明白这个论点背后的原因。有人在乎解释非最终java.lang.String最终会导致效率低下还是不安全?
MRA 2012年

27
@MRA如果我创建一个接受String作为参数的方法,则我认为它是不可变的,因为Strings是。结果,我知道我可以安全地在String对象上调用任何方法,而不必更改传递的String。如果我要扩展String,并更改子字符串的实现以更改实际的String,那么您期望不可变的String对象将不再可变。
Cruncher

1
@Sortofabeginner就像您说的那样,您希望所有String方法和字段都是最终的,以便您可以创建具有附加功能的类...此时,您最好只创建一个具有-字符串和创建对该字符串进行操作的方法。
Cruncher

1
@Shay final(除其他事项外)用于使对象不可变,因此我不会说它们之间没有任何关系。看到这里docs.oracle.com/javase/tutorial/essential/concurrency/...
CELERITAS

184

在Java中,带final修饰符的项目无法更改!

这包括最终类,最终变量和最终方法:

  • 最终班不能由任何其他班延长
  • 最终变量不能重新分配另一个值
  • 最终方法不能被覆盖

40
实际的问题是为什么,而不是什么
Francesco Menzani 2015年

8
语句“在Java中,final不能修改带有修饰符的项!” 的说法过于明确,实际上并不完全正确。正如Grady Booch所说,“一个对象具有状态,行为和身份”。尽管一旦将对象的引用标记为最终对象,我们就无法更改其身份,但我们确实有机会通过向其非字段分配新值(当然,只要它具有它们)来更改其状态final。计划获得Oracle Java认证(例如1Z0-808等)应牢记这一点,因为考试方面可能对此有疑问...
Igor Soudakevitch

33

由于安全原因,当您要阻止类的继承时,final很重要。这使您可以确保正在运行的代码不会被某人覆盖

另一种情况是优化:我似乎记得Java编译器内联了一些来自最终类的函数调用。因此,如果您调用a.x()并声明了a final,我们将在编译时知道代码将是什么并且可以内联到调用函数中。我不知道这是否真的完成,但最终还是有可能的。


7
内联通常仅在运行时由即时编译器完成。它也可以在没有final的情况下工作,但是JIT编译器还有很多工作要做,以确保没有扩展类(或者这些扩展类不涉及此方法)。
圣保罗Ebermann

一个好的写了上内联和优化的问题可以在这里找到:lemire.me/blog/archives/2014/12/17/...
乔希Hemann

24

最好的例子是

公共最后一堂课String

这是一个不变的类,不能扩展。当然,不只是使班级的决赛变得一成不变。


呵呵,有时它可以保护Rube Goldergian开发人员免受其侵害。
Zoidberg 2011年

16

相关阅读:鲍勃·马丁(Bob Martin)的《开闭原则》

关键语录:

软件实体(类,模块,功能等)应为扩展打开,但为修改而关闭。

final关键字是在Java中执行这个手段,无论是在方法或类上使用。


6
@Sean:不会声明final将类关闭以进行扩展而不是打开吗?还是我从字面上看呢?
Goran Jovic

4
@Goran全局应用final,是的。关键是在您不希望进行修改的地方选择性地应用final(当然,也可以为扩展提供良好的支持)
肖恩·帕特里克·弗洛伊德

26
在OCP中,“修改”是指修改源代码,而“扩展”是指实现继承。因此,final如果您希望关闭实现代码以进行修改而打开以通过继承进行扩展,则在类/方法声明上使用on毫无意义。
罗杰里奥

1
@Rogerio我从Spring Framework Reference(MVC)借用了参考(和解释。恕我直言,这比原始版本有意义得多。
肖恩·帕特里克·弗洛伊德

扩展已死。无用。破旧。被毁。我不在乎OCP。扩展课堂从来没有任何借口。
乔什·伍德考克

15

如果您将类层次结构想象成一棵树(就像Java中一样),则抽象类只能是分支,而最终类则只能是叶子。不属于这两个类别的类既可以是分支,也可以是叶子。

这里没有违反OO原则,最终只是提供了很好的对称性。

在实践中,如果希望对象不可变或正在编写API,则可以使用final来向API用户表明该类不打算扩展。


13

关键字final本身表示某些东西是最终的,不应以任何方式进行修改。如果标记了一个类final,则不能扩展或子类化。但是问题是为什么我们要标记一个班级final?IMO有多种原因:

  1. 标准化:某些类执行标准功能,而不是要对其进行修改,例如,执行与字符串操作或数学功能等有关的各种功能的类。
  2. 安全原因:有时我们编写执行各种与身份验证和密码相关的功能的类,并且我们不希望任何人对其进行更改。

我听说打标课final提高了效率,但坦率地说,我找不到这种论点有多大意义。

如果Java是面向对象的,并且您将类声明为final,它是否会停止具有对象特征的类的想法?

也许是的,但有时这是预期的目的。有时,我们这样做是通过牺牲此类的扩展能力来获得更大的安全性等好处。但是如果需要的话,最后一堂课仍然可以扩展一堂课。

附带说明一下,与继承相比,我们应该更喜欢使用组合,final关键字实际上有助于实施这一原则。


6

当您上一类“最终”课时要小心。因为如果您要为最终类编写单元测试,则不能使用该最终类的子类来使用迈克尔·C·费瑟斯(Michael C. Feathers)的书《有效地使用传统代码》中描述的打破依赖的技术“子类和重写方法”。 。Feathers在这本书中说:“严重的是,我们很容易相信,密封的和最终的错误是一个错误的错误,它们不应该添加到编程语言中。但是真正的错误在于我们。当我们直接依赖于我们无法控制的库,我们只是在自找麻烦。”


6

final class 添加新方法时可以避免破坏公共API

假设您在Base课程的版本1中执行以下操作:

public class Base {}

客户执行以下操作:

class Derived extends Base {
    public int method() { return 1; }
}

然后,如果要在版本2中添加一个method方法到Base

class Base {
    public String method() { return null; }
}

它将破坏客户端代码。

如果我们final class Base改用了该客户端,则该客户端将无法继承,并且添加方法也不会破坏该API。


5

如果该类被标记final,则意味着该类的结构不能被任何外部修改。最明显的地方是当您执行传统的多态继承时,基本上class B extends A是行不通的。从根本上讲,这是一种保护代码的某些部分的方法

为了澄清起见,标记类final不会标记其字段,final因此不会保护对象属性,而是保护实际的类结构。


1
对象属性是什么意思?如果将类声明为final,是否意味着我可以修改该类的成员变量?因此,最终类的唯一目的是防止继承。
亚当·吕

5

解决最终课程问题:

有两种方法可以使班级进入决赛。第一种是在类声明中使用关键字final:

public final class SomeClass {
  //  . . . Class contents
}

使类最终化的第二种方法是将其所有构造函数声明为私有:

public class SomeClass {
  public final static SOME_INSTANCE = new SomeClass(5);
  private SomeClass(final int value) {
  }

如果将其标记为final,则可以省去麻烦,如果发现它确实是final,以演示一下此Test类。乍一看。

public class Test{
  private Test(Class beanClass, Class stopClass, int flags)
    throws Exception{
    //  . . . snip . . . 
  }
}

不幸的是,由于该类的唯一构造函数是私有的,因此无法扩展该类。对于Test类,没有理由该类应该是最终的。Test类是隐式最终类如何引起问题的一个很好的例子。

因此,当您通过将类的构造函数设为私有来隐式地使类为final时,应将其标记为final。


4

最终课程是无法扩展的课程。也可以将方法声明为final,以指示不能被子类覆盖。

如果编写API或库并希望避免扩展以更改基本行为,则防止类被子类化尤其有用。


4

将班级定为决赛的一个优点:-

字符串类保持最终状态,因此没有人可以覆盖其方法并更改功能。例如,没有人可以更改length()方法的功能。它将始终返回字符串的长度。

此类的开发人员不希望更改此类的功能,因此他将其保留为最终版本。



3

在java中final关键字用于以下场合。

  1. 最终变量
  2. 最终方法
  3. 期末课程

在Java中,final变量无法重新分配,final类不能扩展,并且final方法不能覆盖。


1

期末课程无法扩展。因此,如果您希望某个类以某种方式运行并且不希望有人重写这些方法(可能效率较低且更具恶意代码),则可以将整个类声明为您不想成为的最终方法或特定方法改变了。

因为声明一个类不会阻止实例化一个类,所以这并不意味着它会阻止该类具有对象的特征。只是您将必须按照在类中声明方法的方式来坚持使用这些方法。


1

认为FINAL是“行尾”-那家伙再也不能生产后代了。因此,当您以这种方式看到它时,将会遇到大量现实情况,这些情况要求您为班级标记“行尾”标记。这是域驱动设计-如果您的域要求给定的ENTITY(类)不能创建子类,则将其标记为FINAL。

我应该注意,没有什么可以阻止您继承“应该标记为最终的”类。但这通常被归类为“滥用继承”,并且这样做是因为大多数情况下,您希望从类的基类中继承某些函数。

最好的方法是查看领域,并由它决定您的设计决策。


1

如上所述,如果您不希望任何人更改该方法的功能,则可以将其声明为final。

示例:用于下载/上传的应用程序服务器文件路径,基于偏移量拆分字符串,此类方法可以声明为Final,以便这些方法功能不会被更改。并且,如果您想在单独的类中使用此类final方法,则将该类定义为Final类。所以Final类将具有所有final方法,因为Final方法可以在非final类中声明和定义。



1

假设您有一个Employee具有方法的类greetgreet调用该方法时,仅打印Hello everyone!。所以这是预期行为greet方法

public class Employee {

    void greet() {
        System.out.println("Hello everyone!");
    }
}

现在,让GrumpyEmployee子类Employee和重写greet方法如下所示。

public class GrumpyEmployee extends Employee {

    @Override
    void greet() {
        System.out.println("Get lost!");
    }
}

现在在下面的代码中查看该sayHello方法。它以Employeeinstance作为参数,并调用greet方法,希望它会说Hello everyone!但我们得到的是Get lost!。行为上的变化是由于Employee grumpyEmployee = new GrumpyEmployee();

public class TestFinal {
    static Employee grumpyEmployee = new GrumpyEmployee();

    public static void main(String[] args) {
        TestFinal testFinal = new TestFinal();
        testFinal.sayHello(grumpyEmployee);
    }

    private void sayHello(Employee employee) {
        employee.greet(); //Here you would expect a warm greeting, but what you get is "Get lost!"
    }
}

如果这种情况可以避免Employee上课,final。试想一下,如果未将StringClass声明为,那么厚脸皮的程序员可能会造成多大的混乱final


1

期末课程无法进一步扩展。如果不需要使类在Java中可继承,则可以使用此方法。

如果我们只需要使类中的特定方法不被覆盖,则可以将final关键字放在它们前面。在那里,该类仍然是可继承的。


-1

对象定向与继承无关,它与封装有关。继承破坏了封装。

在许多情况下,宣布班级决赛很有意义。代表“价值”(例如颜色或金额)的任何对象都是最终的。他们自己站着。

如果要编写库,请使类为最终类,除非您明确缩进来派生它们。否则,人们可能会派生您的类并覆盖方法,从而破坏您的假设/不变式。这也可能涉及安全性。

Joshua Bloch在“ Effective Java”中建议显式设计继承或禁止继承,他指出进行继承设计并不那么容易。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.