Java 关键字详解:final 与 static(作用 + 使用场景)
final和static是 Java 中使用频率最高、也最容易混淆的两个关键字。本文从「能修饰什么 → 起到什么作用 → 用在什么场景」三个层次,把这两个关键字彻底讲清楚。📌 一句话区分:
final管「不可改变」,static管「类级别共享」。
目录
- 一、final 关键字
- 1.1 final 修饰类
- 1.2 final 修饰方法
- 1.3 final 修饰变量
- 1.4 final 修饰引用类型的易错点
- 1.5 final 的三大使用场景
- 二、static 关键字
- 2.1 static 修饰成员变量
- 2.2 static 修饰成员方法
- 2.3 static 修饰代码块
- 2.4 static 修饰内部类
- 2.5 static 的典型使用场景
- 2.6 static 常见误区
- 三、final 与 static 对比总结
- 四、总结
一、final 关键字
一句话概括:final的语义是「最终的、不可改变的」,它可以修饰类、方法、变量三种目标,但「不可改变」的具体含义随修饰目标而不同。
| 修饰目标 | 作用 | 一句话记忆 |
|---|---|---|
| 类 | 该类不能被继承 | 断子绝孙 |
| 方法 | 该方法不能被重写 | 不容篡改 |
| 变量 | 该变量只能赋值一次(常量) | 一锤定音 |
1.1 final 修饰类
当final修饰一个类时,该类不能被继承,所有成员方法都不能被重写。
publicfinalclassString{// String 就是典型的 final 类}// ❌ 编译报错:无法从最终类 String 中进行继承classMyStringextendsString{}核心作用:防止通过继承来改变类的行为。JDK 中的典型案例:String、Integer等包装类都被声明为final——因为如果允许继承并重写方法,就可能破坏它们「不可变」的语义。
1.2 final 修饰方法
当final修饰一个方法时,该方法不能被重写,但可以正常被子类继承使用。
classFather{publicfinalvoidshow(){System.out.println("Father 的方法,不允许被改写");}}classSonextendsFather{// ❌ 编译报错:Son 中的 show() 无法覆盖 Father 中的 show()// public void show() { }}核心作用:① 锁定方法行为,防止子类篡改;② 在早期 JVM 中还有利于内联优化(现代 JVM 已能自动判断,这条更多是历史原因)。
1.3 final 修饰变量
final修饰变量时,该变量成为常量,只能被赋值一次。按变量种类又分三种情况:
① 修饰局部变量:赋值后不可再改。
publicstaticvoidmain(String[]args){finalinta=10;// a = 20; // ❌ 编译报错:无法为最终变量 a 分配值}② 修饰成员变量:必须显式初始化,且只能通过以下两种方式之一赋值:
- 声明时直接赋值;
- 在构造方法中赋值(每个构造方法都要保证赋值一次)。
classStudent{finalStringname="Hanmeimei";// 方式一:声明时赋值finalintage;Student(intage){// 方式二:构造方法中赋值this.age=age;}}③ 修饰静态变量(static final):即全局常量,通常要求声明时立即赋值,命名习惯是全大写。
publicclassConstants{publicstaticfinaldoublePI=3.1415926;publicstaticfinalintMAX_COUNT=100;}1.4 final 修饰引用类型的易错点
这是面试与日常编码的高频坑点:当final修饰引用类型时,它约束的是引用本身(指向不能变),而不是引用指向的对象内容。
publicstaticvoidmain(String[]args){finalint[]array={1,2,3,4,5};array[0]=100;// ✅ 编译通过:内容可以修改System.out.println(Arrays.toString(array));// [100, 2, 3, 4, 5]// array = new int[]{4, 5, 6}; // ❌ 编译报错:无法为最终变量 array 分配值}结论:
final修饰引用类型 →指向不能变,内容可以变。这也是「String 不可变不是因为 value 被 final 修饰」的原因——真正原因是 String 类中没有任何修改 value 内容的方法。
1.5 final 的三大使用场景
| 场景 | 写法 | 说明 |
|---|---|---|
| 定义全局常量 | public static final int MAX = 100; | 最常用,配合static形成类级别常量 |
| 保护不可变类 | public final class Xxx | 禁止被继承,防止行为被篡改 |
| 锁定关键方法逻辑 | public final void login() | 安全校验、模板方法等不允许被子类改写 |
二、static 关键字
一句话概括:static的语义是「静态的、类级别的」——被static修饰的成员属于类,而不属于某个具体对象,在类加载时就完成初始化,全类共享同一份。
| 修饰目标 | 作用 | 一句话记忆 |
|---|---|---|
| 成员变量 | 变成类变量,全类共享一份 | 大家共用一个 |
| 成员方法 | 变成类方法,可通过类名直接调用 | 不用 new 也能用 |
| 代码块 | 成为静态代码块,类加载时执行一次 | 一次性初始化 |
| 内部类 | 成为静态内部类,不依赖外部类实例 | 独立于外部对象 |
2.1 static 修饰成员变量
被static修饰的变量称为静态变量 / 类变量,它在方法区中只存一份,被该类的所有对象共享。
classStudent{publicStringname;// 实例变量:每个对象各有一份publicstaticStringschool="第一中学";// 静态变量:所有对象共享一份publicStudent(Stringname){this.name=name;}}publicstaticvoidmain(String[]args){Students1=newStudent("张三");Students2=newStudent("李四");System.out.println(s1.school);// 第一中学(不推荐这样写)System.out.println(Student.school);// ✅ 推荐:通过类名访问Student.school="第二中学";// 修改静态变量System.out.println(s2.school);// 第二中学 —— s2 也受影响!}关键区别:实例变量随对象的创建而存在;静态变量在类加载时就已存在,即使没有创建任何对象也能使用。
2.2 static 修饰成员方法
被static修饰的方法称为静态方法 / 类方法,可以直接通过类名调用,不需要创建对象。
publicclassUtils{publicstaticintadd(inta,intb){returna+b;}publicintsub(inta,intb){// 实例方法returna-b;}}publicstaticvoidmain(String[]args){intr1=Utils.add(1,2);// ✅ 静态方法:类名直接调用// Utils.sub(3, 1); // ❌ 编译报错:实例方法必须先 newUtilsu=newUtils();intr2=u.sub(3,1);// 实例方法:通过对象调用}静态方法的两条硬性限制:
- 静态方法中不能直接访问非静态(实例)成员——因为静态方法在对象创建前就可能被调用,此时实例成员还不存在;
- 静态方法中不能使用
this和super—— 它们都依赖当前对象。
publicclassDemo{intnum=10;// 实例变量publicstaticvoidtest(){// System.out.println(num); // ❌ 编译报错:无法从静态上下文中引用非静态变量// System.out.println(this.num); // ❌ 编译报错:this 不可用于静态上下文}}反之,实例方法可以自由访问静态成员。
2.3 static 修饰代码块
静态代码块在类加载时自动执行一次,且只执行一次,常用于初始化静态成员(如加载配置文件、初始化连接池)。
publicclassConfig{publicstaticMap<String,String>map;static{// 静态代码块:类加载时执行一次map=newHashMap<>();map.put("url","jdbc:mysql://localhost:3306/db");map.put("driver","com.mysql.cj.jdbc.Driver");System.out.println("静态代码块执行了");}publicConfig(){System.out.println("构造方法执行了");}publicstaticvoidmain(String[]args){newConfig();// 输出顺序:静态代码块执行了 → 构造方法执行了newConfig();// 输出:构造方法执行了(静态代码块不再执行)}}执行顺序(重要):
父类静态代码块 → 子类静态代码块 → 父类实例代码块 → 父类构造方法 → 子类实例代码块 → 子类构造方法2.4 static 修饰内部类
静态内部类不依赖外部类对象即可创建,不能直接访问外部类的非静态成员。
publicclassOuter{privatestaticinta=1;privateintb=2;staticclassInner{// 静态内部类publicvoidshow(){System.out.println(a);// ✅ 可以访问外部类的静态成员// System.out.println(b); // ❌ 不能访问外部类的实例成员}}classInner2{// 普通内部类publicvoidshow(){System.out.println(b);// ✅ 可以访问外部类实例成员}}publicstaticvoidmain(String[]args){Outer.Innerinner=newOuter.Inner();// 静态内部类:无需外部类对象inner.show();Outerouter=newOuter();Outer.Inner2inner2=outer.newInner2();// 普通内部类:必须先有外部类对象inner2.show();}}2.5 static 的典型使用场景
| 场景 | 示例 | 说明 |
|---|---|---|
| 工具类方法 | Math.abs()、Arrays.sort() | 无需创建对象,直接调用 |
| 全局常量 | public static final int MAX = 100; | 类级别共享常量 |
| 全局计数器 | static int count; | 统计该类创建了多少对象 |
| 单例模式 | private static final Xxx instance = new Xxx(); | 饿汉式单例 |
| 静态工厂方法 | Integer.valueOf(1) | 隐藏构造细节 |
| 资源初始化 | 静态代码块加载配置文件 | 类加载时执行一次 |
main方法 | public static void main(String[] args) | JVM 无需创建对象即可调用入口 |
// 场景示例:用静态变量统计创建了多少个对象classCounter{publicstaticintcount=0;// 所有对象共享publicCounter(){count++;// 每创建一个对象,count 加 1}}publicstaticvoidmain(String[]args){newCounter();newCounter();newCounter();System.out.println(Counter.count);// 3}2.6 static 常见误区
- 静态方法里不能用
this/super,也不能直接访问实例变量和实例方法; - 静态方法不能被重写:父类静态方法在子类中「同名同参」时是隐藏(Hide),不是重写,且调用取决于编译时类型;
- 静态变量是共享的,一个对象改了会影响所有对象(多线程场景下要格外小心,可能需要
synchronized或volatile); static不能修饰局部变量(局部变量属于方法栈帧,无"类级别"概念)。
三、final 与 static 对比总结
3.1 final vs static
| 维度 | final | static |
|---|---|---|
| 核心语义 | 最终的、不可改变 | 静态的、类级别共享 |
| 修饰类 | 类不能被继承 | 只用于静态内部类 |
| 修饰方法 | 方法不能被重写 | 方法属于类,类名可直接调用 |
| 修饰变量 | 变量只能赋值一次 | 变量属于类,全类共享一份 |
| 生效时机 | 编译期约束(引用不可变) | 类加载时初始化 |
| 常见组合 | public static final→ 全局常量 | 同左 |
3.2 容易混淆的组合
| 组合 | 含义 | 示例 |
|---|---|---|
static final | 静态常量,类加载时赋值,全类共享 | public static final int MAX = 100; |
final static | 与上等价(修饰符顺序不影响语义) | final static int MIN = 0; |
abstractvsfinal | 互斥:抽象要求被继承重写,final 禁止 | 二者不能同时修饰同一个类/方法 |
staticvsabstract | 互斥:静态方法不能被重写,抽象方法必须被重写 | 不能同时修饰同一个方法 |
四、总结
final是「不可变」的保证,三种修饰含义不同:类不可继承、方法不可重写、变量只能赋值一次;注意final修饰引用类型时只锁指向、不锁内容;static是「类级别」的标记,修饰的成员属于类、全类共享、类加载时初始化;静态方法不能访问实例成员、不能用 this/super、不能被重写;static final组合是定义全局常量的标准姿势,命名习惯全大写;
如果这篇文章对你有帮助,欢迎点赞 👍 + 收藏 ⭐ + 关注,后续会继续更新 Java 系列文章!