跳到主要内容

关键字详解

问题

Java 中 finalstaticvolatilesynchronized 等核心关键字分别有什么作用?

答案

final

final 表示"不可变",可以修饰类、方法、变量:

修饰目标含义
不能被继承(如 StringInteger
方法不能被子类重写
变量基本类型:值不可变;引用类型:引用不可变(对象内容可变)
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 中保证执行的代码块
finalizeObject 方法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++ 这样的复合操作(读-改-写),仍然需要 synchronizedAtomicInteger

private volatile int count = 0;
// count++ 不是原子操作,volatile 不能保证线程安全
// 应使用:
private AtomicInteger count = new AtomicInteger(0);
count.incrementAndGet(); // 原子操作

Q4: 为什么 String 被设计为 final?

答案

  1. 安全性:String 广泛用于类加载、网络连接、文件路径等安全敏感场景,不可变防止被恶意子类篡改
  2. 字符串池:不可变才能安全地在池中共享
  3. hashCode 缓存:不可变保证 hashCode 恒定,适合作为 HashMap 的 key
  4. 线程安全:不可变对象天然线程安全

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 labelcontinue label 实现类似的跳转功能:

outer:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (j == 5) break outer; // 跳出外层循环
}
}

相关链接