在 Java 开发中,final是一个高频使用但容易被浅尝辄止的关键字。它的核心语义是 “不可变、最终的”,但在修饰类、方法、变量时的表现差异,以及在 JDK 源码中的巧妙应用,往往是面试和实际开发的关键考点。本文将从基础用法到源码实践,全方位拆解final关键字,帮你彻底掌握它的本质。

一、final 关键字核心定义

final(最终的)是 Java 的非访问修饰符,用于限制目标的可修改性,核心作用是约束变化、保证不可变性,可修饰:

  • 变量(局部变量、成员变量、静态变量)
  • 方法

设计初衷:

  1. 保证数据安全性(如常量不被篡改);
  2. 提升代码可读性(明确标识 “不可变” 语义);
  3. 辅助 JVM 优化(编译期确定常量值、避免方法重写带来的动态分派开销)。

二、final 的基础用法详解

2.1 修饰变量:不可变的 “常量”

final修饰变量时,变量成为 “只读常量”,核心规则是:只能赋值一次,赋值后不可修改

2.1.1 不同类型变量的赋值规则
变量类型 赋值要求
局部变量 声明后必须赋值(直接赋值 / 先声明后赋值二选一,仅能赋值一次)
成员变量(实例) 必须在「声明时、构造方法、初始化块」三者中选其一赋值
静态变量 必须在「声明时、静态初始化块」中赋值(不能在构造方法赋值,因为静态变量初始化更早)
2.1.2 基础示例

public class FinalVariableDemo {
    // 1. 静态常量(推荐命名:全大写+下划线分隔)
    public static final double PI = 3.1415926;
    
    // 2. 实例常量:声明时直接赋值
    final String DEFAULT_NAME = "未知用户";
    
    // 3. 实例常量:初始化块赋值
    final int DEFAULT_AGE;
    {
        DEFAULT_AGE = 18;
    }
    
    // 4. 实例常量:构造方法赋值(支持动态赋值)
    final String USER_ID;
    public FinalVariableDemo(String userId) {
        this.USER_ID = userId; // 仅能赋值一次
    }

    public static void main(String[] args) {
        // 局部常量:先声明后赋值
        final int MAX_COUNT;
        MAX_COUNT = 100;
        // MAX_COUNT = 200; // 编译报错:无法为最终变量分配值
        
        // 关键易错点:引用类型常量
        final User user = new User("张三", 20);
        // user = new User("李四", 21); // 编译报错:引用地址不可变
        user.setAge(21); // 合法:对象内容可修改
        System.out.println(user.getAge()); // 输出 21
    }
}

class User {
    private String name;
    private int age;
    
    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
    
    // get/set方法
    public void setAge(int age) {
        this.age = age;
    }
    public int getAge() {
        return age;
    }
}
2.1.3 关键注意点
  • 基本类型:值不可变;
  • 引用类型:引用地址不可变,但对象 / 数组的内容可修改(如final int[] arr = {1,2}; arr[0] = 3;合法);
  • 静态常量:推荐public static final组合使用,成为全局常量,编译期即可确定值(编译时常量)。

2.2 修饰方法:禁止重写

final修饰方法时,子类无法重写该方法(但可继承使用),核心作用是固定方法逻辑,防止子类篡改

2.2.1 核心规则
  • 可继承:子类能正常调用父类的 final 方法;
  • 不可重写:子类不能定义与父类 final 方法签名一致的方法;
  • 可重载:子类可定义同名但参数不同的方法(重载不受 final 限制)。
2.2.2 示例代码

public class FinalMethodDemo {
    // final方法:禁止子类重写
    public final void printInfo() {
        System.out.println("父类固定逻辑");
    }
    
    // 普通方法:允许重写
    public void normalMethod() {
        System.out.println("父类普通方法");
    }
}

class SubDemo extends FinalMethodDemo {
    // 编译报错:无法重写最终方法 FinalMethodDemo.printInfo()
    // @Override
    // public void printInfo() {
    //     System.out.println("子类修改逻辑");
    // }
    
    // 合法:重载final方法(参数不同)
    public final void printInfo(String msg) {
        System.out.println("子类重载方法:" + msg);
    }
    
    // 合法:重写普通方法
    @Override
    public void normalMethod() {
        System.out.println("子类修改普通方法逻辑");
    }
    
    public static void main(String[] args) {
        SubDemo demo = new SubDemo();
        demo.printInfo(); // 调用父类final方法,输出:父类固定逻辑
        demo.printInfo("测试"); // 调用子类重载方法,输出:子类重载方法:测试
        demo.normalMethod(); // 调用子类重写方法,输出:子类修改普通方法逻辑
    }
}

2.3 修饰类:禁止继承

final修饰类时,该类成为 “最终类”,无法被任何类继承,核心作用是保证类的功能完整性和安全性

2.3.1 适用场景
  • 核心工具类 / 基础类:防止子类篡改核心逻辑(如 JDK 的StringInteger);
  • 安全敏感类:避免通过继承注入恶意代码;
  • 性能优化:JVM 无需为 final 类做方法重写的动态分派优化,提升调用效率。
