final

在 Java 中,final 是一个关键字,用于表示 “不可变的” 或 “最终的”。它可以修饰变量、方法和类,在不同场景下有不同的含义和用法。

final 修饰变量

当一个变量被 final 修饰时,该变量应该被叫做常量,它只能被赋值一次且在被使用之前必须被赋值。赋值之后,其值不能再被修改。这适用于基本类型和引用类型。

常量名的定义规范:单个单词组成时所有字母大写;多个单词组成时每个单词之间用下划线连接,如:XXX_YYY_ZZZ

基本类型变量

对于基本类型(如 int,double 等),final 使得该变量成为一个常量,其数值不可改变。

const 是 Java 保留的关犍字,但目前并没有使用。

final 修饰的常量可以放在 switch 语句的 case 后。

程序示例:

public static void main(String[] args) {
    final int a = 20;
    a = 10;  // 报错: cannot assign a value to final variable 'a'
}

程序示例:

public static void main(String[] args) {
    final int a;                // 不使用 a 时不对 a 进行赋值是不会报错的
    System.out.println(a);      // 报错: Variable 'a' might not have been initialized
}

引用类型变量

对于引用类型,final 保证的是引用指向的对象地址不可变,即不能指向另一个对象,但对象内部的状态(成员变量)仍然可以改变。

final 引用只能保证引用本身不变,不保证对象内容不变。要实现真正的不可变对象,还需要类本身设计为不可变(如所有成员变量都是 final,且不提供修改内部状态的方法)。

程序示例:

public class Test {
    public static void main(String[] args) {
        final StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World");      // 允许,修改的是对象内容
        System.out.println(sb);   // 输出 "Hello World"

        sb = new StringBuilder(); // 编译错误!不能重新赋值
    }
}

程序示例:

JavaBean 类 Student 类:

public class Student {
    public int age;
    public String name;

    public Student() {
    }

    public Student(int age, String name) {
        this.age = age;
        this.name = name;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}

测试类:

public class Test {
    public static void main(String[] args) {
        final Student S = new Student(23, "zhangSan");  // 用 final 修饰一个引用数据类型
        S.setAge(24);                 // 对象内部的值可以改变
        S.setName("liSi");            // 对象内部的值可以改变
        System.out.println(S.getAge() + ", " + S.getName());  // 24, liSi

        S = new Student();  // 报错: cannot assign a value to final variable 'S'
    }
}

程序示例:

public class Test {
    public static void main(String[] args) {
        final int[] ARR = {1, 2, 3, 4};  // 用 final 修饰一个引用数据类型
        ARR[0] = 20;                     // 对象内部的值可以改变
        ARR[1] = 10;                     // 对象内部的值可以改变
        for (int i = 0; i < ARR.length; i++) {
            System.out.print(ARR[i] + "    ");    // 20    10    3    4
        }
        ARR = new int[3];    // 报错: cannot assign a value to final variable 'ARR'
    }
}

程序示例:

public class Test {
    public static void main(String[] args) {
        final StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World");      // 允许,修改的是对象内容
        System.out.println(sb);   // 输出 "Hello World"

        sb = new StringBuilder(); // 编译错误!不能重新赋值
    }
}

成员变量

实例变量:可以在声明时、构造代码块中或构造方法中赋值,且必须保证在对象创建完成前被赋值。

程序示例:

public class Student {
    final String name;          // 空白 final 变量

    public Student(String name) {
        this.name = name;       // 在构造方法中赋值
    }
}

静态变量:可以在声明时或静态代码块中赋值。

程序示例:

public class Constants {
    static final double PI = 3.14159;        // 声明时赋值
    static final long ID;

