Java中的final关键字

1.介绍

虽然继承使我们能够重用现有代码,但有时我们确实需要出于各种原因对可扩展性设置限制;final关键字可以让我们做到这一点。

在本教程中,我们将了解final关键字对类、方法和变量的意义。

2.Final类

标记为final的类不能被继承。如果我们看一下Java核心库的代码,我们会在那里找到许多最终的类。一个例子是String类。如果我们可以扩展String类,重写它的任何方法,并用特定String子类的实例替换所有的String实例,对字符串对象的操作结果将变得不可预测。考虑到String类到处都在使用,这是不可接受的。这就是为什么String类被标记为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()方法是最终的。

此方法检查线程是否处于活动状态。由于许多原因,无法正确重写isAlive()方法。其中之一就是这个方法是本地的。本机代码是用另一种编程语言实现的,通常特定于运行本机代码的操作系统和硬件。

让我们创建一个Dog类并将其sound()方法设为final:

public class Dog {
    public final void sound() {
        // ...
    }
}

现在,让我们继承Dog类并尝试重写其sound()方法:

public class BlackDog extends Dog {
    public void sound() {
    }
}

结果就是编译报错:

  • overrides
    com.java.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的变量。一旦最后一个变量被初始化,它就不能被改变。

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引用变量,我们也不能重新分配它。但这并不意味着它所指的对象是不变的。我们可以自由地改变这个对象的属性。为了演示这一点,让我们声明最后一个引用变量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字段,这意味着我们可以初始化它们:

  • 如上例所示声明时
  • 在静态初始化程序块中

4.3 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

posted @ 2021-06-20 21:51  一锤子技术员  阅读(2)  评论(0编辑  收藏  举报  来源