2.3.2 示例代码

// final类:禁止继承
final class FinalClassDemo {
    public void show() {
        System.out.println("final类的方法");
    }
}

// 编译报错:无法继承最终类 FinalClassDemo
// class SubClass extends FinalClassDemo {
// }

public class TestFinalClass {
    public static void main(String[] args) {
        // 合法:实例化并使用final类
        FinalClassDemo demo = new FinalClassDemo();
        demo.show(); // 输出:final类的方法
    }
}

三、final 在 JDK 源码中的典型应用

理解final的最佳方式是看 JDK 源码的设计思路,以下是高频应用场景:

3.1 修饰核心类:保证类不可继承

JDK 中大量基础类被声明为final,避免子类修改核心逻辑:

  • java.lang.String:字符串是不可变对象的核心,若允许继承,子类可篡改equals()hashCode()等关键方法,导致整个 Java 字符串体系崩溃;
  • java.lang.Integer/Long等包装类:保证数值类型的不可变性,避免多线程环境下的安全问题;
  • java.util.UUID:唯一标识生成类,禁止继承以保证 ID 生成规则的唯一性。

String 类源码片段

public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence {
    // 字符数组:final修饰,保证引用不可变
    private final char value[];
    
    // 构造方法
    public String() {
        this.value = "".value;
    }
    // ... 其他方法
}

3.2 修饰常量:定义全局不可变值

JDK 源码中大量使用public static final定义常量,作为行业标准值:

  • java.lang.Math:定义数学常量,如PIE
  • java.nio.charset.StandardCharsets:定义字符集常量(UTF_8GBK等);
  • java.net.HttpURLConnection:定义 HTTP 状态码常量(HTTP_OK=200HTTP_NOT_FOUND=404)。

Math 类源码片段

public final class Math {
    // 私有化构造方法:禁止实例化
    private Math() {}
    
    // 圆周率常量
    public static final double PI = 3.14159265358979323846;
    // 自然常数
    public static final double E = 2.7182818284590452354;
    
    // ... 数学方法
}

3.3 修饰方法:固定核心逻辑

JDK 中关键方法被声明为final,防止子类重写破坏逻辑:

  • java.lang.ObjectgetClass()方法是 final 的,保证返回的 Class 对象是当前对象的真实类型,避免子类伪造类型;
  • java.util.ArrayListsize()方法是 final 的,保证返回的集合大小是真实值,防止子类篡改;
  • java.lang.Stringequals()hashCode()intern()等核心方法是 final 的,保证字符串比较、哈希计算的逻辑统一。

Object 类源码片段

public class Object {
    // final方法:返回对象的运行时类
    public final native Class<?> getClass();
    
    // 普通方法:允许重写(如toString()、equals())
    public String toString() {
        return getClass().getName() + "@" + Integer.toHexString(hashCode());
    }
}

3.4 修饰参数:保证方法参数不可变

JDK 源码中部分方法的参数被声明为 final,防止方法内部修改参数引用:

  • java.util.CollectionaddAll(Collection<? extends E> c)方法的参数 c 常被 final 修饰(不同 JDK 版本略有差异);
  • java.lang.Stringconcat(String str)方法的参数 str 是 final 的,保证拼接的源字符串不被篡改。

示例(模拟 JDK 风格)

public final class String {
    // 拼接字符串方法:参数str为final
    public String concat(final String str) {
        if (str.isEmpty()) {
            return this;
        }
        int len = value.length;
        int otherLen = str.value.length;
        char buf[] = Arrays.copyOf(value, len + otherLen);
        str.getChars(buf, len);
        return new String(buf, true);
    }
}

四、final 的常见面试题 & 易错点

4.1 面试高频问题

  1. final 修饰的引用变量,指向的对象内容能改吗?能。final 仅限制引用地址不可变,对象内部属性可正常修改(如final List list = new ArrayList(); list.add(1);合法)。
  2. final、finally、finalize 的区别?
    • final:关键字,限制不可变;
    • finally:异常处理关键字,保证代码块必执行;
    • finalize:Object 类的方法,垃圾回收前执行(已被标记为过时)。
  3. static final 和 final 的区别?
    • static final:静态常量,属于类,类加载时初始化,全局唯一;
    • final:实例常量,属于对象,每个对象可有不同值(如构造方法赋值的 final 变量)。

4.2 开发易错点

  1. final 成员变量未赋值:编译报错,需确保在声明 / 构造方法 / 初始化块中赋值;
  2. 试图继承 final 类:如自定义类继承 String,编译直接报错;
  3. final 局部变量重复赋值:即使值相同,也会编译报错(如final int a = 1; a = 1;)。

五、总结

  1. final的核心是限制修改:修饰变量时值 / 引用不可变,修饰方法时不可重写,修饰类时不可继承;
  2. JDK 源码中,final多用于保护核心类(如 String)、定义全局常量(如 Math.PI)、固定关键方法逻辑(如 Object.getClass ());
  3. 实际开发中,合理使用 final 可提升代码安全性和可读性,尤其在多线程、常量定义场景下。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