小熊奶糖(BearCandy)
小熊奶糖(BearCandy)
发布于 2025-10-11 / 0 阅读
0
0

JAVA final详解

final 在英文中是“最终的”、“不可改变的”意思。在 Java 中,它正是用来表示这种“不可变性”的语义。它可以用来修饰变量、方法和类,但在这三种上下文中的具体含义和效果有所不同。


1. final 修饰变量

这是 final 最常用的情况。一个被 final 修饰的变量只能被赋值一次,一旦初始化后,其值就不能再被更改。本质上,它变成了一个常量。

根据变量的类型,我们又可以分为:基本类型变量和引用类型变量。

a) final 修饰基本类型变量

变量的不能再改变。

final int maxScore = 100;
// maxScore = 95; // 编译错误!无法为最终变量maxScore再次赋值

final double pi = 3.14159;
// pi = 3.14; // 编译错误!

b) final 修饰引用类型变量

变量的引用(即指向内存地址的“指针”)不能再改变。但是,它所指向的那个对象内部的状态(对象的属性)是可以被修改的

这一点非常重要,也常常是初学者混淆的地方。

// 定义一个简单的Person类
class Person {
    public String name;
    public int age;
}

public class Test {
    public static void main(String[] args) {
        final Person person = new Person(); // person是一个final引用
        person.name = "Alice"; // 正确:可以修改对象内部的属性
        person.age = 25;       // 正确:可以修改对象内部的属性

        // person = new Person(); // 编译错误!无法将新对象的引用赋给最终变量person

        System.out.println(person.name); // 输出: Alice
    }
}

关键理解final Person person 意味着 person 这个“遥控器”只能控制最开始绑定的那个“电视”,不能再绑定到别的“电视”上。但是,用这个“遥控器”去调台、调音量(修改对象的属性)是完全允许的。

final 变量的初始化

final 变量必须在明确初始化之后才能使用。有两种初始化方式:

  1. 声明时直接初始化

    final int CONSTANT_VALUE = 10;
    
  2. 在构造器或构造代码块中初始化(对于实例变量)
    这种方式非常有用,它允许一个类的每个实例拥有不同的“final”值。

    class MyClass {
        final int instanceFinalVar;
    
        public MyClass(int value) {
            this.instanceFinalVar = value; // 在构造器中初始化
        }
    
        // 或者使用构造代码块
        // {
        //    instanceFinalVar = 100;
        // }
    }
    
    public class Test {
        public static void main(String[] args) {
            MyClass obj1 = new MyClass(10);
            MyClass obj2 = new MyClass(20);
            // obj1和obj2的instanceFinalVar值不同,但各自初始化后都不能再改变。
        }
    }
    

静态 final 变量(常量)
如果 finalstatic 一起修饰一个变量,那它就成为了一个类常量。通常用全大写字母和下划线命名,必须在声明时或静态代码块中初始化。

class Constants {
    public static final String APPLICATION_NAME = "MyApp";
    public static final double PI;

    static {
        PI = 3.1415926; // 在静态代码块中初始化
    }
}

2. final 修饰方法

当一个方法被 final 修饰时,表示这个方法不能被子类重写(Override)

设计目的

  • 防止继承体系中的关键方法被篡改:确保父类中的某个方法行为在子类中保持不变。
  • 效率考虑(早期Java):编译器在早期可能会对 final 方法进行内联优化。不过现代 JVM 已经非常智能,这个优化意义不大了。
class Parent {
    // 这是一个不允许子类重写的方法
    public final void forbiddenToOverride() {
        System.out.println("这是父类的最终实现,子类不能修改。");
    }

    public void canBeOverridden() {
        System.out.println("子类可以重写我。");
    }
}

class Child extends Parent {
    // @Override
    // public void forbiddenToOverride() { } // 编译错误!无法重写父类中的final方法

    @Override
    public void canBeOverridden() { // 正确:可以重写非final方法
        System.out.println("子类重写了这个方法。");
    }
}

3. final 修饰类

当一个类被 final 修饰时,表示这个类不能被继承,即不能有子类。

设计目的

  • 保证类的行为和安全:如果一个类的实现已经非常完美,或者从安全角度考虑,不希望任何人通过继承来修改其行为(例如,可能会引入恶意代码),就将其声明为 final
  • JDK 中的例子String, Integer, Double 等包装类都是 final 类。这保证了像 String 的不可变性等核心特性不会被破坏。
final class FinalClass {
    public void someMethod() {
        // ...
    }
}

// class SubClass extends FinalClass { } // 编译错误!无法从final类FinalClass继承

总结与最佳实践

修饰目标 含义与效果 常见场景
变量 只能赋值一次,成为常量。 1. 定义程序中的字面常量(如 MAX_SIZE)。
2. 确保引用在初始化后不再指向其他对象。
方法 不能被子类重写。 1. 保护关键方法的实现逻辑。
2. 在涉及继承的严谨架构设计中明确禁止重写。
不能被继承。 1. 设计不可变类(如 String)。
2. 保证核心 API 的安全和稳定。

最佳实践与深入理解

  1. final 与不可变对象
    要创建一个真正的不可变对象,需要满足:

    • 类声明为 final(或者所有构造方法都是 private 的,并用静态工厂方法创建对象),防止子类改变其行为。
    • 所有字段用 private final 修饰。
    • 不提供修改内部状态的 setter 方法。
    • 如果字段是引用类型,要确保不会泄露该引用,或者在返回时返回其深拷贝。
  2. final 与线程安全
    正确初始化的 final 字段具有很好的线程安全性。JVM 内存模型保证,在构造方法中初始化一个 final 字段时,只要对象被正确构造(没有发生 this 引用逸出),那么其他线程就能看到最终初始化后的值,无需额外的同步。这也是为什么不可变对象天生是线程安全的。

  3. final 参数
    在方法参数列表中也可以使用 final,表示在方法内部不能修改这个参数的值(对于基本类型)或引用(对于引用类型)。这主要是一种代码规范和设计意图的体现,可以提高代码的可读性,并防止无意中的修改。

    public void process(final String message, final int id) {
        // message = "new message"; // 编译错误!
        // id = 456; // 编译错误!
        System.out.println("Processing " + message + " with ID: " + id);
    }
    

评论