☕ 第四章 面向对象(下)

4.2 final关键字

📄 一句话概括

final = 最终的,不许再改。加在变量前锁"值"、加在方法前锁"重写"、加在类前锁"继承"——三把锁全是给 4.1 的继承踩刹车:改不起的东西,一开始就锁死。


1. 一个词、三把锁

final 是本章学到的第一个"修饰符关键词",它能出现在三种位置,锁的东西完全不同(上面那张图):

位置 锁住什么 效果
final 变量 值 赋一次终身不变,再赋编译报错
final 方法 重写权 子类能用这个方法,但不许自己重写一版
final 类 继承权 这个类"断子绝孙",谁也不能 extends 它

记忆抓手:final 的英文本意是"最终的"——变量到了最终值、方法到了最终版本、类到了最终形态,都到此为止。


2. 第一把锁:final 变量(最常用)

final int WEEK_DAYS = 7;   // 赋一次
WEEK_DAYS = 8;             // ❌ 编译报错:cannot assign a value to final variable

注意措辞:不是"运行时改不动",是编译器直接不让过——错误在你按保存键的那一刻就暴露,比 bug 逃到运行时强多了。

两种落点,规矩不同

// ① final 局部变量:声明时就赋值(和 2.2 的局部变量规矩合体)
final double RATE = 0.03;

// ② final 成员变量:声明时、构造方法里,二选一必须赋上
class Circle {
    final double PI = 3.14159;        // 写法一:声明时直接给
    final String id;
    public Circle(String id) {
        this.id = id;                  // 写法二:构造方法里给(每个对象可不同)
    }
}
⚠️ final 成员变量不赋值 = 编译报错

普通成员变量有默认值(0/null),final 成员变量连默认值的待遇都没有——必须有人明确赋一次,否则编译不过。逻辑上说得通:"最终值"怎么能是没人定的默认值呢?

static final = 常量标配(2.1 的老朋友正式转正)

public static final double PI = 3.141592653589793;
public static final int MAX_SCORE = 100;

三个特征凑齐,就是 Java 常量的标准姿势。你天天用的 Math.PI、Integer.MAX_VALUE 全是这个套路——现在你能看懂它们的"配方"了。

什么时候把数提成常量? 代码里出现 0.03 这种"魔法数字"且含义重要时,提成 final double RATE = 0.03;——将来利率变了只改一处,又见 3.3 的"把变化圈在一处"。


3. 第二把锁:final 方法(为 4.3 铺路)

class Animal {
    final void breathe() {                    // 锁住重写权
        System.out.println("吸气,呼气");
    }
}

class Dog extends Animal {
    // ❌ 编译报错:breathe() 不能被重写
    // void breathe() { System.out.println("用鳃呼吸"); }

    void bark() { ... }                       // ✅ 自己的新方法随便加
}

两个容易混的点,提前说清:

什么时候锁方法? 方法的核心流程不允许子类篡改时。比如一个支付类的 checkPassword(),校验步骤错了就是安全事故——设成 final,子类想动也没门。


4. 第三把锁:final 类

final class String { ... }        // Java 官方就是这么定义 String 的

class MyString extends String { } // ❌ 编译报错:无法从最终 String 进行继承

你身边的 final 类比你想象的多:String、Integer、Double、System……全是 final。为什么?这些是 Java 大厦的承重墙——如果谁都能写个 MyString extends String 塞进系统,字符串的可靠性就完了。稳定压倒灵活,是 final 的设计哲学。

final 类里所有方法自动都是"不可重写"的(都没有儿子了,还重写给谁),不用再挨个加 final。


5. 一个进阶小坑:final 锁的是"钥匙",不是"车"

(延续 3.2 的栈钥匙堆实物暗线)

final Dog d = new Dog();
d.age = 3;          // ✅ 能改!改的是堆里那只狗
d = new Dog();      // ❌ 报错!钥匙不许再指向别的狗

