Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

面向对象

基本概念

类与修饰符

一个 .java 文件中,最多只能有一个 public 顶级类,文件名必须和这个 public 类名一致。Java 中不能有独立函数,函数必须属于类

Java 通过四种修饰符来控制类、属性、方法的可见性。

修饰符本类同一个包子类(不同包)任意位置(不同包)备注
private✅❌❌❌封装的核心。仅限类内部使用。
default✅✅❌❌不写任何关键字时的默认状态(包权限)。
protected✅✅✅❌继承的核心。专门给子类留的后门。
public✅✅✅✅只要能导入这个包,就能访问。

三大特性

  1. 封装(Encapsulation)的核心思想是隐藏内部实现,只对外暴露必要接口。

class User {
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. 继承(Inheritance)的核心思想是子类复用父类代码。

class Animal {
    public void eat() {
        System.out.println("吃东西");
    }
}

class Dog extends Animal {
}

Java 只支持单继承,是为了避免多继承带来的“菱形继承”二义性问题,防止子类在继承多个父类时对同一方法或属性产生调用冲突,从而保证继承结构清晰、逻辑确定、实现简单。同时,Java 通过允许接口多实现来弥补单继承在扩展性上的不足,实现“类单继承 + 接口多实现”的设计平衡。

final 关键词可以修饰类、方法、变量,用于限定修饰对象的使用范围:

修饰对象示例作用典型场景底层影响
类final class A {}不能被继承工具类、安全类类不会有子类,结构固定
方法public final void test(){}不能被重写不希望子类修改核心逻辑可被 JVM 内联优化(静态绑定)
变量(基本类型)final int a = 10;只能赋值一次常量定义值不可改变
变量(引用类型)final User u = new User();引用地址不能改变防止对象被重新指向不能指向新对象,但对象内部属性可变
  1. 多态(Polymorphism)的核心思想是父类引用指向子类对象,方法调用是动态绑定,即编译时不知道调用哪个方法,运行时才确定调用子类的方法。多态的三个前提是:有继承、有方法重写、父类引用指向子类对象。

在 Java 中,多态的底层依赖于 虚方法表(vtable)机制。JVM 会为每个类在方法区维护一张虚方法表,表中记录该类可被重写的实例方法地址。当通过父类引用调用方法时,编译阶段只确定“方法符号引用”,真正执行哪个方法要等到运行时:JVM 先根据对象找到其真实类型(如 Dog),再到该类的虚方法表中查找对应的方法入口,最终调用子类重写后的实现。这就是动态绑定。与之对比,像 static、private、final 方法在编译期就能确定调用版本,属于静态绑定;而普通成员方法默认采用运行期确定的动态绑定机制。

Animal a = new Dog();
a.eat(); // 实际调用 Dog 的 eat()

重载与重写

对比项重载(Overload)重写(Override)
发生位置同一个类中子类中
方法名必须相同必须相同
参数列表必须不同(类型 / 个数 / 顺序)必须相同
返回值可以不同必须相同或协变返回
访问权限无限制不能变小
发生阶段编译期(静态绑定)运行期(动态绑定)
是否要求继承不需要必须有继承关系

private 方法不能被重写,因为它不参与继承,子类中即使定义了同名同参的方法,也只是一个新的方法,与父类方法没有重写关系。

class A {
    private void test() {}
}

class B extends A {
    public void test() {} // 不是重写,是新方法
}

static 方法不能被重写,只能被隐藏,因为它属于类而不是对象,调用在编译期就已确定,不参与运行期的动态绑定。

class A {
    static void test() {
        System.out.println("A");
    }
}

class B extends A {
    static void test() {
        System.out.println("B");
    }
}

public class Main {
    public static void main(String[] args) {
        A a = new B();
        a.test();   // 输出:A
    }
}

构造器

构造器是与类名相同、没有返回值的方法,在创建对象时自动调用,可以重载但不能被继承或重写;构造器不能重写的原因是它不参与继承且子类不会继承父类构造器;当创建子类对象时,一定会先执行父类构造器,再执行子类构造器,保证对象从父到子完成初始化。

super() 用于调用父类构造器,如果不写,默认隐式调用 super(),this() 用于调用本类其他构造器,二者都必须写在构造器的第一行;由于构造器的第一行只能有一条语句,因此 this() 和 super() 不能同时出现,只能二选一。

对比thissuper
表示当前对象父类对象
访问成员本类成员父类成员
调用构造调本类构造器调父类构造器
是否必须第一行仅 this() 时仅 super() 时

构造器执行完整流程为:

Object方法

常见的Object方法包括:

方法作用
toString()返回对象字符串表示(默认为类名@十六进制hash值)
equals()比较对象是否相等
hashCode()返回对象哈希值
wait()当前线程进入等待
notify()唤醒一个等待线程
clone()复制对象

注意,必须同时重写 equals 和 hashCode。如果只重写 equals() 而不重写 hashCode(),就会破坏 Java 的约定:两个对象如果 equals 相等,它们的 hashCode 必须相等。因为在 HashMap、HashSet 等基于哈希的集合中,元素存取时会先比较 hashCode 决定桶位置,再用 equals 判断是否为同一个对象;如果两个逻辑上相等的对象 hashCode 不同,就会被分到不同位置,从而导致集合中出现重复元素或查找失败的问题。

抽象类与接口

什么时候用抽象类?当多个子类之间存在明显的继承关系,并且需要共享属性、通用方法或部分实现逻辑时,应使用抽象类,它既可以提供公共代码实现,又可以定义抽象方法强制子类实现,适合强调“is-a”关系和代码复用的场景。

什么时候用接口?当类之间不强调继承关系,而只是需要约定某种行为规范或扩展某种能力时,应使用接口,它更侧重定义标准和能力扩展,支持多实现,适合解耦设计和横向能力扩展的场景。

对比项抽象类接口
继承方式单继承多实现
方法可以有普通方法、抽象方法Java 8 之后可有 default、static 方法
成员变量可以有普通成员变量只能是 public static final 常量
构造器有没有
设计目的代码复用规范约束

静态成员

static 属于类,而不是对象,不依赖对象,也不参与多态。随类加载而加载,只执行一次。静态方法中不能访问非静态成员(因为没有对象)。静态方法不能被重写,只能被隐藏(编译期绑定)。类在首次主动使用时才会被加载和初始化,例如创建对象、访问静态变量、调用静态方法、通过反射使用类或初始化子类时触发父类初始化,并且一个类在 JVM 生命周期中只会被加载和初始化一次。

静态代码块在类加载阶段执行且只执行一次,实例代码块在每次创建对象时都会执行,而构造器是在对象初始化过程中的最后一步执行。new 一个对象执行顺序为:

内部类