    static {
        ID = 1001L;     // 静态代码块中赋值
    }
}

局部变量

局部变量在使用前必须赋值,一旦赋值后不能再修改。

程序示例:

public class Test {
    public void method() {
        final int localVar = 10;
        // localVar = 20;  // 编译错误
    }
}

参数

方法参数也可以声明为 final,表示在方法内部不能修改参数的值。

程序示例:

public class Test {
    public void print(final int value) {
        // value = 100;  // 编译错误
        System.out.println(value);
    }
}

类常量

在 Java 中,类常量是指被 static final 关键字共同修饰的变量。它属于类级别(而不是对象级别),一旦初始化后其值就不能再被改变。类常量通常用于定义程序中通用的、不会变化的固定值。

类常量通过类名直接访问,无需创建对象。

类常量存储在方法区(或从 Java 8 开始,存储在堆中的 Class 对象中),在类加载时完成初始化。

所有实例共享同一份内存,不会因为创建多个对象而重复分配。

接口中定义的变量默认就是 public static final 的,因此接口中的变量也是类常量(但通常不推荐在接口中定义常量,而应使用枚举或专门的常量类)。

常量类:通常将相关的类常量放在一个专门的常量类中,便于集中管理。

程序示例:

public final class AppConstants {
    private AppConstants() {} // 私有构造,防止实例化
    public static final int TIMEOUT = 5000;
    public static final String BASE_URL = "https://api.example.com";
}

类常量的定义

public class MathConstants {
    // 类常量:属于类,值不可变
    public static final double PI = 3.141592653589793;
    public static final int MAX_COUNT = 100;
}

static:表示该变量属于类,所有实例共享一份内存,可以通过 类名.变量名 直接访问。

final:表示该变量只能被赋值一次,之后不可更改。

Math 类中的类常量:


图 1

类常量的分类

根据初始化时机和值的确定性,类常量可分为两类:

(1) 编译时常量

由基本类型或 String 类型定义,并且值在编译时就能确定。

编译器会直接将常量的值 “内联” 到使用它的代码中,不会产生对常量的引用。

示例:

public static final int AGE = 30;
public static final String NAME = "Alice";

(2) 运行时常量

值在运行时才能确定,例如通过方法调用、对象创建等。

即使使用 static final 修饰,它的值也是在类加载时初始化的,但不会发生编译期内联。

示例:

public static final long CURRENT_TIME = System.currentTimeMillis();
public static final String RUNTIME_CONFIG = loadConfig();

引用类型类常量

如果类常量的类型是引用类型(如数组、集合、自定义对象),final 仅保证引用不能指向另一个对象,但对象内部的状态仍然可以修改。

因此,定义引用类型的类常量时,如果希望集合本身也不可变,通常配合 Collections.unmodifiableXxx() 等方法包装。

程序示例:

public class Test {
    public static final List<String> NAMES = new ArrayList<>();

    public static void main(String[] args) {
        // 允许修改列表内容
        NAMES.add("Tom");    // 合法
        NAMES.remove(0);     // 合法

        // 不允许重新赋值
        // NAMES = new ArrayList<>();  // 编译错误
    }
}

为什么字符串的值不可变:

字符串用数组 value 用来存储字符串的内容,数组 value 存储的是字节,因为 value 是用 final 修饰的,所以 value 保存的地址值不能发生改变。由于有 private 修饰符,所以外界无法获取到 value 的值,也就是获取不到这个数组的地址,并且 String.java 没有提供 value 的 getter() 和 setter() 方法,所以在外界不能获取到 value 记录的地址值。这里是用 final 和 private 两个关键字结合起来使用,使得字符串不能发生改变。


图 2

图 3

final 修饰方法

用 final 修饰方法就表示这个方法是最终的方法,不能被子类重写。

设计目的:防止子类修改父类中关键方法的实现,确保行为一致性和安全性(例如模板方法模式中的固定步骤)。

程序示例:

class Parent {
    public final void show() {
        System.out.println("This is final method.");
    }
}

class Child extends Parent {
    // 编译错误!不能重写 final 方法
    // public void show() { }
}

final 修饰类

用 final 修饰一个类就表示这个类是最终的类,不能被继承,也就是不能有子类。

程序示例:

final class Utility {
    // 工具类,不希望被扩展
}

// 编译错误!不能继承 final 类
// class SubUtility extends Utility { }

常见例子:Java 核心类库中的 String、Integer 等包装类都是 final 的,主要是为了安全性和不可变性,避免被子类修改行为。


图 4
posted @ 2026-03-19 21:44  YouKong  阅读(0)  评论(0)    收藏  举报