杰瑞科技汇

Java final关键字的核心作用有哪些?

final 是 Java 中的一个关键字,它意为“最终的”、“不可变的”,当它被用来修饰不同类型的元素时,其作用也不同。final 关键字可以用来修饰类、方法和变量

Java final关键字的核心作用有哪些?-图1
(图片来源网络,侵删)

下面我们分别从这三个方面来阐述其作用。


修饰变量

final 修饰变量时,意味着该变量一旦被初始化赋值后,其值就不能再被修改,它本质上是一个常量

根据变量类型的不同,其具体规则又分为三种情况:

a) 修饰基本数据类型 (如 int, double, char 等)

final 修饰的是基本数据类型的变量,那么变量的值不能被改变

Java final关键字的核心作用有哪些?-图2
(图片来源网络,侵删)
public class FinalExample {
    final int FINAL_NUMBER = 10;
    public void modifyValue() {
        // FINAL_NUMBER = 20; // 编译错误!无法为最终变量FINAL_NUMBER分配值
        System.out.println("FINAL_NUMBER is: " + FINAL_NUMBER);
    }
}

b) 修饰引用数据类型 (如 String, ArrayList, 自定义类等)

final 修饰的是引用数据类型的变量,它意味着变量的引用(即它指向的内存地址)不能被改变,但是引用对象内部的状态(属性)是可以被修改的

这是一个非常重要的区别,很多初学者会在这里混淆。

import java.util.ArrayList;
import java.util.List;
public class FinalReferenceExample {
    final List<String> names = new ArrayList<>();
    public void modifyReference() {
        // 1. 这行代码会编译错误,因为names的引用地址不能改变
        // names = new ArrayList<>(); 
        // 2. 这是允许的,因为修改的是names所指向的ArrayList对象内部的内容
        names.add("Alice");
        names.add("Bob");
        System.out.println("Names list: " + names); // 输出: [Alice, Bob]
    }
}

c) 修饰成员变量(类的字段)

对于类的成员变量,final 变量必须被初始化,初始化的方式有两种:

  1. 声明时初始化(显式初始化):像上面的例子一样,在声明变量时就直接赋值。
  2. 在构造函数中初始化:如果声明时没有赋值,那么必须在所有的构造函数中对其进行初始化,Java 确保对象在创建完成前,final 成员变量一定被赋值。
public class FinalMemberExample {
    final int id; // 声明时未初始化
    // 必须在构造函数中初始化 final 成员变量
    public FinalMemberExample(int id) {
        this.id = id;
    }
    // 如果有多个构造函数,每个都必须初始化 id
    public FinalMemberExample() {
        this(0); // 调用另一个构造函数来初始化
    }
    // public FinalMemberExample(String name) {
    //     // 编译错误!构造函数中没有初始化 final 变量 id
    // }
}

修饰方法

final 修饰方法时,意味着该方法不能被其子类重写(Override)

Java final关键字的核心作用有哪些?-图3
(图片来源网络,侵删)

这主要有两个目的:

  1. 锁定方法:防止任何子类修改其行为,保证了方法实现的稳定性和一致性。
  2. 性能优化:在 Java 早期版本(JDK 1.5 之前),final 方法可以被编译器内联(Inlining),即直接将方法调用处的代码替换为方法体本身,从而消除方法调用的开销,虽然现代 JVM 的即时编译器已经非常智能,即使没有 final 关键字也能进行大量的内联优化,但 final 仍然是一个明确的信号。
class Animal {
    // final 方法,子类不能重写
    public final void eat() {
        System.out.println("Animal is eating.");
    }
}
class Dog extends Animal {
    // @Override
    // public void eat() { // 编译错误!无法重写最终方法
    //     System.out.println("Dog is eating.");
    // }
    public void bark() {
        System.out.println("Dog is barking.");
    }
}
public class Main {
    public static void main(String[] args) {
        Animal myDog = new Dog();
        myDog.eat(); // 输出: Animal is eating. 调用的是父类的 final 方法
    }
}

修饰类

final 修饰类时,意味着该类不能被继承

它创建了一个“终极”类,任何试图继承它的行为都会导致编译错误。

这在以下场景中非常有用:

  1. 安全性:防止不可信的代码扩展或修改类的核心行为。String 类就是 final 的,这保证了字符串的不可变性,也防止了恶意代码通过继承 String 类来破坏其安全性。
  2. 设计意图:当一个类的设计意图就是作为工具类(只包含静态方法和静态字段)或一个不可变的实体时,将其声明为 final 可以清晰地表明其不应被继承的设计意图。
// final 类,不能被继承
final class ImmutableClass {
    private final int value;
    public ImmutableClass(int value) {
        this.value = value;
    }
    public int getValue() {
        return value;
    }
}
// class MyImmutableClass extends ImmutableClass { // 编译错误!无法继承最终类
// }

总结表格

修饰对象 作用 示例 主要目的
变量 值不可变 final int MAX_AGE = 100;
final List<String> list = new ArrayList<>();
定义常量
保证线程安全(不可变对象天生线程安全)
引用地址不可变
方法 不能被子类重写 public final void doSomething() {...} 锁定方法实现,防止被修改
(历史原因)优化性能
不能被继承 public final class String {...} 保证类的安全性和完整性
明确设计意图

一个重要的补充:finalprivate 的关系

你可能注意到,private 方法也隐式地是 final 的,因为 private 方法不能在子类中被访问,自然也就不能被重写。

class Parent {
    private void privateMethod() {
        System.out.println("Parent's private method");
    }
}
class Child extends Parent {
    // 这个方法并不是重写,而是 Child 类自己定义的一个新方法
    // 因为它无法访问或重写父类的 privateMethod
    public void privateMethod() {
        System.out.println("Child's new method");
    }
}

如果你将一个 private 方法显式地声明为 final,编译器会给出警告,因为这通常是多余的代码。

最佳实践

  • 优先使用 final:如果你发现一个变量、方法或类不打算被修改或继承,就大胆地使用 final,这能让代码意图更清晰,也能帮助编译器进行一些优化。
  • 常量命名规范:对于 static final 修饰的常量,按照 Java 约定,其命名应该全部使用大写字母,单词之间用下划线 _ 分隔,MAX_VALUEPI
  • 谨慎使用 final:将类设为 final 会限制其扩展性,除非有非常充分的理由(如 String 类),否则应谨慎使用,更好的做法是通过良好的设计来避免继承被滥用。
分享:
扫描分享到社交APP
上一篇
下一篇