  1. 成员内部类:定义在类中方法外,依赖外部类对象存在。由于是因为编译后内部类会隐式持有一个外部类对象引用,因此可以访问外部类的所有成员(包括 private)。

class Outer {
    private int a = 10;

    class Inner {
        void test() {
            System.out.println(a); // 可以访问
        }
    }
}
  1. 静态内部类:使用 static 修饰,不依赖外部类对象,只能访问外部类的静态成员。

class Outer {
    static int a = 10;

    static class Inner {
        void test() {
            System.out.println(a);
        }
    }
}
  1. 局部内部类:定义在方法内部,作用范围仅限当前方法,并且只能访问方法中有效 final 的变量。

void method() {
    int x = 10; // 不能再修改
    class Inner {
        void test() {
            System.out.println(x);
        }
    }
}
  1. 匿名内部类:没有类名,通常用于临时实现接口或继承类,多用于回调或事件处理场景。

Runnable r = new Runnable() {
    @Override
    public void run() {
        System.out.println("run");
    }
};

枚举

枚举是一种 特殊的类,用于表示一组固定常量,本质是一个继承自 java.lang.Enum 的类,用于定义固定常量集合,类型安全、可扩展、线程安全,常用于状态表示和单例模式实现。

特性说明
默认继承继承 Enum
构造器默认 private
可以定义方法✅
可以定义成员变量✅
可以实现接口✅
不能继承类❌
public enum Color {
    RED,
    GREEN,
    BLUE
}

枚举不仅可以定义常量,还可以像普通类一样定义成员变量、构造器和方法;其构造器默认是 private,用于在创建每个枚举常量时初始化属性,并且枚举常量会在类加载时一次性创建完成,因此枚举实例是固定且唯一的。

enum Color {
    RED("红色"),
    GREEN("绿色"),
    BLUE("蓝色");

    private String desc;

    Color(String desc) {
        this.desc = desc;
    }

    public String getDesc() {
        return desc;
    }
}

枚举类自带一些常用方法,例如 values() 用于获取所有枚举值,valueOf(String) 根据名称获取对应枚举对象,name() 返回枚举常量名称,ordinal() 返回枚举在声明时的索引位置,这些方法方便对枚举进行遍历、查找和状态判断。

泛型

Java 的泛型采用的是类型擦除机制,也就是说泛型只在编译期存在,用于类型检查和保证类型安全,编译完成后会被擦除,运行时统一变成原始类型,例如 List 和 List 在运行时都会变成 List。正因为类型信息在运行时被擦除,所以泛型不能使用基本类型(如 List 不合法,只能使用包装类型 List,因为擦除后默认以 Object 作为上界,而基本类型不是对象),也不能在泛型中 new T()(因为运行时无法确定 T 的具体类型),同时也不能创建泛型数组或使用 instanceof 判断具体泛型类型,这些限制本质上都来源于类型擦除机制。

  1. 泛型类

class Box<T> {
    private T value;

    public void set(T value) {
        this.value = value;
    }

    public T get() {
        return value;
    }
}
  1. 泛型方法

public <T> T getFirst(T a) {
    return a;
}
  1. 泛型接口

public interface Box<T> {
    T get();
}
  1. 泛型通配符

写法名称含义典型场景
<?>无界通配符任意类型不关心具体类型
<? extends T>上界通配符T 及其子类生产数据(Producer)
<? super T>下界通配符T 及其父类消费数据(Consumer)

新特性

Record

record 是在 Java 14 引入(preview),并在 Java 16 正式成为标准特性,其核心目标是为“纯数据载体类”提供更简洁、不可变的语法。

public record Person(String name, int age) {}

编译器自动生成:

record 天生是不可变的,所有字段默认都是 private final,并且不会生成 setter 方法。

record 不能继承其他类(它默认继承 java.lang.Record),但可以实现接口。

record 可以自定义构造器,并且可以定义方法:

public record Person(String name, int age) {
    public Person {
        if (age < 0) {
            throw new IllegalArgumentException("Age cannot be negative");
        }
    }
    public boolean isAdult() {
        return age >= 18;
    }
}

Sealed Class

sealed class 是 Java 17 引入的特性,用于限制类的继承范围,只允许指定的子类进行扩展,从而实现受控继承。在Java 17之前,如果你想限制继承,只能用 final,但 final 会完全禁止继承,没办法“只允许特定子类”。

// 只有 Circle 和 Rectangle 可以继承 Shape
public sealed class Shape
    permits Circle, Rectangle {
}

// 被允许继承的类,必须声明为:final/sealed/non-sealed
public final class Circle extends Shape {
}

不仅 class 可以 sealed,interface 也可以:

public sealed interface Result
    permits Success, Failure {
}