Java Class
类(class)是 Java 面向对象编程的基本构建单元,它既是数据抽象的载体,也是行为封装的边界。理解 class 的核心内容,本质上是理解 Java 如何用类型系统组织数据与行为。本文从现代 Java(25 LTS)视角出发,梳理 class 设计的核心要素。
类的本质:状态与行为的封装
一个 class 由两部分构成:字段(状态)和方法(行为)。封装的目的是把可变状态收敛到最小范围,对外只暴露不可变视图。
public final class User {
private final String name; // 不可变状态
private final String email;
public User(String name, String email) {
this.name = Objects.requireNonNull(name);
this.email = Objects.requireNonNull(email);
}
public String name() { return name; } // 只读访问器
public String email() { return email; }
}核心要点:
- 字段默认
private final,最大化不可变性 - 不暴露 setter,避免外部随意改写状态
final修饰类,防止意外继承破坏封装
优先用 Record 表达纯数据
当 class 只是一组数据的载体(没有复杂行为、不需要继承),优先用 record。它会自动生成构造器、访问器、equals/hashCode/toString,且天然不可变。
public record Point(int x, int y) {}
var p = new Point(1, 2);
int x = p.x(); // 访问器是方法形式,而非字段record 适合值对象、DTO、领域事件等。一旦需要可变状态或继承层次,再回到普通 class。
构造与创建:静态工厂优于构造器
构造器容易重载混乱、无法缓存实例,静态工厂方法更灵活且语义清晰。
public final class Email {
private final String value;
private Email(String value) { this.value = value; }
public static Email of(String value) {
Objects.requireNonNull(value, "email must not be null");
if (!value.contains("@")) {
throw new IllegalArgumentException("invalid email: " + value);
}
return new Email(value);
}
public String value() { return value; }
}静态工厂的优势:
- 可校验参数、可返回缓存或子类型实例
- 方法名能表达意图(
of、from、valueOf) - 构造器保持
private,强制走受控的创建路径
继承体系:Sealed + Pattern Matching
传统继承容易失控,Java 25 推荐用 sealed 接口限定实现范围,配合 switch 模式匹配实现穷尽检查,构成代数数据类型。
public sealed interface Shape permits Circle, Rectangle {}
public record Circle(double radius) implements Shape {}
public record Rectangle(double width, double height) implements Shape {}
double area = switch (shape) {
case Circle c -> Math.PI * c.radius() * c.radius();
case Rectangle r -> r.width() * r.height();
};
// 编译器保证所有分支被覆盖,新增实现会强制提示
设计原则:组合优于继承。继承表达 “is-a”,组合表达 “has-a”;后者耦合更低、更易测试。
// 错误:继承滥用
public class AdminUser extends User {}
// 正确:组合
public record UserWithRole(User user, Role role) {}接口:抽象契约与行为协议
interface 定义的是能力契约而非实现,它是 Java 实现多态、解耦的核心机制。与 class 不同,interface 关注"能做什么",class 关注"是什么"。
基本形态与演进
早期 interface 只能包含抽象方法和常量。从 Java 8 起支持 default 和 static 方法,Java 9 起支持 private 方法。现代 interface 已兼具契约定义与代码复用能力。
public interface Repository<T, ID> {
Optional<T> findById(ID id); // 抽象方法:实现类必须提供
// default 方法:提供默认实现,实现类可按需覆盖
default Optional<T> findByIdOrFail(ID id) {
return findById(id).orElseThrow(() ->
new NoSuchElementException("not found: " + id));
}
// static 方法:承载工厂与工具方法
static <E> Repository<E, String> inMemory() {
return new InMemoryRepository<>();
}
// private 方法:复用多个 default 方法之间的逻辑
private void log(String msg) {
System.out.println("[repo] " + msg);
}
}注意几个隐式约定:interface 中的方法默认 public abstract,字段默认 public static final——它们天然是常量,不能放可变状态。
函数式接口
只有一个抽象方法的 interface 可用 @FunctionalInterface 标注,作为 lambda 的目标类型。这是函数式编程在 Java 中的基石。
@FunctionalInterface
public interface PriceCalculator {
double calculate(Order order); // 唯一抽象方法
// default 方法不计入抽象方法计数
default PriceCalculator andThen(double discount) {
return order -> calculate(order) * (1 - discount);
}
}
PriceCalculator calc = order -> order.total() * 0.9;JDK 内置 Function、Predicate、Consumer、Supplier 等函数式接口,优先复用而非自定义。
多实现与菱形冲突
一个 class 可以 implements 多个 interface,这是 Java 解决多继承的方式。当多个 interface 提供同名 default 方法时,编译器强制要求子类覆盖以消除歧义:
interface A { default String hello() { return "A"; } }
interface B { default String hello() { return "B"; } }
class C implements A, B {
@Override
public String hello() {
return A.super.hello(); // 显式选择某一个的实现
}
}Sealed Interface:受限的类型层次
用 sealed 限定哪些类型可以 implements,配合 permits 列表实现穷尽检查(见上文继承体系章节)。这是现代 Java 表达代数数据类型的标准方式:把所有可能取值收拢到固定集合,让编译器替你保证 switch 分支完整。
interface 与 abstract class 的取舍
| 维度 | interface | abstract class |
|---|---|---|
| 状态字段 | 只能有常量(隐式 public static final) | 可有实例字段 |
| 多继承 | 支持多 implements | 单继承 |
| 构造器 | 无 | 有 |
| 表达力 | “能做什么”(能力) | “是什么”(归类) |
经验法则:优先 interface。只有当需要共享实例状态或构造逻辑时才用 abstract class。interface 可被多个不相关的类实现,扩展性更强;而 interface 的演化靠 default 方法平滑推进,不会破坏既有实现。
可见性:最小化暴露
可见性按 private > 包私有 > protected > public 的顺序从严选择,只暴露必要的契约。
| 修饰符 | 同类 | 同包 | 子类(跨包) | 其他包 | 典型场景 |
|---|---|---|---|---|---|
private | ✅ | ❌ | ❌ | ❌ | 字段、辅助方法、内部实现细节 |
| 包私有(不写修饰符) | ✅ | ✅ | ❌ | ❌ | 包内协作的类、测试辅助方法 |
protected | ✅ | ✅ | ✅ | ❌ | 模板方法模式中供子类覆写的钩子 |
public | ✅ | ✅ | ✅ | ✅ | API 契约、接口方法、跨包复用的类型 |
从严选择原则:先给 private,不够再逐级放宽。绝大多数类和成员不需要 public。
| 维度 | 建议 |
|---|---|
| 顶层类 | 尽量包私有;只有跨包使用的才 public |
| 字段 | 一律 private + 不可变(final),通过 record 或 accessor 暴露 |
| 方法 | 内部辅助方法 private;只有契约方法按需提升 |
| 构造器 | 需要控制实例化时 private(工厂/Builder 模式),否则按需开放 |
| 嵌套类 | 仅外部类使用的嵌套类 private static |
| 接口方法 | 默认 public abstract;default 方法用于平滑演化 |
public interface UserService {
Optional<User> findById(String id);
User create(String name, String email);
}字段、辅助方法、内部状态一律 private;只有需要跨包复用的契约才提升到 public。
方法设计:校验与 Optionality
方法入口应尽早校验参数,失败原子性保证异常后对象仍有效。
public User createUser(String name, String email) {
Objects.requireNonNull(name, "name must not be null");
if (name.isBlank()) {
throw new IllegalArgumentException("name must not be blank");
}
return new User(name, email);
}对于"可能无值"的返回,用 Optional 而非 null。注意 Optional 只用于返回值,不要塞进参数或字段。
public Optional<User> findById(String id) {
return Optional.ofNullable(repository.find(id));
}不可变与并发安全
不可变对象天然线程安全,是并发编程的首选。必须共享可变状态时,用并发集合或 final 字段加锁隔离。
// 不可变、无状态:天然安全
public record Processor(Config config) {
public Result process(Input input) {
return new Result(transform(input));
}
}
// 必须共享时用并发集合
private final ConcurrentHashMap<String, User> cache = new ConcurrentHashMap<>();高并发 I/O 场景下,配合虚拟线程可以以同步写法获得异步吞吐:
try (var executor = Executors.newVirtualThreadPerTaskExecutor()) {
executor.submit(() -> doTask());
}枚举:受限的 class
enum 本质是继承自 java.lang.Enum 的 final class,实例在类加载时静态创建、全局唯一。它天然不可变、线程安全、单例语义明确,适合表达固定的取值集合。
public enum Role {
ADMIN, USER, GUEST;
}枚举真正的威力在于可以携带字段与行为,把相关的常量、元数据、策略集中在一处:
public enum OrderStatus {
PENDING(false),
PAID(true),
SHIPPED(true),
CANCELLED(false);
private final boolean terminal; // 每个实例自带状态
OrderStatus(boolean terminal) { this.terminal = terminal; }
public boolean isTerminal() { return terminal; }
}要点:
- 枚举是实现单例的最佳方式(线程安全、防序列化攻击,无需手写双重检查锁)
- 配合
switch模式匹配可获得穷尽检查,新增常量时编译器强制处理 - 不要用
ordinal()做逻辑判断,它的顺序是脆弱的实现细节
嵌套类:收敛辅助类型
嵌套类把只服务于外部类的类型藏在其内部,减少包级污染。优先级:静态嵌套类 > 内部类 > 匿名类/局部类。
public final class Cache<K, V> {
// 静态嵌套类:不持有外部类引用,首选
private static final class Node<K, V> {
K key; V value; Node<K, V> next;
}
// 内部类(非 static):隐式持有外部类引用,
// 既阻碍 GC 又容易隐藏 bug,现代 Java 几乎用不到
private class View implements Iterator<V> { /* ... */ }
}经验法则:嵌套类若不需要访问外部实例状态,一律加 static。Lambda 和 record 在多数场景下已取代匿名类与局部类——能用 lambda 表达的策略,就别写匿名内部类。
泛型 class:类型安全的参数化
泛型让 class 在定义时推迟具体类型,使用时再指定,编译期即可消除类型转换与不安全访问。核心收益是把类型错误从运行期提前到编译期。
public final class Stack<E> {
private final List<E> elements = new ArrayList<>();
public void push(E e) { elements.add(e); }
public E pop() {
if (elements.isEmpty()) throw new NoSuchElementException();
return elements.removeLast();
}
}几个易错点:
类型擦除:运行时
Stack<String>与Stack<Integer>是同一个类,泛型信息只在编译期存在;不能new E()、不能new T[...],运行时也无法用instanceof Stack<String>区分边界通配符:生产者用
<? extends T>(读)、消费者用<? super T>(写),即 PECS 原则
无边界通配符
<?>表达"任意类型但不依赖它",比原始类型Stack安全得多,原始类型仅为兼容遗留代码保留
Record 的紧凑构造器与校验
record 默认生成的构造器不做任何校验。需要保证不变量时,用紧凑构造器(compact constructor)在校验后赋值,保证对象一旦创建就处于有效状态。
public record Email(String value) {
public Email { // 紧凑构造器,无参数列表
Objects.requireNonNull(value);
if (!value.contains("@")) {
throw new IllegalArgumentException("invalid email: " + value);
}
value = value.trim().toLowerCase(); // 可规范化赋值
}
}紧凑构造器在自动赋值前执行,赋值语句会覆盖默认行为。这是把"创建即合法"的值对象模式落到 record 上的标准写法——校验逻辑与类型定义同处一地,无需散落的工厂方法。
Object 与对象头
前面讨论的都是语言层面的 class 抽象,但运行时每个对象在堆上都有具体布局。理解这点,才能解释 hashCode、锁、GC 的底层行为。
根类型 Object
所有 class 隐式继承 java.lang.Object,它定义了一组通用契约:equals/hashCode/toString/getClass/clone/finalize,以及 wait/notify/notifyAll 这套管程协同原语。其中 hashCode 和 equals 必须一致——equals 相等的对象必须有相同 hashCode,这是把对象放进 HashMap/HashSet 的前提。
@Override
public boolean equals(Object o) {
return o instanceof User u && u.name.equals(name) && u.email.equals(email);
}
@Override
public int hashCode() {
return Objects.hash(name, email); // 与 equals 字段一致
}record 会自动生成与组件字段一致的 equals/hashCode,这是它适合做 Map key 的原因之一。
对象头(Object Header)
每个堆对象在 JVM 内部都有一个对象头,主要由两部分构成:
- Mark Word(64 位上通常 64 bit):存储运行时元数据——identity hashcode、GC 分代年龄、锁状态标志等
- Klass Pointer:指向类元数据(
Class对象在元空间的表示),开启指针压缩时压缩为 32 bit

