Java中final关键字使用

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 关键字,但它可能是一个很好的设计解决方案。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值