ChatGPT解决这个技术问题 Extra ChatGPT

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

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

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

如果Java是面向对象的,而你声明了一个类final,这不是停止了类具有对象特性的想法吗?


a
aioobe

首先,我推荐这篇文章:Java: When to create a final class

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

final 类只是一个无法扩展的类。

(这并不意味着对类对象的所有引用都会像声明为 final 一样。)

这个问题的答案中涵盖了将类声明为 final 是否有用:

在 Java 中禁止继承的充分理由?

如果Java是面向对象的,而你声明了一个final类,那岂不是停止了类具有对象特性的想法?

在某种意义上是的。

通过将一个类标记为最终类,您可以禁用该部分代码的强大而灵活的语言特性。但是,某些类不应该(并且在某些情况下不能)被设计为以一种好的方式考虑子类化。在这些情况下,将类标记为 final 是有意义的,即使它限制了 OOP。 (但请记住,最终类仍然可以扩展另一个非最终类。)


为了补充答案,Effective Java 的原则之一是支持组合而不是继承。 final 关键字的使用也有助于执行该原则。
“你这样做主要是出于效率和安全原因。”我经常听到这句话(甚至维基百科也这么说),但我仍然不明白这个论点背后的原因。有人愿意解释一下,比如说,一个非最终的 java.lang.String 最终会导致效率低下或不安全吗?
@MRA如果我创建一个接受字符串作为参数的方法,我假设它是不可变的,因为字符串是。因此,我知道我可以安全地调用 String 对象上的任何方法,而不会更改传递的 String。如果我要扩展 String,并更改子字符串的实现以更改实际的 String,那么您期望不可变的 String 对象不再是不可变的。
@Cruncher 为什么不创建子字符串 final 并让字符串打开以进行扩展?这将与肖恩·帕特里克·弗洛伊德 (Sean Patrick Floyd) 的帖子中提到的“开闭原则”保持一致。
@Sortofabeginner 一旦你说你希望所有 String 方法和字段都是最终的,这样你就可以创建一些具有附加功能的类......此时你不妨创建一个具有字符串和创建对该字符串进行操作的方法。
O
Ojonugwa Jude Ochalifu

在 Java 中,不能更改带有 final 修饰符的项目!

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

最终类不能被任何其他类扩展

最终变量不能重新分配另一个值

最终方法不能被覆盖


实际的问题是为什么,而不是什么。
“在 Java 中,不能更改带有 final 修饰符的项目!”这句话过于明确,实际上并不完全正确。正如 Grady Booch 所说,“一个对象具有状态、行为和身份”。虽然一旦对象的引用被标记为 final,我们就无法更改对象的身份,但我们确实有机会通过为其非 final 字段分配新值来更改其状态(前提是,当然,它有它们。)任何计划获得Oracle Java认证(例如1Z0-808等)的人都应该记住这一点,因为考试中可能会有这方面的问题......
n
nbro

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

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


内联通常仅由即时编译器在运行时完成。它也可以在没有 final 的情况下工作,但是 JIT 编译器需要做更多的工作来确定没有扩展类(或者这些扩展类不会触及这个方法)。
可以在此处找到关于内联和优化问题的好文章:lemire.me/blog/archives/2014/12/17/…
> 不能被某人覆盖 由谁?
我认为安全一词在学术意义上几乎是令人困惑的,意思是安全。安全意味着当您使用一个类时,您知道另一个类没有扩展它并且依赖于基类的内部实现。我认为我们都遇到过类以各种讨厌的方式扩展的情况,如果不花掉你一天的大部分时间,就不可能对代码进行推理。
n
nbro

最好的例子是

公共最终类字符串

这是一个不可变的类,不能扩展。当然,不仅仅是让类 final 成为不可变的。


呵呵,有时它会保护 Rube Goldergian 开发人员免受自己的伤害。
b
biziclop

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

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

在实践中,如果您希望您的对象是不可变的或者如果您正在编写 API,您希望使用 final 来向 API 的用户表明该类不用于扩展。


S
Sean Patrick Floyd

相关阅读:The Open-Closed Principle Bob Martin。

关键报价:

软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭。

final 关键字是在 Java 中强制执行此操作的方法,无论是用于方法还是用于类。


@Sean:声明它 final 不会使类关闭以进行扩展而不是打开吗?还是我太从字面上理解了?
@Goran 全球申请决赛,是的。关键是在你不想修改的地方有选择地应用 final (当然要为扩展提供好的钩子)
在OCP中,“修改”是指修改源代码,“扩展”是指实现继承。因此,如果您希望实现代码关闭以进行修改但打开以通过继承进行扩展,则在类/方法声明中使用 final 是没有意义的。
@Rogerio 我从 Spring Framework Reference (MVC) 中借用了参考(和解释)。恕我直言,这比原始版本更有意义。
扩展已死。无用。被淘汰。毁坏。我不关心OCP。永远没有借口扩展课程。
C
Community

关键字 final 本身意味着某些东西是最终的,不应该以任何方式修改。如果一个类如果标记为 final,则它不能被扩展或子分类。但问题是我们为什么要标记一个类 final? IMO有多种原因:

