关键字详解
问题
Java 中 final、static、volatile、synchronized 等核心关键字分别有什么作用?
答案
final
final 表示"不可变",可以修饰类、方法、变量:
| 修饰目标 | 含义 |
|---|---|
| 类 | 不能被继承(如 String、Integer) |
| 方法 | 不能被子类重写 |
| 变量 | 基本类型:值不可变;引用类型:引用不可变(对象内容可变) |
FinalDemo.java
// final 变量
final int MAX = 100; // 常量,不可修改
final List<String> list = new ArrayList<>();
list.add("hello"); // ✅ 对象内容可变
// list = new ArrayList<>(); // ❌ 引用不可变
// final 方法参数:方法内不能重新赋值
public void process(final String name) {
// name = "new"; // 编译错误
}
// blank final:声明时不初始化,在构造器中初始化(每个构造器都必须初始化)
public class Config {
private final String env;
public Config(String env) {
this.env = env; // 必须在构造器中赋值
}
}
final 与性能
final字段在构造器完成后,其他线程可以安全地看到正确的值(JMM 保证)final方法可以被 JIT 内联优化- 但不要为了"性能"到处加
final,应从设计意图出发
static
static 表示"属于类而非实例":
| 修饰目标 | 说明 |
|---|---|
| 字段 | 类变量,所有实例共享 |
| 方法 | 类方法,不需要实例即可调用 |
| 代码块 | 类加载时执行一次 |
| 内部类 | 静态嵌套类,不持有外部类引用 |
| 导入 | import static,直接使用静态成员 |
StaticDemo.java
public class Counter {
private static int count = 0; // 静态字段:所有实例共享
private static final int MAX = 100; // 静态常量
static {
// 静态代码块:类加载时执行一次,用于初始化
System.out.println("Counter class loaded");
}
public static int getCount() { // 静态方法
// 不能访问 this,不能访问非静态成员
return count;
}
}
初始化顺序:
父类静态变量/静态代码块 → 子类静态变量/静态代码块 →
父类实例变量/代码块 → 父类构造器 →
子类实例变量/代码块 → 子类构造器
volatile
volatile 保证变量的可见性和有序性(禁止指令重排),但不保证原子性:
VolatileDemo.java
public class Singleton {
// volatile 防止指令重排导致的半初始化问题
private static volatile Singleton instance;
public static Singleton getInstance() {
if (instance == null) { // 第一次检查
synchronized (Singleton.class) {
if (instance == null) { // 第二次检查
instance = new Singleton(); // 没有 volatile 可能会重排
}
}
}
return instance;
}
}
volatile 的内存语义
- 写 volatile 变量:将工作内存中的值刷新到主内存,并通知其他线程缓存失效
- 读 volatile 变量:从主内存读取最新值到工作内存
- 通过内存屏障(Memory Barrier)实现
synchronized
synchronized 提供互斥锁,保证同一时刻只有一个线程执行同步代码:
SynchronizedDemo.java
public class SafeCounter {
private int count = 0;
// 同步方法:锁是 this 对象
public synchronized void increment() {
count++;
}
// 同步代码块:可以指定锁对象
public void decrement() {
synchronized (this) {
count--;
}
}
// 静态同步方法:锁是 Class 对象
public static synchronized void staticMethod() {
// 锁对象是 SafeCounter.class
}
}
关于 synchronized 的更多内容(锁升级、偏向锁、轻量级锁等),参见 并发编程 分类。
transient
transient 修饰的字段不参与 Java 原生序列化,详见 序列化。
instanceof
instanceof 用于检查对象是否是某个类/接口的实例:
InstanceofDemo.java
Object obj = "hello";
if (obj instanceof String) {
String s = (String) obj;
System.out.println(s.length());
}
// JDK 16+ 模式匹配(Pattern Matching for instanceof)
if (obj instanceof String s) {
// 直接使用 s,无需强制转换
System.out.println(s.length());
}
常见面试问题
Q1: final、finally、finalize 的区别?
答案:
| 关键字 | 类型 | 用途 |
|---|---|---|
final | 关键字 | 不可变:类不可继承、方法不可重写、变量不可重赋值 |
finally | 异常处理 | try-catch-finally 中保证执行的代码块 |
finalize | Object 方法 | GC 前调用,已废弃(JDK 9+) |
Q2: static 方法能不能访问非 static 成员?
答案:
不能直接访问。static 方法属于类,执行时没有 this 引用,无法访问实例成员。要访问非静态成员,必须通过对象引用:
public static void printName() {
// System.out.println(name); // 编译错误(name 是实例变量)
MyClass obj = new MyClass();
System.out.println(obj.name); // 通过对象访问
}
Q3: volatile 能保证线程安全吗?
答案:
不能完全保证。volatile 只保证可见性和有序性,不保证原子性。对于 count++ 这样的复合操作(读-改-写),仍然需要 synchronized 或 AtomicInteger:
private volatile int count = 0;
// count++ 不是原子操作,volatile 不能保证线程安全
// 应使用:
private AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作
Q4: 为什么 String 被设计为 final?
答案:
- 安全性:String 广泛用于类加载、网络连接、文件路径等安全敏感场景,不可变防止被恶意子类篡改
- 字符串池:不可变才能安全地在池中共享
- hashCode 缓存:不可变保证 hashCode 恒定,适合作为 HashMap 的 key
- 线程安全:不可变对象天然线程安全
Q5: static 内部类和非 static 内部类的区别?
答案:
| 维度 | 静态内部类 | 非静态内部类(成员内部类) |
|---|---|---|
| 外部类引用 | 不持有 | 隐式持有外部类 this |
| 创建方式 | new Outer.Inner() | outer.new Inner() |
| 访问外部成员 | 只能访问 static 成员 | 可以访问所有成员 |
| 内存泄漏风险 | 无 | 可能阻止外部类被 GC |
| 使用建议 | 优先使用 | 确实需要访问外部实例时使用 |
public class Outer {
private int x = 1;
private static int y = 2;
// 静态内部类:推荐
static class StaticInner {
void test() { System.out.println(y); } // 只能访问 static 成员
}
// 非静态内部类
class Inner {
void test() { System.out.println(x + y); } // 可访问所有成员
}
}
Q6: Java 中有 goto 关键字吗?
答案:
goto 是 Java 的保留字但未使用(没有对应的语法)。Java 使用 break label 和 continue label 实现类似的跳转功能:
outer:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (j == 5) break outer; // 跳出外层循环
}
}