final 修饰引用类型时,锁的是"引用不能再指别处",不是"对象内容不能改"。类比:final 房产证不许过户,但屋里家具随便挪。这个坑初学阶段记住结论即可,学到第五章对象的更多细节时会回头补一刀。


6. 动手练习(保姆式,接着 IDEA04 项目写)

📝 一次一步

① 打开 4.1 的 IDEA04 项目,src 右键 → New → Java Class → 输入 Demo01 ② 抄下面的代码(四段实验各有编号注释) ③ 绿三角 ▶ 跑,对照"预期输出"逐行核对

public class Demo01 {
    // 常量标配:static final + 全大写
    public static final int MAX_SCORE = 100;

    public static void main(String[] args) {
        // 实验① final 局部变量
        final int WEEK_DAYS = 7;
        System.out.println("一周有 " + WEEK_DAYS + " 天");
        // WEEK_DAYS = 8;   // 取消注释试试 → 编译报错

        // 实验② final 锁钥匙不锁车
        final Dog d = new Dog();
        d.name = "旺财";
        d.name = "小黑";     // ✅ 对象内容随便改
        // d = new Dog();    // 取消注释试试 → 编译报错

        // 实验③ final 类的成员变量必须在构造里赋值
        System.out.println("满分:" + MAX_SCORE);
    }
}

final class Certificate {           // 实验④ final 类
    final String no;
    public Certificate(String no) { this.no = no; }
}
// class FakeCert extends Certificate { }  // 取消注释试试 → 编译报错

成功标准:正常版跑通两行输出;三处注释各取消一次,亲眼看三种编译报错——final 的三把锁,锁一次记一辈子。


7. 常见误区

⚠️ 误区 1:final 变量必须在声明那一行赋值

成员变量在构造方法里赋值也算数(每个对象定一次终身)。只有局部变量才必须声明时赋值。

⚠️ 误区 2:final 方法 = 子类用不了

反了——子类照样继承、照样调用,只是不许换自己的版本。final 锁的是"改版权",不是"使用权"。

⚠️ 误区 3:final 修饰对象 = 对象内容不能改

final 锁的是引用(钥匙不许换),对象内部的属性随便改。见第 5 节。

⚠️ 误区 4:常量写 `static final int maxScore = 100;`

功能上没错,但命名惯例破功:常量必须全大写 + 下划线(MAX_SCORE)。团队代码里小写常量会被当成变量误改——惯例不是装饰,是给读代码的人的信号灯。

⚠️ 误区 5:加了 final 程序会更快

微乎其微,别为性能加 final。用 final 的正当理由只有一个:表达"这里不许改"的设计意图,让编译器和读代码的人都帮你盯着。


8. 自测题(先自己答,再展开)

❓ Q1:`static final int MAX = 100;` 里 static 和 final 各管什么?

static:属于类,全类一份,类名.MAX 直接访问(3.7);final:赋一次终身不改。合起来 = 类级常量,Java 常量标配三件套的另外两件是"全大写命名"。

❓ Q2:final 类里的方法还需要挨个加 final 吗?

不需要。final 类没有子类,"被重写"这件事根本不可能发生——所有方法自动不可重写。

❓ Q3:下面代码哪里错?
class Circle {
    final double PI;
}

final 成员变量 PI 既没在声明时赋值,也没在构造方法里赋值——编译报错"变量 PI 未初始化"。二选一必须占一个。

❓ Q4:`final StringBuilder sb = new StringBuilder("hi"); sb.append("!");` 合法吗?

合法。final 锁的是钥匙 sb 不能再指向别的对象,但堆里那个 StringBuilder 的内容随便改。final 引用 ≠ 不可变对象。

❓ Q5:为什么 String 要设计成 final 类?

String 是 Java 大厦的承重墙:几乎所有代码都依赖字符串的可靠性。如果不锁,任何人都能 class MyString extends String 篡改行为,全生态的地基就晃了。稳定压倒灵活——这就是 final 的设计哲学。


9. 下一步学什么


相关笔记