首先,我推荐这篇文章:Java: When to create a final class
如果他们这样做,他们什么时候使用它,以便我可以更好地理解它并知道何时使用它。
final
类只是一个无法扩展的类。
(这并不意味着对类对象的所有引用都会像声明为 final
一样。)
这个问题的答案中涵盖了将类声明为 final 是否有用:
在 Java 中禁止继承的充分理由?
如果Java是面向对象的,而你声明了一个final类,那岂不是停止了类具有对象特性的想法?
在某种意义上是的。
通过将一个类标记为最终类,您可以禁用该部分代码的强大而灵活的语言特性。但是,某些类不应该(并且在某些情况下不能)被设计为以一种好的方式考虑子类化。在这些情况下,将类标记为 final 是有意义的,即使它限制了 OOP。 (但请记住,最终类仍然可以扩展另一个非最终类。)
在 Java 中,不能更改带有 final
修饰符的项目!
这包括最终类、最终变量和最终方法:
最终类不能被任何其他类扩展
最终变量不能重新分配另一个值
最终方法不能被覆盖
final
修饰符的项目!”这句话过于明确,实际上并不完全正确。正如 Grady Booch 所说,“一个对象具有状态、行为和身份”。虽然一旦对象的引用被标记为 final,我们就无法更改对象的身份,但我们确实有机会通过为其非 final
字段分配新值来更改其状态(前提是,当然,它有它们。)任何计划获得Oracle Java认证(例如1Z0-808等)的人都应该记住这一点,因为考试中可能会有这方面的问题......
出于安全原因,当您想要阻止类的继承时,final 很重要的一种情况。这使您可以确保您正在运行的代码不会被某人覆盖。
另一种情况是用于优化:我似乎记得 Java 编译器内联了一些来自 final 类的函数调用。因此,如果您调用 a.x()
并且 a 被声明为 final
,我们在编译时就知道代码将是什么并且可以内联到调用函数中。我不知道这是否真的完成了,但最终有可能。
最好的例子是
公共最终类字符串
这是一个不可变的类,不能扩展。当然,不仅仅是让类 final 成为不可变的。
如果您将类层次结构想象成一棵树(就像在 Java 中一样),那么抽象类只能是分支,而最终类只能是叶子。不属于这两个类别的类既可以是分支也可以是叶子。
这里没有违反 OO 原则,final 只是提供了一个很好的对称性。
在实践中,如果您希望您的对象是不可变的或者如果您正在编写 API,您希望使用 final 来向 API 的用户表明该类不用于扩展。
相关阅读:The Open-Closed Principle Bob Martin。
关键报价:
软件实体(类、模块、函数等)应该对扩展开放,但对修改关闭。
final
关键字是在 Java 中强制执行此操作的方法,无论是用于方法还是用于类。
final
不会使类关闭以进行扩展而不是打开吗?还是我太从字面上理解了?
final
是没有意义的。
关键字 final
本身意味着某些东西是最终的,不应该以任何方式修改。如果一个类如果标记为 final
,则它不能被扩展或子分类。但问题是我们为什么要标记一个类 final
? IMO有多种原因:
标准化:某些类执行标准功能并且它们不打算被修改,例如执行与字符串操作或数学函数等相关的各种功能的类。安全原因:有时我们编写执行各种身份验证和密码相关功能的类,我们不想要它们被其他人改变。
我听说标记类 final
提高了效率,但坦率地说,我找不到这个论点有多大分量。
如果Java是面向对象的,而你声明了一个final类,那岂不是停止了类具有对象特性的想法?
也许是的,但有时这是预期的目的。有时我们这样做是为了通过牺牲这个类的扩展能力来获得更大的安全性等好处。但是如果需要,最终类仍然可以扩展一个类。
在旁注中,我们应该 prefer composition over inheritance 和 final
关键字实际上有助于执行这一原则。
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。
final 类是不能扩展的类。也可以将方法声明为 final 以指示不能被子类覆盖。
如果您编写 API 或库并希望避免被扩展以更改基本行为,则防止类被子类化可能特别有用。
在 java final 关键字用于以下场合。
最终变量 最终方法 最终类
在java中,最终变量不能重新分配,最终类不能扩展,最终方法不能覆盖。
将课程设为“最终”时要小心。因为如果你想为最终类编写单元测试,你不能子类化这个最终类以使用 Michael C. Feathers 的书“有效地使用遗留代码”中描述的依赖破坏技术“子类和覆盖方法” . Feathers 在这本书中说:“说真的,很容易相信sealed 和final 是一个错误的错误,它们不应该被添加到编程语言中。但真正的错误在于我们。当我们直接依赖于我们无法控制的图书馆,我们只是自找麻烦。”
如果该类被标记为final
,则表示该类的结构不能被任何外部修改。这是最明显的地方,当您进行传统的多态继承时,基本上 class B extends A
将不起作用。这基本上是一种保护代码的某些部分的方法(在一定程度上)。
澄清一下,标记类 final
不会将其字段标记为 final
,因此不会保护对象属性,而是保护实际的类结构。
解决最后一类问题:
有两种方法可以使班级决赛。第一种是在类声明中使用关键字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 类如何导致问题的一个很好的例子。
因此,当您通过将构造函数设为私有来隐式地使类最终成为最终时,您应该将其标记为最终。
将课程保持为最终课程的一个优点:-
String 类保持最终状态,因此没有人可以覆盖其方法并更改功能。例如,没有人可以更改 length() 方法的功能。它总是返回一个字符串的长度。
这个类的开发者不希望任何人改变这个类的功能,所以他把它作为最终的。
其他答案集中在 final class
告诉编译器的内容:不允许另一个类声明它 extends
这个类,以及为什么这是可取的。
但编译器并不是短语 final class
的唯一读者。每个阅读源代码的程序员也会阅读它。它可以帮助快速理解程序。
一般来说,如果程序员看到 Thing thing = that.someMethod(...);
并且想要了解通过 thing
对象引用访问的对象的后续行为,则程序员必须考虑 Thing
类层次结构:可能有许多类型,分散在许多包。但是,如果程序员知道或读过 final class Thing
,他们会立即知道他们不需要搜索和研究这么多 Java 文件,因为没有派生类:他们只需要研究 Thing.java
,也许,它是基类。
将 FINAL 视为“生产线的尽头”——那家伙不能再生育后代了。所以当你这样看时,你会遇到很多现实世界的场景,需要你在课堂上标记一个“行尾”标记。它是领域驱动设计——如果您的领域要求给定的实体(类)不能创建子类,则将其标记为最终。
我应该注意,没有什么可以阻止您继承“应该标记为最终”类。但这通常被归类为“滥用继承”,这样做是因为您通常希望从类中的基类继承某些函数。
最好的方法是查看领域并让它决定您的设计决策。
如上所述,如果您希望没有人可以更改该方法的功能,那么您可以将其声明为 final。
示例:下载/上传的应用服务器文件路径,根据偏移量分割字符串,这样的方法你可以将它声明为Final,这样这些方法功能就不会被改变。如果你想在一个单独的类中使用这样的 final 方法,那么将该类定义为 Final 类。所以最终类将具有所有最终方法,其中可以在非最终类中声明和定义最终方法。
假设您有一个具有方法 greet
的 Employee
类。当调用 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
,那么厚颜无耻的程序员可能会造成多大的混乱。
最终类不能进一步扩展。如果我们不需要在java中使类可继承,我们可以使用这种方法。
如果我们只需要使类中的特定方法不被覆盖,我们只需将 final 关键字放在它们前面。那里的类仍然是可继承的。
最终类不能扩展。因此,如果您希望一个类以某种方式表现并且不有人重写这些方法(可能效率较低且恶意代码较多),您可以将整个类声明为您不想成为的最终或特定方法改变了。
由于声明一个类不会阻止一个类被实例化,这并不意味着它会阻止该类具有对象的特性。只是您必须按照它们在类中声明的方式坚持方法。
Android Looper 类就是一个很好的实际例子。 http://developer.android.com/reference/android/os/Looper.html
Looper 类提供了某些不打算被任何其他类覆盖的功能。因此,这里没有子类。
面向对象不是关于继承,而是关于封装。继承打破了封装。
在很多情况下,声明一个类 final 是非常有意义的。任何代表“价值”的对象,如颜色或金额,都可能是最终的。他们独自站立。
如果您正在编写库,请使您的类成为最终类,除非您明确缩进它们以进行派生。否则,人们可能会派生您的类并覆盖方法,从而破坏您的假设/不变量。这也可能具有安全隐患。
Joshua Bloch 在“Effective Java”中建议明确地为继承设计或禁止它,他指出为继承而设计并不是那么容易。
final
并让字符串打开以进行扩展?这将与肖恩·帕特里克·弗洛伊德 (Sean Patrick Floyd) 的帖子中提到的“开闭原则”保持一致。