JAVA面试|final关键字的用法

final核心思想就是“不可变”或者“最终版”。把它加在不同的地方,作用略有不同,但都围绕着这个核心。

可以把final想象成一种“封印”或者“刻在石头上”的标记。

一、final修饰变量

通俗解释:给变量贴上一张“只读”标签。一旦你给它赋了值,它就再也不能改变了,就像刻在石头上的字一样。它变成了一个常量。

详细说明:

基本类型变量 (int, double, char, boolean 等): 值不能再改变。

final int MAX_SIZE = 100;

MAX_SIZE = 200; // 编译错误!不能再改了,MAX_SIZE 已经是 100了。

引用类型变量 (对象、数组): 引用本身不能再指向另一个对象或数组。但是!对象内部的状态(成员变量的值)或者数组里的元素 还是可以修改的!这点最容易混淆。

final List<String> myList = new ArrayList<>();

myList.add(“Hello”); // 允许!修改的是对象内部的内容

myList.add(“World”); // 允许!

myList = new LinkedList<>(); // 编译错误!不能再让 myList 指向另一个 List 对象了。

// 石头上的字(myList 指向的地址)不能变,但石头里面的东西(ArrayList 里的元素)可以动。

成员变量 (Instance Variable): 必须在声明时、或者在每个构造方法中、或者在初始化块中显式地赋值一次且仅一次。之后不能再改。

静态变量 (Static Variable / Class Variable): 必须在声明时、或者在静态初始化块中显式地赋值一次且仅一次。之后不能再改。

局部变量 (Local Variable): 可以在声明时赋值,也可以只声明不赋值,但在第一次使用之前必须赋值一次且仅一次。之后不能再改。

final int localVar;

if (someCondition) {

localVar = 10; // 第一次赋值

} else {

localVar = 20; // 第一次赋值 (走这个分支)

}

// localVar = 30; // 编译错误!已经赋值过了,不能再改。

为什么用?

定义常量:列如 Math.PI。

提高可读性和安全性:明确告知别人和编译器这个值不应该改变,防止意外修改。

配合匿名内部类:匿名内部类访问外部方法的局部变量时,该局部变量必须是final或等效final(Java 8+)。这是由于局部变量在方法执行完就没了,而内部类对象可能还存在,为了保证内部类访问到的值是一致的,Java复制了一份值过去,要求原值不变才能保证复制的值有效。

二、final修饰方法

通俗解释:给方法贴上“禁止后代重写”的封条。这个方法就是最终版本,子类不能用自己的实现来覆盖它。

详细说明:

被final修饰的方法不能被子类覆盖 (Override)。

父类的final方法,子类只能老老实实继承使用,不能改变实则现。

class Parent {

public final void doSomethingImportant() {

// 关键操作,必须这样执行

System.out.println(“Parent's important work”);

}

}

class Child extends Parent {

// @Override // 加上这个注解会更明显看出错误

public void doSomethingImportant() { // 编译错误!不能覆盖父类的 final 方法

System.out.println(“Trying to change important work…”);

}

}

为什么用?

防止关键行为被篡改:父类设计者认为这个方法的行为至关重大且固定,不允许子类以任何方式改变实则现逻辑,确保核心功能的一致性。例如,Object类中的 getClass() 方法是final的,由于获取对象类型这个行为必须是标准且不可变的。

性能优化 (历史缘由/争议):早期JVM可能对final方法有轻微内联优化(编译器直接替换方法调用为方法体代码)。现代JVM超级智能,即使没有final,也能很好地判断哪些方法可以安全内联。所以性能不再是主要思考因素。 设计意图(禁止重写)才是重点。

安全/设计约束:确保方法的行为符合父类设计者的预期。

三、final修饰类

通俗解释:给整个类贴上“禁止开枝散叶”的封印。这个类就是家族的最后一代,不能有子类了。

详细说明:

被final修饰的类不能被任何其他类继承。

它自己不能是父类。

final class ImmutablePoint {

private final int x;

private final int y;

public ImmutablePoint(int x, int y) {

this.x = x;

this.y = y;

}

// 只有 getter, 没有 setter, 由于字段也是 final

}

class SpecialPoint extends ImmutablePoint { // 编译错误!不能继承 final 类

// …

}

为什么用?

设计不可变类 (Immutable Class):这是最重大的缘由!如果一个类本身不可被继承(final),并且所有字段都是private final,且不提供修改字段的方法(或者通过防御性拷贝返回),那么这个类创建的对象状态就是真正不可变的。例如Java标准库中的String, Integer, Double等包装类都是final。不可变对象在并发编程(线程安全)、缓存、作为Map键等方面有巨大优势。

安全:防止恶意子类化破坏类的行为(列如覆盖方法改变逻辑)。例如 String 如果是可继承的,就可能有人创建伪装的String对象破坏安全性。

设计意图清晰:明确告知使用者,这个类就是最终形态,没有扩展点,不要尝试继承它来做修改。

四、final修饰方法参数

通俗解释:给传入方法内部的参数值贴上一张“只读”标签(针对基本类型)或给传入的“遥控器”贴上一张“不能换遥控对象”的标签(针对引用类型)。

详细说明:

在方法内部,不能再给这个参数变量本身赋值。

对于基本类型:不能改变它的值。

对于引用类型:不能让它指向另一个对象(和final局部变量一样)。但对象内部的状态依旧可以修改(除非对象本身是不可变的)。

public void processValue(final int input) {

// input = 10; // 编译错误!不能修改 final 参数的值

System.out.println(input);

}

public void modifyList(final List<String> list) {

// list = new ArrayList<>(); // 编译错误!不能让它指向新列表

list.add(“Modified”); // 允许!修改的是列表里面的内容

}

为什么用?

明确意图:告知方法内部的代码(以及读代码的人),这个参数不应该被重新赋值(指向新的引用)。有助于防止不小心修改了参数引用。

配合匿名内部类 (历史缘由):在Java 8之前,如果要在匿名内部类中使用外部方法的参数,该参数必须声明为final。缘由和局部变量一样(需要复制值保证一致性)。Java 8引入了“等效 final”的概念(变量虽然没有显式声明final,但在初始化后从未改变过),这种情况下可以不用显式写final,但效果一样。显式写上final依然清晰表明意图。

五、总结

final就是Java给你的一把“锁”或一支“刻刀”,让你在代码中明确标记出那些不应该再被改变的东西(值、引用、方法行为、类的结构)。用好它能提高代码的清晰度、安全性、可维护性,尤其是在设计不可变对象和框架时。

© 版权声明
THE END
如果内容对您有所帮助,就支持一下吧!
点赞0 分享
曹小捷捷捷_Redmancy17的头像 - 宋马
评论 抢沙发

请登录后发表评论

    暂无评论内容