Java中final关键字使用指南-Java快速入门教程
1. 概述
虽然继承使我们能够重用现有代码,但有时出于各种原因,我们确实需要对可扩展性设置限制;final这个关键字允许我们做到这一点。
在本教程中,我们将了解 final 关键字对类、方法和变量的含义。
2. Final类
标记为final的类无法扩展。如果我们看一下Java核心库的代码,我们会在那里找到许多final类。一个例子是String类。如果我们可以扩展 String 类,重写它的任何方法,并将所有 String 实例替换为特定 String 子类的实例。当我们按以上设想来实现的话,对 String 对象的操作结果将变得不可预测。鉴于 String 类在任何地方都使用,这是不可接受的。这就是 String 类被标记为 final 的原因。
任何从final类继承的尝试都将导致编译器错误。为了演示这一点,让我们创建final类 Cat:
public final class Cat {
private int weight;
// standard getter and setter
}
让我们尝试扩展它:
public class BlackCat extends Cat {
}
我们将看到编译器错误:
The type BlackCat cannot subclass the final class Cat
请注意,类声明中的 final 关键字并不意味着此类的对象是不可变的。我们可以自由更改 Cat 对象的字段:
Cat cat = new Cat();
cat.setWeight(1);
assertEquals(1, cat.getWeight());
我们只是无法扩展它。
如果我们严格遵循良好设计的规则,我们应该仔细创建和记录一个类,或者出于安全原因将其声明为final类。但是,在创建final类时,我们应该谨慎行事。
请注意,使类final化意味着没有其他程序员可以改进它。想象一下,我们正在使用一个类,但没有它的源代码,并且其中部分方法存在问题。
如果类是 final,则无法扩展它以重写方法并解决问题。换句话说,我们失去了可扩展性,这是面向对象编程的好处之一。
3. final方法
标记为 final 的方法不能被覆盖。当我们设计一个类并觉得一个方法不应该被重写时,我们可以使这个方法成为final的。我们还可以在 Java 核心库中找到许多final方法。
有时我们不需要完全禁止类扩展,而只需要防止覆盖某些方法。一个很好的例子是 Thread 类。扩展它并因此创建自定义线程类是合法的。但它的isAlive()方法是final的。
此方法检查线程是否处于活动状态。由于许多原因,不可能正确覆盖 isAlive() 方法。其中之一是此方法是本地的。本地代码以另一种编程语言实现,通常特定于运行它的操作系统和硬件。
让我们创建一个 Dog 类并使其 sound() 方法final化:
public class Dog {
public final void sound() {
// ...
}
}
现在让我们扩展 Dog 类并尝试覆盖其 sound() 方法:
public class BlackDog extends Dog {
public void sound() {
}
}
我们将看到编译器错误:
- overrides
com.baeldung.finalkeyword.Dog.sound
- Cannot override the final method from Dog
sound() method is final and can’t be overridden
如果我们类的某些方法被其他方法调用,我们应该考虑将被调用的方法定为final方法。否则,覆盖它们可能会影响调用方的工作并导致令人惊讶的结果。
如果我们的构造函数调用其他方法,出于上述原因,我们通常应该将这些方法声明为 final。
将类的所有方法定为final方法和将类本身标记为final方法有什么区别?在第一种情况下,我们可以扩展类并向其添加新方法。
在第二种情况下,我们不能这样做。
4. final变量
标记为 final 的变量无法重新分配。一旦初始化了final变量,就无法更改它。
4.1. final原始变量
让我们声明一个原始的final变量 i,然后给它赋值 1。
让我们尝试为其分配一个值 2:
public void whenFinalVariableAssign_thenOnlyOnce() {
final int i = 1;
//...
i=2;
}
编译器说:
The final local variable i may already have been assigned
4.2. final引用变量
如果我们有一个final的引用变量,我们也不能重新分配它。但这并不意味着它引用的对象是不可变的。我们可以自由更改此对象的属性。
为了证明这一点,让我们声明final的引用变量 cat 并初始化它:
final Cat cat = new Cat();
如果我们尝试重新分配它,我们将看到一个编译器错误:
The final local variable cat cannot be assigned. It must be blank and not using a compound assignment
但是我们可以更改 Cat 实例的属性:
cat.setWeight(5);
assertEquals(5, cat.getWeight());
4.3. final字段
final字段可以是常量字段,也可以是一次性写入字段。为了区分它们,我们应该问一个问题——如果我们要序列化对象,我们会包括这个字段吗?如果不是,则它不是对象的一部分,而是一个常量。
请注意,根据命名约定,类常量应为大写,组件由下划线 (“_”) 字符分隔:
static final int MAX_WIDTH = 999;
请注意,必须在构造函数完成之前初始化任何final字段。
对于静态final字段,这意味着我们可以初始化它们:
- 如上例所示的声明
- 在静态初始值设定项块中
例如final字段,这意味着我们可以初始化它们:
- 声明时
- 在实例初始值设定项块中
- 在构造函数中
否则,编译器会给我们一个错误。
4.4. final论点
final一个关键字放在方法参数之前也是合法的。final一个参数不能在方法中更改:
public void methodWithFinalArguments(final int x) {
x=1;
}
上述赋值会导致编译器错误:
The final local variable x cannot be assigned. It must be blank and not using a compound assignment
5. 结论
在本文中,我们了解了 final 关键字对类、方法和变量的含义。虽然我们可能不会在内部代码中经常使用 final 关键字,但它可能是一个很好的设计解决方案。