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;
// 标准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项目中找到。