1. 概述

继承让我们能复用现有代码,但有时出于各种原因,我们需要限制扩展性final关键字正是为此而生。

本文将深入探讨final关键字在类、方法和变量上的具体含义和应用场景。

2. Final类

final修饰的类无法被继承。查看Java核心库源码,你会发现大量final类,典型的就是String类。

想象一下:如果允许继承String类,重写其任意方法,并用自定义的String子类实例替换所有String实例,那么对String对象的操作结果将变得不可预测。考虑到String类无处不在,这是不可接受的。这就是String类被声明为final的原因。

任何继承final类的尝试都会导致编译错误。下面创建一个finalCat

public final class Cat {

    private int weight;

    // 标准getter和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的,你就无法通过继承来重写方法修复问题。换句话说,你失去了面向对象编程的核心优势之一:扩展性。

3. Final方法

final修饰的方法无法被重写。设计类时,如果认为某个方法不应被重写,可以将其声明为final。Java核心库中同样存在大量final方法。

有时我们不需要完全禁止类继承,只需阻止某些方法被重写。Thread类就是很好的例子:允许继承它创建自定义线程类,但其isAlive()方法是final的。

这个方法检查线程是否存活。由于多种原因(比如它是native方法),正确重写isAlive()几乎不可能。Native代码由其他语言实现,通常与操作系统和硬件紧密相关。

创建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修饰的变量无法被重新赋值。一旦初始化,就不能再修改。

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字段必须在构造函数完成前初始化

对于static 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,但它确实是解决特定设计问题的利器。

完整代码示例可在GitHub项目中找到。


原始标题:The "final" Keyword in Java | Baeldung