几个关键点:
- hashcode 惰性计算:
hashCode()首次调用时才写入 Mark Word,之后复用;这也是为什么重写hashCode后仍要保证一致性 - 锁状态复用 Mark Word:synchronized 的轻量级锁、重量级锁就是通过改写 Mark Word 实现的;偏向锁自 JDK 15(JEP 374)起默认禁用并废弃,Java 25 中锁升级直接从无锁/轻量级开始
- GC 年龄占 4 bit:分代年龄上限为 15(
-XX:MaxTenuringThreshold),正是受限于这 4 位 - 指针压缩:堆小于 32 GB 时默认开启,Klass Pointer 与对象引用都压缩到 32 bit,显著降低内存占用
对象头之后才是实例字段,且 JVM 会对字段做重排序与对齐填充以优化访问。这也是 record 和紧凑布局的价值——字段少、对齐开销小,对象更省内存。
实践含义
对象头不是纯理论,它直接影响工程决策:
- 用
IdentityHashMap区分引用相等与equals相等,依赖的正是 identity hashcode(Mark Word 里的那个) - 大量小对象的内存开销里,对象头占比可观——这正是 LRU 缓存、图结构等场景需要考虑紧凑表示的原因
- 锁的代价与 Mark Word 状态机挂钩:无竞争时近乎免费,竞争激烈时膨胀为重量级锁开销陡增,因此优先无状态/不可变设计
小结
Java class 的核心可以用一句话概括:用不可变状态 + 受控创建 + 最小可见性 + 组合优先,组织数据与行为。落到具体选择上:
- 纯数据用
record,复杂对象用final class - 创建用静态工厂,继承用
sealed限定 - 可见性从严,方法入口校验
- 默认不可变,并发场景优先无状态设计
把握这几条,写出的 class 通常是简洁、安全、易测试的。