标准化:某些类执行标准功能并且它们不打算被修改,例如执行与字符串操作或数学函数等相关的各种功能的类。安全原因:有时我们编写执行各种身份验证和密码相关功能的类,我们不想要它们被其他人改变。

我听说标记类 final 提高了效率,但坦率地说,我找不到这个论点有多大分量。

如果Java是面向对象的,而你声明了一个final类,那岂不是停止了类具有对象特性的想法?

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

在旁注中,我们应该 prefer composition over inheritancefinal 关键字实际上有助于执行这一原则。


C
Ciro Santilli Путлер Капут 六四事

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。


J
JuanZe

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

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


K
Kavinda Pushpitha

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

最终变量 最终方法 最终类

在java中,最终变量不能重新分配,最终类不能扩展,最终方法不能覆盖。


B
Ben Wu

将课程设为“最终”时要小心。因为如果你想为最终类编写单元测试,你不能子类化这个最终类以使用 Michael C. Feathers 的书“有效地使用遗留代码”中描述的依赖破坏技术“子类和覆盖方法” . Feathers 在这本书中说:“说真的,很容易相信sealed 和final 是一个错误的错误,它们不应该被添加到编程语言中。但真正的错误在于我们。当我们直接依赖于我们无法控制的图书馆,我们只是自找麻烦。”


这是遗留代码的技术,而不是您正在编写的新类的技术,根据定义,它不是遗留代码。
E
Esko

如果该类被标记为final,则表示该类的结构不能被任何外部修改。这是最明显的地方,当您进行传统的多态继承时,基本上 class B extends A 将不起作用。这基本上是一种保护代码的某些部分的方法(在一定程度上)

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


对象属性是什么意思?这是否意味着如果该类被声明为final,我可以修改该类的成员变量?所以 final 类的唯一目的是防止继承。
m
mel3kings

解决最后一类问题:

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

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

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

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 类如何导致问题的一个很好的例子。

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


R
Rahul

将课程保持为最终课程的一个优点:-

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

这个类的开发者不希望任何人改变这个类的功能,所以他把它作为最终的。


R
Raedwald

其他答案集中在 final class 告诉编译器的内容:不允许另一个类声明它 extends 这个类,以及为什么这是可取的。

但编译器并不是短语 final class 的唯一读者。每个阅读源代码的程序员也会阅读它。它可以帮助快速理解程序。

一般来说,如果程序员看到 Thing thing = that.someMethod(...); 并且想要了解通过 thing 对象引用访问的对象的后续行为,则程序员必须考虑 Thing 类层次结构:可能有许多类型,分散在许多包。但是,如果程序员知道或读过 final class Thing,他们会立即知道他们不需要搜索和研究这么多 Java 文件,因为没有派生类:他们只需要研究 Thing.java,也许,它是基类。


J
James

是的,有时出于安全或速度的原因,您可能想要这样做。它也在 C++ 中完成。它可能不适用于程序,但更适用于框架。 http://www.glenmccl.com/perfj_025.htm


K
Kingz

将 FINAL 视为“生产线的尽头”——那家伙不能再生育后代了。所以当你这样看时,你会遇到很多现实世界的场景,需要你在课堂上标记一个“行尾”标记。它是领域驱动设计——如果您的领域要求给定的实体(类)不能创建子类,则将其标记为最终。

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

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


B
BenMorel

如上所述,如果您希望没有人可以更改该方法的功能,那么您可以将其声明为 final。

示例:下载/上传的应用服务器文件路径,根据偏移量分割字符串,这样的方法你可以将它声明为Final,这样这些方法功能就不会被改变。如果你想在一个单独的类中使用这样的 final 方法,那么将该类定义为 Final 类。所以最终类将具有所有最终方法,其中可以在非最终类中声明和定义最终方法。


A
Andy

假设您有一个具有方法 greetEmployee 类。当调用 greet 方法时,它只打印 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 方法。它接受 Employee 实例作为参数并调用 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,则可以避免这种情况。想象一下,如果 String 类未声明为 final,那么厚颜无耻的程序员可能会造成多大的混乱。


G
G. Raven

最终类不能进一步扩展。如果我们不需要在java中使类可继承,我们可以使用这种方法。

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


s
skhaapioloir

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

由于声明一个类不会阻止一个类被实例化,这并不意味着它会阻止该类具有对象的特性。只是您必须按照它们在类中声明的方式坚持方法。


j
jaamit

Android Looper 类就是一个很好的实际例子。 http://developer.android.com/reference/android/os/Looper.html

Looper 类提供了某些不打算被任何其他类覆盖的功能。因此,这里没有子类。


e
ericn

我只知道一个实际用例:生成的类

在生成类的用例中,我知道一个:依赖注入,例如 https://github.com/google/dagger


W
Wilhem Meignan

面向对象不是关于继承,而是关于封装。继承打破了封装。

在很多情况下,声明一个类 final 是非常有意义的。任何代表“价值”的对象,如颜色或金额,都可能是最终的。他们独自站立。

如果您正在编写库,请使您的类成为最终类,除非您明确缩进它们以进行派生。否则,人们可能会派生您的类并覆盖方法,从而破坏您的假设/不变量。这也可能具有安全隐患。

Joshua Bloch 在“Effective Java”中建议明确地为继承设计或禁止它,他指出为继承而设计并不是那么容易。