02-Java面向对象编程
侧边栏壁纸
  • 累计撰写 40 篇文章
  • 累计收到 1 条评论

02-Java面向对象编程

ASN__
2026-06-22 / 0 评论 / 8 阅读 / 正在检测是否收录...

2.1 类与对象

核心概念

类是对象的模板,对象是类的实例。类定义了对象的属性(成员变量)和行为(成员方法),而对象是类的具体化表现。

定义语法

// 定义类
[访问修饰符] class 类名 {
    // 成员变量(属性)
    数据类型 变量名;

    // 成员方法(行为)
    返回类型 方法名(参数列表) {
        // 方法体
    }
}

实例化语法

类名 对象名 = new 类名();

完整示例

// 定义一个学生类
public class Student {
    // 成员变量
    String name;
    int age;
    double score;

    // 成员方法
    public void study() {
        System.out.println(name + " 正在学习");
    }

    public void introduce() {
        System.out.println("我叫 " + name + ",今年 " + age + " 岁,成绩是 " + score);
    }
}

// 使用类创建对象
public class Main {
    public static void main(String[] args) {
        // 实例化:创建对象
        Student s1 = new Student();
        Student s2 = new Student();

        // 为对象赋值
        s1.name = "张三";
        s1.age = 18;
        s1.score = 92.5;

        s2.name = "李四";
        s2.age = 19;
        s2.score = 88.0;

        // 调用对象方法
        s1.study();      // 输出:张三 正在学习
        s1.introduce();  // 输出:我叫 张三,今年 18 岁,成绩是 92.5
        s2.introduce();  // 输出:我叫 李四,今年 19 岁,成绩是 88.0
    }
}

内存分析

  • 栈内存:存储局部变量和对象引用(引用变量名)。
  • 堆内存:存储 new 出来的对象实体(成员变量值)。
  • 元空间:静态变量存储在堆中,类元数据(方法、字段描述等)存储在元空间
Student s1 = new Student();  // s1 在栈中,new Student() 在堆中

注意事项

  1. 一个 Java 文件中可以有多个类,但最多只有一个 public 修饰的类,且文件名必须与 public 类名一致。
  2. 对象的默认值:数值类型为 0 / 0.0booleanfalse,引用类型为 null
  3. 使用 new 关键字的过程:加载类 → 在堆中分配内存 → 初始化默认值 → 执行构造方法 → 返回引用地址。

2.2 方法

定义语法

[访问修饰符] [static] [final] 返回值类型 方法名(参数类型 参数名, ...) {
    // 方法体
    [return 返回值;]
}

完整示例

public class Calculator {

    // 实例方法:需要对象调用
    public int add(int a, int b) {
        return a + b;
    }

    // 静态方法:通过类名直接调用
    public static int subtract(int a, int b) {
        return a - b;
    }

    // 无返回值方法
    public void printResult(String operation, int result) {
        System.out.println(operation + " 结果:" + result);
    }
}

// 调用示例
public class Main {
    public static void main(String[] args) {
        // 静态方法:类名调用
        int diff = Calculator.subtract(10, 3);

        // 实例方法:对象调用
        Calculator calc = new Calculator();
        int sum = calc.add(10, 3);
        calc.printResult("加法", sum);
    }
}

静态方法 vs 实例方法对比

对比维度静态方法 (static)实例方法 (非static)
修饰符使用 static 修饰不使用 static
调用方式类名直接调用 ClassName.method()必须通过对象调用 obj.method()
内存位置方法区(就是静态的代码方法而已)元空间(就是静态的代码方法而已)
访问实例成员不能直接访问实例变量和实例方法可以访问所有成员(静态 + 实例)
访问静态成员可以访问(推荐用类名访问)可以访问
this / super不能使用可以使用
应用场景工具方法(如 Math.pow())、独立功能与对象状态相关的方法

参数传递详解

Java 中所有参数传递都是值传递,但理解基本类型和引用类型的区别至关重要。

基本类型传递(传递副本,不改变原值)

public class ParamTest {
    public static void changeValue(int x) {
        x = 100;  // 修改的是副本,不影响原值
        System.out.println("方法内 x = " + x);
    }

    public static void main(String[] args) {
        int a = 10;
        changeValue(a);
        System.out.println("方法外 a = " + a);  // 输出 10,原值不变
    }
}

引用类型传递(传递引用副本,可修改对象内容但不能改变引用指向)

public class ParamTest {
    public static void modifyObject(StringBuilder sb) {
        sb.append(" World");  // 可以修改对象内容
        System.out.println("方法内 sb = " + sb);
    }

    public static void changeReference(StringBuilder sb) {
        sb = new StringBuilder("New Object");  // 不能改变引用指向
        System.out.println("方法内 sb = " + sb);
    }

    public static void main(String[] args) {
        StringBuilder sb = new StringBuilder("Hello");
        modifyObject(sb);
        System.out.println("方法外 sb = " + sb);  // 输出 Hello World,内容已改变

        changeReference(sb);
        System.out.println("方法外 sb = " + sb);  // 仍输出 Hello World,引用未改变
    }
}

可变参数(varargs)

// 可变参数:参数类型... 参数名(本质是数组)
public static int sum(int... numbers) {
    int total = 0;
    for (int n : numbers) {
        total += n;
    }
    return total;
}

// 调用
int result = sum(1, 2, 3, 4, 5);  // result = 15
int result2 = sum();              // result2 = 0
注意:可变参数必须是参数列表中的最后一个参数,一个方法最多只能有一个可变参数。

方法设计原则

原则说明示例
单一职责每个方法只做一件事,职责明确calculateTotal() 不应同时打印和计算
命名清晰方法名使用动词开头,见名知义getUserById() 优于 get()
参数不超过5个参数过多应封装为对象使用 DTO / POJO 封装多参数
方法体不超过50行保持方法简短易读超出则拆分子方法
无副作用尽量避免修改传入参数返回新值而非修改参数

方法命名规范

  • 获取值getXxx() / findXxx() / queryXxx()
  • 设置值setXxx() / updateXxx()
  • 判断isXxx() / hasXxx() / canXxx()
  • 转换toXxx() / asXxx()
  • 创建createXxx() / buildXxx() / newXxx()
  • 处理processXxx() / handleXxx() / executeXxx()

2.3 构造方法

核心概念

构造方法是一种特殊的方法,用于在创建对象时初始化对象的状态。

构造方法特征

  • 方法名必须与类名完全相同(包括大小写)
  • 没有返回类型,连 void 也不能写
  • 使用 new 关键字自动调用,不能像普通方法一样显式调用
  • 每个类至少有一个构造方法

基本用法

public class Person {
    private String name;
    private int age;

    // 无参构造方法(默认构造方法)
    public Person() {
        System.out.println("Person 无参构造被调用");
    }

    // 有参构造方法
    public Person(String name, int age) {
        this.name = name;
        this.age = age;
        System.out.println("Person 有参构造被调用,name = " + name);
    }

    // 多个重载的构造方法
    public Person(String name) {
        this.name = name;
        this.age = 0;  // 默认值
    }
}

重要规则

一旦手动定义了有参构造方法,编译器将不再自动提供默认的无参构造方法。如果需要无参构造,必须显式定义。
public class Student {
    private String name;

    // 只定义了有参构造
    public Student(String name) {
        this.name = name;
    }
}

// 以下代码将编译错误!
// Student s = new Student();  // 错误:无参构造不存在

链式调用 this()

this() 用于在一个构造方法中调用本类的另一个构造方法,实现代码复用。

public class Employee {
    private String name;
    private int age;
    private String department;

    // 构造方法1:全参
    public Employee(String name, int age, String department) {
        this.name = name;
        this.age = age;
        this.department = department;
    }

    // 构造方法2:只有姓名和年龄
    public Employee(String name, int age) {
        this(name, age, "未分配");  // 调用构造方法1
    }

    // 构造方法3:只有姓名
    public Employee(String name) {
        this(name, 0, "未分配");  // 调用构造方法1
    }

    // 构造方法4:无参
    public Employee() {
        this("未知", 0, "未分配");  // 调用构造方法1
    }
}

this() 使用规则

  1. this() 必须是构造方法的第一条语句
  2. 一个构造方法中最多只能出现一次 this() 调用。
  3. 不能形成循环调用,否则编译错误。
// 错误示例:循环调用
public class A {
    public A() {
        this(10);  // 调用有参构造
    }
    public A(int x) {
        this();    // 调用无参构造 → 形成循环,编译错误!
    }
}

Builder 模式

当类的属性过多时,构造方法的参数列表会变得冗长且难以阅读。Builder 模式是解决此问题的优雅方案。

public class Computer {
    // 必选参数
    private final String cpu;
    private final String ram;

    // 可选参数
    private final String gpu;
    private final String storage;
    private final String os;

    // 私有构造方法,仅 Builder 可调用
    private Computer(Builder builder) {
        this.cpu = builder.cpu;
        this.ram = builder.ram;
        this.gpu = builder.gpu;
        this.storage = builder.storage;
        this.os = builder.os;
    }

    // 静态内部 Builder 类
    public static class Builder {
        // 必选参数
        private final String cpu;
        private final String ram;

        // 可选参数(默认值)
        private String gpu = "集成显卡";
        private String storage = "256GB SSD";
        private String os = "Windows 11";

        // Builder 构造方法:必须提供必选参数
        public Builder(String cpu, String ram) {
            this.cpu = cpu;
            this.ram = ram;
        }

        public Builder gpu(String gpu) {
            this.gpu = gpu;
            return this;
        }

        public Builder storage(String storage) {
            this.storage = storage;
            return this;
        }

        public Builder os(String os) {
            this.os = os;
            return this;
        }

        public Computer build() {
            return new Computer(this);
        }
    }

    @Override
    public String toString() {
        return "Computer {cpu=" + cpu + ", ram=" + ram +
               ", gpu=" + gpu + ", storage=" + storage + ", os=" + os + "}";
    }
}

// 使用 Builder 模式创建对象
public class Main {
    public static void main(String[] args) {
        Computer pc1 = new Computer.Builder("Intel i7", "16GB")
                .build();

        Computer pc2 = new Computer.Builder("AMD Ryzen 9", "32GB")
                .gpu("NVIDIA RTX 4080")
                .storage("1TB NVMe SSD")
                .os("Linux")
                .build();

        System.out.println(pc1);
        System.out.println(pc2);
    }
}

构造方法设计原则

原则说明
参数验证在构造方法中对参数进行合法性校验,确保对象创建后处于有效状态
链式调用利用 this() 减少重复代码,保持构造逻辑集中
防御性复制对于可变引用类型参数,创建防御性副本,防止外部修改影响内部状态
保持简洁构造方法应只做初始化工作,避免复杂业务逻辑
不调用可重写方法构造方法中调用可被重写的方法会导致不可预期的行为(子类可能尚未初始化完毕)
// 参数验证 + 防御性复制示例
public class Order {
    private final List<String> items;
    private final Date createTime;

    public Order(List<String> items) {
        // 参数验证
        if (items == null || items.isEmpty()) {
            throw new IllegalArgumentException("订单项不能为空");
        }
        // 防御性复制
        this.items = new ArrayList<>(items);
        this.createTime = new Date();
    }

    public List<String> getItems() {
        // 返回防御性副本
        return new ArrayList<>(items);
    }
}

2.4 访问修饰符

四种访问级别

Java 提供四种访问修饰符来控制类、成员变量、成员方法和构造方法的可见性。

修饰符同类同包子类(不同包)所有类
private可访问不可访问不可访问不可访问
default(无修饰符)可访问可访问不可访问不可访问
protected可访问可访问可访问不可访问
public可访问可访问可访问可访问

代码示例

package com.example.base;

public class Parent {
    private   int privateField   = 1;   // 仅本类内访问
              int defaultField   = 2;   // 同包访问(default)
    protected int protectedField = 3;   // 同包 + 子类访问
    public    int publicField    = 4;   // 所有类访问

    public void testAccess() {
        System.out.println(privateField);   // 可访问(同类内)
        System.out.println(defaultField);   // 可访问
        System.out.println(protectedField); // 可访问
        System.out.println(publicField);    // 可访问
    }
}
package com.example.base;

public class SamePackageClass {
    public void testAccess() {
        Parent p = new Parent();
        // System.out.println(p.privateField);   // 编译错误:不同类
        System.out.println(p.defaultField);      // 可访问(同包)
        System.out.println(p.protectedField);    // 可访问(同包)
        System.out.println(p.publicField);       // 可访问
    }
}
package com.example.other;

import com.example.base.Parent;

public class Child extends Parent {
    public void testAccess() {
        // System.out.println(privateField);    // 编译错误
        // System.out.println(defaultField);    // 编译错误:不同包
        System.out.println(protectedField);     // 可访问(子类)
        System.out.println(publicField);        // 可访问
    }
}
package com.example.other;

import com.example.base.Parent;

public class UnrelatedClass {
    public void testAccess() {
        Parent p = new Parent();
        // System.out.println(p.privateField);  // 编译错误
        // System.out.println(p.defaultField);  // 编译错误
        // System.out.println(p.protectedField);// 编译错误:不同包非子类
        System.out.println(p.publicField);      // 可访问
    }
}

访问修饰符可修饰的元素

修饰的元素privatedefaultprotectedpublic
外部类不支持支持不支持支持
内部类支持支持支持支持
成员变量支持支持支持支持
成员方法支持支持支持支持
构造方法支持支持支持支持

设计原则

  1. 封装核心:隐藏内部实现细节,只暴露必要的公共接口。
  2. 最小权限原则:能用 private 就不用 default,能用 default 就不用 protected,能用 protected 就不用 public
  3. 字段私有:所有成员变量应使用 private 修饰,通过 getter/setter 方法访问。
  4. 常量公开:公共常量使用 public static final
  5. 内部辅助方法:仅供类内部使用的方法使用 private
// 良好的封装示例
public class BankAccount {
    private String accountNumber;  // 私有的内部数据
    private double balance;

    // 公共的访问接口
    public String getAccountNumber() {
        return accountNumber;
    }

    public double getBalance() {
        return balance;
    }

    // 存款:公共方法,但内部有验证
    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("存款金额必须大于0");
        }
        balance += amount;
    }

    // 内部使用的私有辅助方法
    private void validateAmount(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("金额无效");
        }
    }
}

2.5 this 关键字

核心概念

this 是 Java 中的关键字,表示对当前对象的引用。每个实例方法中都有一个隐含的 this 引用指向调用该方法的对象。

三大用途

1. 引用成员变量(区分同名的局部变量和成员变量)

当方法参数名与成员变量名相同时,必须使用 this 来区分。

public class Person {
    private String name;
    private int age;

    // 必须使用 this 区分
    public Person(String name, int age) {
        this.name = name;   // this.name 是成员变量,name 是参数
        this.age = age;     // this.age 是成员变量,age 是参数
    }

    public void setName(String name) {
        this.name = name;   // 参数与成员变量同名时必须使用 this
    }

    // 不同名时可以省略 this(但不推荐,降低可读性)
    public void setAge(int newAge) {
        age = newAge;  // 可以省略 this
        // this.age = newAge; // 推荐显式使用 this
    }
}

2. 调用本类的其他构造方法 this()

public class Student {
    private String name;
    private int age;
    private String grade;

    public Student() {
        this("未知", 0, "未分配");  // 调用三参构造方法
    }

    public Student(String name) {
        this(name, 0, "未分配");    // 调用三参构造方法
    }

    public Student(String name, int age) {
        this(name, age, "未分配");  // 调用三参构造方法
    }

    // 最终的构造方法(入口)
    public Student(String name, int age, String grade) {
        this.name = name;
        this.age = age;
        this.grade = grade;
    }
}

3. 方法链式调用(Fluent API)

返回 this 可以实现链式调用,使代码更简洁流畅。

public class QueryBuilder {
    private String table;
    private List<String> columns = new ArrayList<>();
    private String whereClause;
    private String orderBy;

    public QueryBuilder select(String... cols) {
        columns.addAll(Arrays.asList(cols));
        return this;  // 返回当前对象,支持链式调用
    }

    public QueryBuilder from(String table) {
        this.table = table;
        return this;
    }

    public QueryBuilder where(String condition) {
        this.whereClause = condition;
        return this;
    }

    public QueryBuilder orderBy(String field) {
        this.orderBy = field;
        return this;
    }

    public String build() {
        // 构建 SQL 语句...
        return "SELECT " + String.join(", ", columns) +
               " FROM " + table +
               (whereClause != null ? " WHERE " + whereClause : "") +
               (orderBy != null ? " ORDER BY " + orderBy : "");
    }

    // 使用示例
    public static void main(String[] args) {
        QueryBuilder qb = new QueryBuilder();
        String sql = qb.select("id", "name", "age")
                       .from("users")
                       .where("age > 18")
                       .orderBy("name")
                       .build();
        System.out.println(sql);
        // 输出:SELECT id, name, age FROM users WHERE age > 18 ORDER BY name
    }
}

this 使用限制

限制说明示例
静态方法中不能使用静态方法属于类,没有对象上下文,不存在 thisstatic void method() { this.xxx; }
静态代码块中不能使用静态代码块在类加载时执行,此时没有对象实例static { this.xxx; }
this() 必须是构造方法第一条语句构造方法调用必须在最前面public A() { this("default"); }

使用场景总结

场景是否使用 this说明
成员变量与局部变量同名必须使用this.name = name;
成员变量与局部变量不同名推荐使用提高可读性,明确意图
构造方法间相互调用必须使用 this()第一条语句
链式调用必须使用 return thisFluent API 模式
将当前对象作为参数传递使用 thismethod(this)
静态上下文中禁止使用编译错误

2.6 static 关键字

核心概念

static 表示"静态的"、"属于类的",被 static 修饰的成员不属于任何一个对象实例,而是属于类本身。

静态变量(类变量)

静态变量属于类,被该类的所有对象共享,其生命周期与类的生命周期一致。

public class Counter {
    // 静态变量:所有 Counter 对象共享
    private static int totalCount = 0;

    // 实例变量:每个对象独立拥有
    private int instanceId;

    public Counter() {
        totalCount++;           // 每次创建对象,计数 +1
        instanceId = totalCount; // 将总数作为当前ID
    }

    public static int getTotalCount() {
        return totalCount;
    }

    public int getInstanceId() {
        return instanceId;
    }

    public static void main(String[] args) {
        Counter c1 = new Counter();
        Counter c2 = new Counter();
        Counter c3 = new Counter();

        System.out.println(c1.getInstanceId());     // 输出: 1
        System.out.println(c2.getInstanceId());     // 输出: 2
        System.out.println(c3.getInstanceId());     // 输出: 3
        System.out.println(Counter.getTotalCount());// 输出: 3(所有对象共享)

        // 三个对象共享同一个 totalCount
        System.out.println(c1.getTotalCount());     // 输出: 3
        System.out.println(c2.getTotalCount());     // 输出: 3
    }
}

静态变量的内存与生命周期

阶段说明
加载类被 JVM 加载时,静态变量在方法区分配空间并初始化
使用可通过 类名.变量名对象.变量名 访问(推荐类名方式)
卸载类被卸载时,静态变量占用的内存释放

静态方法(重点)

类名.静态变量变量引用在(附着于Class对象)类加载时初始化,类卸载(或JVM退出)时销毁
类名.静态方法()代码在元空间,执行时的临时数据在类加载后即可调用,调用结束栈帧即销毁,随类卸载而消失

静态方法属于类,只能直接访问静态成员(静态变量和静态方法),不能直接访问实例成员。

public class MathUtils {

    // 静态常量
    public static final double PI = 3.1415926535;

    // 静态方法:不依赖对象状态
    public static double squareArea(double side) {
        return side * side;
    }

    public static double circleArea(double radius) {
        return PI * radius * radius;
    }

    // 实例方法:可以访问静态成员
    public void printPI() {
        System.out.println("PI = " + PI);  // 实例方法访问静态变量,OK
        System.out.println("面积 = " + squareArea(5));  // OK
    }

    // 错误示例:静态方法访问实例成员
    // public static void errorMethod() {
    //     printPI();  // 编译错误!静态方法不能调用实例方法
    // }
}

// 使用
public class Main {
    public static void main(String[] args) {
        // 直接通过类名调用
        double area = MathUtils.circleArea(5.0);
        System.out.println("圆面积:" + area);
    }
}

静态方法不能被重写,只能被隐藏

class Parent {
    public static void staticMethod() {
        System.out.println("Parent 静态方法");
    }

    public void instanceMethod() {
        System.out.println("Parent 实例方法");
    }
}

class Child extends Parent {
    // 这不是重写(Override),而是隐藏(Hide)
    public static void staticMethod() {
        System.out.println("Child 静态方法");
    }

    // 这是真正的重写
    @Override
    public void instanceMethod() {
        System.out.println("Child 实例方法");
    }
}

public class Test {
    public static void main(String[] args) {
        Parent p = new Child();

        p.staticMethod();     // 输出:Parent 静态方法(基于引用类型,不具有多态性)
        p.instanceMethod();   // 输出:Child 实例方法(基于实际对象类型,多态性)

        Child.staticMethod(); // 输出:Child 静态方法(调用子类的隐藏版本)
    }
}

静态代码块

静态代码块在类加载时执行,且只执行一次,常用于初始化静态资源。

public class DatabaseConfig {
    // 静态变量
    private static String url;
    private static String username;
    private static String password;

    // 静态代码块:类加载时只执行一次
    static {
        System.out.println("正在加载数据库配置...");
        // 模拟从配置文件加载
        url = "jdbc:mysql://localhost:3306/mydb";
        username = "root";
        password = "123456";
        System.out.println("数据库配置加载完成");
    }

    // 静态代码块可以有多个,按声明顺序执行
    static {
        System.out.println("第二个静态代码块执行");
    }

    public static void connect() {
        System.out.println("连接数据库:" + url);
    }

    public static void main(String[] args) {
        DatabaseConfig.connect();
        // 输出:
        // 正在加载数据库配置...
        // 数据库配置加载完成
        // 第二个静态代码块执行
        // 连接数据库:jdbc:mysql://localhost:3306/mydb
    }
}

初始化顺序总结

当类被加载和实例化时,各部分的初始化顺序如下:

1. 父类静态变量和静态代码块(按声明顺序)
2. 子类静态变量和静态代码块(按声明顺序)
3. 父类实例变量和实例代码块(按声明顺序)
4. 父类构造方法
5. 子类实例变量和实例代码块(按声明顺序)
6. 子类构造方法
class Parent {
    static { System.out.println("1. Parent 静态代码块"); }
    { System.out.println("3. Parent 实例代码块"); }
    Parent() { System.out.println("4. Parent 构造方法"); }
}

class Child extends Parent {
    static { System.out.println("2. Child 静态代码块"); }
    { System.out.println("5. Child 实例代码块"); }
    Child() { System.out.println("6. Child 构造方法"); }

    public static void main(String[] args) {
        new Child();
    }
}

2.7 继承

核心概念

继承是面向对象编程的核心特性之一,允许一个类(子类)继承另一个类(父类)的属性和方法。

基本语法

[访问修饰符] class 子类名 extends 父类名 {
    // 子类特有的属性和方法
}

基本示例

// 父类(基类 / 超类)
public class Animal {
    protected String name;
    protected int age;

    public Animal(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public void eat() {
        System.out.println(name + " 正在吃东西");
    }

    public void sleep() {
        System.out.println(name + " 正在睡觉");
    }
}

// 子类(派生类)
public class Dog extends Animal {
    private String breed;

    public Dog(String name, int age, String breed) {
        super(name, age);      // 调用父类构造方法
        this.breed = breed;
    }

    // 子类特有方法
    public void bark() {
        System.out.println(name + " 汪汪叫!");
    }

    // 重写父类方法
    @Override
    public void eat() {
        System.out.println(name + "(" + breed + ")正在吃狗粮");
    }
}

// Cat 子类
public class Cat extends Animal {
    public Cat(String name, int age) {
        super(name, age);
    }

    @Override
    public void eat() {
        System.out.println(name + " 正在吃鱼");
    }

    public void meow() {
        System.out.println(name + " 喵喵叫!");
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog("旺财", 3, "金毛");
        Cat cat = new Cat("咪咪", 2);

        dog.eat();    // 输出:旺财(金毛)正在吃狗粮
        dog.bark();   // 输出:旺财 汪汪叫!
        dog.sleep();  // 输出:旺财 正在睡觉(继承自 Animal)

        cat.eat();    // 输出:咪咪 正在吃鱼
        cat.meow();   // 输出:咪咪 喵喵叫!
        cat.sleep();  // 输出:咪咪 正在睡觉(继承自 Animal)
    }
}

Java 继承规则

规则说明
单继承一个类只能有一个直接父类(extends 后只能跟一个类名)
多层继承可以形成继承链:A extends B, B extends C(层次通常不超过 3-4 层)
隐式继承 Object所有类如果没有显式继承,则隐式继承 java.lang.Object
不继承构造方法子类不继承父类的构造方法,但可以通过 super() 调用
继承所有非私有成员子类继承父类的 publicprotecteddefault 成员(同包情况下)

is-a 关系判断

继承表示 "is-a"(是一个)关系。判断是否适合使用继承的关键标准:

// 好的继承:Dog IS-A Animal — 合理
class Dog extends Animal   // 狗是一种动物

// 好的继承:Car IS-A Vehicle — 合理
class Car extends Vehicle  // 汽车是一种交通工具

// 不好的继承:为了复用代码而继承
class Stack extends ArrayList  // 栈不是一个列表,违反了 is-a
// 应该使用组合:
class Stack {
    private ArrayList list = new ArrayList(); // 组合优于继承
}

继承的设计原则

推荐做法

// 1. 父类设计考虑扩展性,使用 protected 提供子类访问
public class BaseService {
    protected Logger logger = LoggerFactory.getLogger(getClass());

    // 模板方法模式:定义框架,子类实现细节
    public final void execute() {
        beforeExecute();
        doExecute();
        afterExecute();
    }

    protected void beforeExecute()   // 钩子方法
    protected abstract void doExecute(); // 子类必须实现
    protected void afterExecute()    // 钩子方法
}

// 2. 使用 @Override 注解防止错误
class ChildService extends BaseService {
    @Override  // 编译器会检查是否正确重写,防止拼写错误
    protected void doExecute() {
        logger.info("执行业务逻辑");
    }
}

// 3. 子类构造调用 super()
class Child extends Parent {
    public Child(String name) {
        super(name);  // 必须调用父类构造方法,确保父类正确初始化
    }
}

避免的做法

避免的做法原因正确做法
为代码复用强行继承违反 is-a 原则,导致耦合使用组合 + 委托
在构造方法中调用可重写方法子类方法可能在父类未完全初始化时被调用构造方法只调用 private / final / static 方法
随意修改父类违反开闭原则通过扩展(新增子类)而非修改父类来增加功能
// 违反开闭原则的反例
// 不应该为新增功能而修改稳定的父类

// 正确做法:使用组合替代不当的继承
// 需要复用某功能,但不符合 is-a 关系时
class ReportGenerator {
    // 组合:持有需要的对象
    private DataFetcher dataFetcher;
    private Formatter formatter;

    public ReportGenerator(DataFetcher dataFetcher, Formatter formatter) {
        this.dataFetcher = dataFetcher;
        this.formatter = formatter;
    }
}

2.8 super 关键字

核心概念

super 是 Java 中指向直接父类对象引用的关键字,用于在子类中访问父类的成员(构造方法、方法、属性)。

三大用途

1. 调用父类构造方法 super()

class Parent {
    protected String name;

    // 父类有参构造
    public Parent(String name) {
        this.name = name;
        System.out.println("Parent 构造:name = " + name);
    }
}

class Child extends Parent {
    private int age;

    public Child(String name, int age) {
        super(name);   // 必须显式调用父类有参构造
        this.age = age;
        System.out.println("Child 构造:age = " + age);
    }
}

public class Main {
    public static void main(String[] args) {
        Child c = new Child("小明", 10);
        // 输出:
        // Parent 构造:name = 小明
        // Child 构造:age = 10
    }
}

2. 访问父类被重写的方法

class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override
    public void makeSound() {
        super.makeSound();   // 调用父类的方法
        System.out.println("狗:汪汪!");  // 扩展父类方法
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.makeSound();
        // 输出:
        // 动物发出声音
        // 狗:汪汪!
    }
}

3. 访问父类被隐藏的属性

class Parent {
    protected String className = "Parent";
    protected int value = 100;
}

class Child extends Parent {
    protected String className = "Child";  // 隐藏父类同名属性

    public void printInfo() {
        System.out.println("子类的 className: " + this.className);   // Child
        System.out.println("父类的 className: " + super.className);  // Parent
        System.out.println("value: " + super.value);                 // 100(继承的)
    }
}

super 使用规则

规则说明
super() 必须是第一条语句super() 调用必须放在构造方法体的第一行
不显式调用自动插入如果子类构造方法没有显式调用 super(),编译器会自动插入父类的无参构造 super()
父类无无参构造如果父类没有无参构造方法,子类必须显式通过 super(参数) 调用父类的有参构造,否则编译错误
不能在静态方法中使用super 是对象级别的引用,静态上下文中不存在
// 错误示例:父类无无参构造但子类未显式调用
class Parent {
    public Parent(int x)   // 只有有参构造
}

// class Child extends Parent {
//     // 编译错误!隐式的 super() 找不到 Parent 的无参构造
// }

// 正确示例
class Child extends Parent {
    public Child(int x) {
        super(x);  // 必须显式调用
    }
}

super vs this 对比

对比维度superthis
访问范围访问父类的成员(方法、属性)访问当前类的成员(方法、属性)
调用构造方法super() 调用父类的构造方法this() 调用本类的其他构造方法
本质对直接父类的引用对当前对象的引用
内存指向指向父类部分的内存区域指向当前对象整体
静态方法中不能使用不能使用
第一条语句限制super() 必须是构造方法第一条语句this() 必须是构造方法第一条语句
能否共存不能与 this() 同时作为第一条语句不能与 super() 同时作为第一条语句

2.9 final 关键字

核心概念

final 表示"最终的、不可改变的",可以修饰类、方法和变量。

final 类:不能被继承

当一个类被 final 修饰时,它不能被任何类继承。

// final 类:不可被继承
public final class StringUtils {
    public static boolean isEmpty(String str) {
        return str == null || str.length() == 0;
    }
}

// 以下代码编译错误!
// class ExtendedStringUtils extends StringUtils   // 编译错误!

典型 final 类java.lang.Stringjava.lang.Mathjava.lang.Integer 等包装类。

final 方法:不能被重写,但能被继承调用

class Parent {
    // final 方法:子类不能重写
    public final void criticalMethod() {
        System.out.println("核心方法,不能被子类改变");
    }

    // 普通方法:可以被子类重写
    public void normalMethod() {
        System.out.println("普通方法");
    }
}

class Child extends Parent {
    // @Override
    // public void criticalMethod()   // 编译错误!final 方法不能重写

    @Override
    public void normalMethod() {
        System.out.println("子类重写的普通方法");
    }

    // 可以继承并调用父类的 final 方法
    public void useParentMethod() {
        criticalMethod();  // 调用继承的 final 方法,OK
    }
}

final 变量:赋值后不能修改

final 变量根据声明位置的不同,初始化时机也有所不同。

public class FinalVariableDemo {

    // 1. 静态 final 变量(类常量):声明时或静态代码块中初始化
    public static final double PI = 3.14159;

    public static final String APP_NAME;
    static {
        APP_NAME = "MyApplication";  // 静态代码块中初始化
    }

    // 2. 实例 final 变量:声明时或构造方法中初始化
    private final String id;         // 声明时未赋值
    private final String name = "默认名称";  // 声明时赋值

    public FinalVariableDemo(String id) {
        this.id = id;  // 构造方法中初始化
    }

    // 3. 局部 final 变量:使用前初始化一次
    public void process() {
        final int localValue;
        localValue = 100;     // 首次赋值,OK
        // localValue = 200;  // 编译错误!不能二次赋值
    }

    // final 引用类型:引用不可变,但对象内容可变
    public void finalReference() {
        final StringBuilder sb = new StringBuilder("Hello");
        sb.append(" World");  // OK:可以修改对象内容
        // sb = new StringBuilder("New"); // 编译错误!不能改变引用指向
    }
}

final 变量初始化规则总结

final 变量类型必须初始化的时机示例
静态 final 变量声明时 或 静态代码块中static final int X = 10;static { X = 10; }
实例 final 变量声明时 或 每个构造方法中final int x = 10; 或构造方法中 this.x = 10;
局部 final 变量使用前赋值一次即可final int x; x = 10; use(x);

final 方法参数

public class FinalParameter {
    // final 参数:方法调用时赋值,方法体内不可修改
    public void printName(final String name) {
        // name = "新的名字";  // 编译错误!不能修改 final 参数
        System.out.println("姓名:" + name);
    }

    // final 参数在匿名内部类中很有用
    public void doSomething(final String message) {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                System.out.println(message);  // 匿名内部类可以访问 final 变量
            }
        };
        new Thread(r).start();
    }
}

final 使用场景

场景说明示例
定义常量public static final 定义公共常量Math.PI
防止继承工具类、不可变类StringInteger
防止重写模板方法中的框架方法流程控制方法
安全发布确保对象在构造函数中完全初始化多线程环境下的不可变对象
匿名内部类匿名内部类访问的局部变量final 局部变量在 Lambda 中

2.10 方法重写与重载

方法重写(Override)

子类重新定义从父类继承的方法,提供自己的实现。

class Animal {
    public void makeSound() {
        System.out.println("动物发出声音");
    }
}

class Dog extends Animal {
    @Override   // 注解标记,编译器会检查是否正确重写
    public void makeSound() {
        System.out.println("狗汪汪叫");
    }
}

class Cat extends Animal {
    @Override
    public void makeSound() {
        System.out.println("猫喵喵叫");
    }
}

重写规则(五同两小一大)

规则说明示例
方法名相同子类方法名必须与父类完全一致makeSound() = makeSound()
参数相同参数列表(类型、数量、顺序)必须完全一致void eat(String food) = void eat(String food)
返回类型相同或子类型Java 5+ 支持协变返回类型父类返回 Animal,子类可返回 DogDog extends Animal
访问修饰符不能更严格只能扩大,不能缩小父类 protected → 子类 public(OK);父类 public → 子类 protected(NO)
不能抛出更宽泛异常只能缩小或相同父类抛 Exception → 子类可抛 IOException(更具体)
// 协变返回类型示例
class Animal {
    public Animal reproduce() { return new Animal(); }
}

class Dog extends Animal {
    @Override
    public Dog reproduce() { return new Dog(); }  // 返回子类型,OK
}

不能被重写的情况

类型原因
final 方法final 锁定,不可改变
static 方法属于类,不能重写(只能隐藏)
private 方法子类不可见,谈不上重写
构造方法不在继承范围内

方法重载(Overload)

在同一个类中,定义多个同名参数列表不同的方法。

public class Calculator {

    // 重载:参数数量不同
    public int add(int a, int b) {
        return a + b;
    }

    public int add(int a, int b, int c) {
        return a + b + c;
    }

    // 重载:参数类型不同
    public double add(double a, double b) {
        return a + b;
    }

    // 重载:参数顺序不同(类型不同)
    public String add(String prefix, int value) {
        return prefix + value;
    }

    public String add(int value, String suffix) {
        return value + suffix;
    }

    // 错误示例:只有返回类型不同,不是重载
    // public long add(int a, int b) { return a + b; }  // 编译错误!
}

重载的选择规则

编译器根据调用时传入的实参类型和数量来决定调用哪个重载方法:

public class OverloadResolution {
    public void test(int x) {
        System.out.println("int 版本:" + x);
    }

    public void test(double x) {
        System.out.println("double 版本:" + x);
    }

    public void test(String x) {
        System.out.println("String 版本:" + x);
    }

    public static void main(String[] args) {
        OverloadResolution demo = new OverloadResolution();
        demo.test(10);       // int 版本
        demo.test(10.5);     // double 版本
        demo.test("hello");  // String 版本

        // 自动类型提升
        demo.test('A');      // char → int,调用 int 版本
    }
}

重写(Override) vs 重载(Overload) 对比

对比维度重写 (Override)重载 (Overload)
发生位置父子类之间同一个类中
方法签名方法名 + 参数列表完全一致方法名相同,参数列表不同
返回类型相同或协变子类型可以不同
访问修饰符不能比父类更严格可以任意
异常不能比父类更宽泛可以任意
多态类型运行时多态(动态绑定)编译时多态(静态绑定)
注解使用 @Override 标注无需特殊注解
static 方法不能重写(只能隐藏)可以重载
final 方法不能重写可以重载
构造方法不能重写可以重载

2.11 多态

核心概念

多态(Polymorphism)是指同一个行为具有多个不同的表现形式。在 Java 中,多态主要通过父类引用指向子类对象来实现。

多态实现的三个必要条件

1. 继承关系(extends)
2. 方法重写(@Override)
3. 向上转型(父类引用指向子类对象)

基本示例

// 1. 定义父类
abstract class Shape {
    public abstract double getArea();

    public void describe() {
        System.out.println("这是一个图形,面积为:" + getArea());
    }
}

// 2. 子类重写方法
class Circle extends Shape {
    private double radius;

    public Circle(double radius) {
        this.radius = radius;
    }

    @Override
    public double getArea() {
        return Math.PI * radius * radius;
    }
}

class Rectangle extends Shape {
    private double width;
    private double height;

    public Rectangle(double width, double height) {
        this.width = width;
        this.height = height;
    }

    @Override
    public double getArea() {
        return width * height;
    }
}

// 3. 向上转型 + 多态调用
public class PolymorphismDemo {
    public static void main(String[] args) {
        // 向上转型:父类引用指向子类对象
        Shape s1 = new Circle(5.0);
        Shape s2 = new Rectangle(4.0, 6.0);

        // 多态调用:运行时根据实际对象类型调用对应方法
        s1.describe();  // 输出:这是一个图形,面积为:78.5398...
        s2.describe();  // 输出:这是一个图形,面积为:24.0

        // 使用数组或集合统一处理
        Shape[] shapes = {
            new Circle(3.0),
            new Rectangle(2.0, 5.0),
            new Circle(4.0)
        };

        double totalArea = 0;
        for (Shape s : shapes) {
            totalArea += s.getArea();  // 多态调用
        }
        System.out.println("总面积:" + totalArea);
    }
}

编译时多态 vs 运行时多态

类型别称实现机制决定时机典型场景
编译时多态静态多态方法重载(Overload)编译期确定同名不同参的方法
运行时多态动态多态方法重写(Override)+ 动态绑定运行期确定父类引用调用子类重写方法

向上转型(Upcasting)

向上转型是自动的、安全的。父类引用只能调用父类中定义的方法,不能调用子类特有的方法。

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

class Dog extends Animal {
    public void eat() { System.out.println("吃骨头"); }
    public void bark() { System.out.println("汪汪叫"); }
}

public class UpcastingDemo {
    public static void main(String[] args) {
        Animal a = new Dog();  // 向上转型(自动)
        a.eat();               // 输出:吃骨头(多态,调用 Dog 的 eat)

        // a.bark();           // 编译错误!Animal 引用看不到 Dog 特有方法
    }
}

向下转型(Downcasting)

向下转型需要显式强制转换,且必须先用 instanceof 检查类型,否则可能导致 ClassCastException

public class DowncastingDemo {
    public static void main(String[] args) {
        Animal a = new Dog();  // 实际是 Dog 对象

        // 向下转型:必须显式转换
        if (a instanceof Dog) {
            Dog d = (Dog) a;   // 安全的向下转型
            d.bark();          // 输出:汪汪叫
        }

        // 错误示例:不安全的向下转型
        Animal a2 = new Animal();
        // Dog d2 = (Dog) a2;  // 运行时抛出 ClassCastException!

        // 安全做法:始终先检查
        if (a2 instanceof Dog) {
            Dog d2 = (Dog) a2;
            d2.bark();
        } else {
            System.out.println("a2 不是 Dog 类型");
        }
    }
}

instanceof 关键字

instanceof 用于判断一个对象是否是指定类型(或其子类型)的实例。

public class InstanceofDemo {
    public static void main(String[] args) {
        Object obj1 = "Hello World";
        Object obj2 = 42;
        Object obj3 = new ArrayList<>();
        Object obj4 = null;

        System.out.println(obj1 instanceof String);    // true
        System.out.println(obj1 instanceof Object);    // true(String 是 Object 的子类)
        System.out.println(obj2 instanceof Integer);   // true
        System.out.println(obj3 instanceof List);      // true(ArrayList 实现了 List)
        System.out.println(obj4 instanceof String);    // false(null 不是任何类型的实例)

        // 模式匹配(Java 16+)
        if (obj1 instanceof String s && s.length() > 0) {
            System.out.println("字符串长度为:" + s.length());  // 直接在条件中使用 s
        }
    }
}

多态的注意事项

注意点说明
静态方法不支持多态静态方法调用基于引用类型,不是实际对象类型
私有方法不能被重写因此不存在多态
构造方法不支持多态构造方法不是通过常规方法调用机制执行的
成员变量没有多态成员变量访问看引用类型,方法调用看实际对象类型
class Parent {
    public String name = "Parent";
    public static void staticMethod() { System.out.println("Parent static"); }
    public void instanceMethod() { System.out.println("Parent instance"); }
}

class Child extends Parent {
    public String name = "Child";  // 隐藏父类变量
    public static void staticMethod() { System.out.println("Child static"); }
    @Override
    public void instanceMethod() { System.out.println("Child instance"); }
}

public class PolymorphismTrap {
    public static void main(String[] args) {
        Parent p = new Child();

        System.out.println(p.name);         // 输出:Parent(变量看引用类型)
        p.staticMethod();                   // 输出:Parent static(静态方法看引用类型)
        p.instanceMethod();                 // 输出:Child instance(实例方法看实际类型)
    }
}

2.12 封装

核心概念

封装(Encapsulation)是将数据(属性)和操作数据的方法绑定在一起,隐藏内部实现细节,只暴露必要的接口给外部使用。

封装的基本原则

public class BankAccount {
    // 1. 所有字段设为 private
    private String accountNumber;
    private String ownerName;
    private double balance;
    private String password;

    // 2. 提供公共的 getter/setter 方法
    public String getAccountNumber() {
        return accountNumber;
    }

    public String getOwnerName() {
        return ownerName;
    }

    public void setOwnerName(String ownerName) {
        if (ownerName == null || ownerName.trim().isEmpty()) {
            throw new IllegalArgumentException("所有者姓名不能为空");
        }
        this.ownerName = ownerName;
    }

    public double getBalance() {
        return balance;
    }

    // 3. 不直接暴露 setter,而是提供业务方法
    public void deposit(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("存款金额必须大于0");
        }
        this.balance += amount;
        logTransaction("存款", amount);
    }

    public void withdraw(double amount) {
        if (amount <= 0) {
            throw new IllegalArgumentException("取款金额必须大于0");
        }
        if (amount > this.balance) {
            throw new IllegalStateException("余额不足");
        }
        this.balance -= amount;
        logTransaction("取款", amount);
    }

    // 4. 内部辅助方法设为 private
    private void logTransaction(String type, double amount) {
        System.out.println("[交易记录] " + type + ": " + amount + " | 余额: " + balance);
    }
}

// 外部使用
public class Main {
    public static void main(String[] args) {
        BankAccount account = new BankAccount();
        // account.balance = 100000;  // 编译错误:balance 是 private
        // account.password = "123";  // 编译错误:password 是 private

        account.deposit(1000);   // 通过公共方法操作
        account.withdraw(300);
        System.out.println("余额:" + account.getBalance());  // 700.0
    }
}

封装的四个层次

访问控制逐级放松:

┌────────────────────────────────────────────┐
│  private     同类内                        │
│  ┌──────────────────────────────────────┐  │
│  │  default   同包内                     │  │
│  │  ┌────────────────────────────────┐  │  │
│  │  │  protected  同包 + 子类         │  │  │
│  │  │  ┌──────────────────────────┐  │  │  │
│  │  │  │  public   所有类均可访问   │  │  │  │
│  │  │  └──────────────────────────┘  │  │  │
│  │  └────────────────────────────────┘  │  │
│  └──────────────────────────────────────┘  │
└────────────────────────────────────────────┘

封装的好处

好处说明示例
隐藏实现细节外部只需知道"做什么",无需知道"怎么做"调用 deposit() 不知内部如何记账
数据验证在 setter 或业务方法中统一校验防止余额出现负数
灵活修改内部实现改变不影响外部调用者改用数据库存储,外部调用代码不变
提高安全性防止外部直接篡改内部数据密码字段不暴露 getter
降低耦合模块间通过接口通信便于单元测试和维护

封装最佳实践

public class Person {
    // 所有字段 private
    private String name;
    private int age;
    private String email;

    // 构造方法中进行参数验证
    public Person(String name, int age, String email) {
        setName(name);  // 复用验证逻辑
        setAge(age);
        setEmail(email);
    }

    // getter:只读属性
    public String getName() { return name; }
    public int getAge() { return age; }

    // setter:包含验证逻辑
    public void setName(String name) {
        if (name == null || name.trim().isEmpty()) {
            throw new IllegalArgumentException("姓名不能为空");
        }
        this.name = name;
    }

    public void setAge(int age) {
        if (age < 0 || age > 150) {
            throw new IllegalArgumentException("年龄必须在 0-150 之间");
        }
        this.age = age;
    }

    public void setEmail(String email) {
        if (email != null && !email.contains("@")) {
            throw new IllegalArgumentException("邮箱格式不正确");
        }
        this.email = email;
    }

    // 需要时才暴露 getter(最小化暴露面)
    // email 不提供 getter,只有需要发送邮件时才内部使用
}

2.13 抽象类与接口

抽象类(Abstract Class)

使用 abstract 关键字修饰的类。抽象类不能实例化,通常作为其他类的基类。

// 抽象类
public abstract class Animal {
    // 成员变量
    protected String name;

    // 构造方法(抽象类可以有构造方法)
    public Animal(String name) {
        this.name = name;
    }

    // 抽象方法:没有方法体,子类必须实现
    public abstract void makeSound();

    // 具体方法:有方法体,可以被子类继承和使用
    public void eat() {
        System.out.println(name + " 正在吃东西");
    }

    public void sleep() {
        System.out.println(name + " 正在睡觉");
    }
}

// 子类实现抽象类
public class Dog extends Animal {
    public Dog(String name) {
        super(name);
    }

    // 必须重写(实现)所有抽象方法
    @Override
    public void makeSound() {
        System.out.println(name + " 汪汪叫!");
    }
}

public class Cat extends Animal {
    public Cat(String name) {
        super(name);
    }

    @Override
    public void makeSound() {
        System.out.println(name + " 喵喵叫!");
    }
}

public class Main {
    public static void main(String[] args) {
        // Animal a = new Animal("xx");  // 编译错误!抽象类不能实例化

        Animal dog = new Dog("旺财");
        Animal cat = new Cat("咪咪");

        dog.makeSound();  // 旺财 汪汪叫!
        dog.eat();        // 旺财 正在吃东西

        cat.makeSound();  // 咪咪 喵喵叫!
        cat.eat();        // 咪咪 正在吃东西
    }
}

抽象类特点

特点说明
abstract 修饰必须使用 abstract 关键字声明
不能实例化new Animal() 编译错误
可有构造方法供子类通过 super() 调用
可有抽象方法没有方法体,abstract 修饰,子类必须实现
可有具体方法有方法体的普通方法
可有成员变量可以有实例变量和各种修饰符的变量
单继承一个类只能继承一个抽象类

接口(Interface)

接口是一种更纯粹的抽象类型,JDK 8+ 支持 defaultstatic 方法。

// 定义接口
public interface Flyable {
    // 常量:默认 public static final
    int MAX_SPEED = 1000;
    String TYPE = "飞行器";

    // 抽象方法:默认 public abstract
    void fly();
    void land();

    // Java 8+:default 方法(有默认实现)
    default void takeOff() {
        System.out.println("准备起飞...");
        checkEngine();
    }

    // Java 8+:static 方法
    static String getFlyableType() {
        return "这是一个可飞行类型";
    }

    // Java 9+:private 方法(供 default 方法内部复用)
    private void checkEngine() {
        System.out.println("检查引擎状态...");
    }
}

// 另一个接口
public interface Swimmable {
    void swim();

    default void dive() {
        System.out.println("潜入水中...");
    }
}

// 一个类可以实现多个接口
public class Duck implements Flyable, Swimmable {
    private String name;

    public Duck(String name) {
        this.name = name;
    }

    @Override
    public void fly() {
        System.out.println(name + " 在飞行");
    }

    @Override
    public void land() {
        System.out.println(name + " 降落在水面");
    }

    @Override
    public void swim() {
        System.out.println(name + " 在游泳");
    }
}

// 接口使用
public class Main {
    public static void main(String[] args) {
        Duck duck = new Duck("唐老鸭");

        duck.takeOff();  // 调用接口 default 方法
        duck.fly();
        duck.land();
        duck.swim();
        duck.dive();     // 调用接口 default 方法

        // 访问接口常量
        System.out.println("最大速度:" + Flyable.MAX_SPEED);

        // 调用接口 static 方法
        System.out.println(Flyable.getFlyableType());
    }
}

接口特点

特点说明
interface 声明使用 interface 关键字,非 class
不能实例化new Flyable() 编译错误
无构造方法接口没有构造方法
所有变量默认 public static final必须是常量
方法默认 public abstractJDK 8 前所有方法都是抽象方法
JDK 8+ default 方法有方法体的默认方法(可被重写)
JDK 8+ static 方法接口的静态工具方法
JDK 9+ private 方法接口内部的私有辅助方法
多实现一个类可以实现多个接口

接口的多实现和冲突解决

interface A {
    default void hello() {
        System.out.println("Hello from A");
    }
}

interface B {
    default void hello() {
        System.out.println("Hello from B");
    }
}

// 当多个接口有同名 default 方法时,必须重写解决冲突
class C implements A, B {
    @Override
    public void hello() {
        // 选择调用某一个接口的实现
        A.super.hello();  // 调用 A 的 hello
        B.super.hello();  // 调用 B 的 hello
        System.out.println("Hello from C");
    }
}

抽象类 vs 接口 对比

对比维度抽象类 (abstract class)接口 (interface)
关键字abstract classinterface
继承/实现单继承(extends多实现(implements
构造方法构造方法没有构造方法
实例变量可以有各种变量只有 public static final 常量
方法可有抽象方法和具体方法JDK 8 前只有抽象方法;JDK 8+ 支持 default/static
访问修饰符方法可以有任意访问级别方法默认 public
成员访问可以有任何访问修饰符所有成员默认 public
设计关系"is-a" 关系,表示本质"can-do" 关系,表示能力
使用场景共享代码 + 模板方法模式定义契约(行为规范)

如何选择

使用抽象类的情况:
  - 需要在子类间共享代码(有方法实现)
  - 需要定义非 public 的成员
  - 需要使用构造方法初始化状态
  - 符合严格的 is-a 关系

使用接口的情况:
  - 定义不相关的类之间的共同行为(can-do)
  - 需要多重继承行为
  - 只定义契约,不关心实现细节
  - 希望支持未来的扩展(default 方法可向后兼容添加)

组合使用的情况:
  - 抽象类实现接口,提供默认实现
  - 如:public abstract class AbstractList<E> implements List<E> 
// 组合使用示例
interface Payable {
    void pay(double amount);
    default void refund(double amount) {
        System.out.println("退款:" + amount);
    }
}

abstract class AbstractPayment implements Payable {
    protected String merchantId;

    public AbstractPayment(String merchantId) {
        this.merchantId = merchantId;
    }

    // 提供通用的验证逻辑
    protected void validateAmount(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("金额无效");
    }

    // 留给子类实现
    @Override
    public abstract void pay(double amount);
}

class WechatPay extends AbstractPayment {
    public WechatPay(String merchantId) {
        super(merchantId);
    }

    @Override
    public void pay(double amount) {
        validateAmount(amount);
        System.out.println("微信支付:" + amount);
    }
}

2.14 内部类

概述

内部类是定义在另一个类内部的类。Java 支持四种内部类:成员内部类、静态嵌套类、局部内部类、匿名类。

1. 成员内部类(非静态内部类)

定义在类中方法外的非静态类。

public class Outer {
    private String outerField = "外部类字段";
    private static String staticField = "静态字段";

    // 成员内部类
    public class Inner {
        // 不能定义静态成员(除 static final 常量外)
        // private static int x;  // 编译错误!

        public void display() {
            // 可以访问外部类的所有成员(包括 private)
            System.out.println("访问外部实例字段:" + outerField);
            System.out.println("访问外部静态字段:" + staticField);

            // 持有外部类引用
            System.out.println("外部类引用:" + Outer.this);
        }
    }

    public void createInner() {
        Inner inner = new Inner();  // 在外部类内部创建
        inner.display();
    }
}

// 外部创建
public class Main {
    public static void main(String[] args) {
        // 创建成员内部类需要先有外部类对象
        Outer outer = new Outer();
        Outer.Inner inner = outer.new Inner();
        inner.display();
    }
}

2. 静态嵌套类(Static Nested Class)

使用 static 修饰的内部类。

public class Outer {
    private String instanceField = "实例字段";
    private static String staticField = "静态字段";

    // 静态嵌套类
    public static class StaticNested {
        private String nestedField = "嵌套类字段";

        public void display() {
            // 只能访问外部类的静态成员
            System.out.println("访问静态字段:" + staticField);
            // System.out.println(instanceField);  // 编译错误!不能访问实例成员

            // 不持有外部类引用,可以独立存在
        }

        public static void staticMethod() {
            System.out.println("静态嵌套类的静态方法");
        }
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        // 不需要外部类对象,直接创建
        Outer.StaticNested nested = new Outer.StaticNested();
        nested.display();

        // 调用静态方法
        Outer.StaticNested.staticMethod();

        // 常见应用:Builder 模式
        Computer pc = new Computer.Builder("i7", "16GB").gpu("RTX 4080").build();
    }
}

3. 局部内部类

定义在方法、构造方法或代码块中的类。

public class Outer {
    public void processData(String prefix) {
        int localVar = 100;  // 事实 final

        // 局部内部类
        class DataProcessor {
            private String name;

            public DataProcessor(String name) {
                this.name = name;
            }

            public void process() {
                // 只能访问 final 或事实 final 的局部变量
                System.out.println(prefix + name + " " + localVar);

                // localVar = 200;  // 如果修改 localVar,上一行编译错误!
                // 因为局部变量被内部类捕获后必须不可变
            }
        }

        DataProcessor processor = new DataProcessor("处理器");
        processor.process();
    }
}

4. 匿名类

没有名字的内部类,用于实现接口或继承类的一次性使用。

public class AnonymousClassDemo {
    public static void main(String[] args) {
        // 1. 匿名类实现接口
        Runnable task = new Runnable() {
            @Override
            public void run() {
                System.out.println("匿名类实现的线程任务");
            }
        };
        new Thread(task).start();

        // 2. 匿名类继承抽象类
        Animal dog = new Animal("旺财") {
            @Override
            public void makeSound() {
                System.out.println(name + " 汪汪叫!(来自匿名类)");
            }
        };
        dog.makeSound();

        // 3. 匿名类作为参数传递
        List<String> names = Arrays.asList("Alice", "Bob", "Charlie");
        Collections.sort(names, new Comparator<String>() {
            @Override
            public int compare(String a, String b) {
                return a.length() - b.length();
            }
        });

        // 4. JDK 8+ 可用 Lambda 替代匿名类
        Collections.sort(names, (a, b) -> a.length() - b.length());
    }
}

四种内部类对比

类型修饰符访问外部成员持有外部引用可定义静态成员典型用途
成员内部类static可访问所有否(除 static final 常量)与外部类紧密关联的辅助类
静态嵌套类static仅静态成员Builder 模式、独立辅助类
局部内部类不能用访问修饰符可访问所有 + final 局部变量方法内临时使用的类
匿名类无名可访问所有 + final 局部变量简单的一次性实现(监听器、Comparator)

选择指南

需要访问外部类实例成员 → 成员内部类
不需要外部类实例 → 静态嵌套类(优先选择,内存效率更高)
简单接口的一次性实现 → 匿名类(或 Lambda)
方法内部临时使用 → 局部内部类

内存效率排序

静态嵌套类 > 局部内部类 > 成员内部类 > 匿名类

原因:静态嵌套类不持有外部引用,没有额外的内存开销;匿名类和成员内部类都隐式持有外部类引用,可能阻止外部类被 GC 回收。


2.15 枚举(Enum)

核心概念

枚举(Enum)是一种特殊的类,用于定义一组固定的常量。所有枚举类都隐式继承 java.lang.Enum

基本定义与使用

// 简单枚举
public enum Day {
    MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, SATURDAY, SUNDAY
}

// 使用
public class EnumBasicDemo {
    public static void main(String[] args) {
        Day today = Day.FRIDAY;

        // switch 中使用枚举
        switch (today) {
            case MONDAY:
                System.out.println("周一综合征");
                break;
            case FRIDAY:
                System.out.println("TGIF!");
                break;
            case SATURDAY:
            case SUNDAY:
                System.out.println("周末愉快!");
                break;
            default:
                System.out.println("工作日");
        }
    }
}

带属性和方法的枚举

public enum Season {
    // 枚举实例(调用构造方法)
    SPRING("春天", "温暖", 1),
    SUMMER("夏天", "炎热", 2),
    AUTUMN("秋天", "凉爽", 3),
    WINTER("冬天", "寒冷", 4);

    // 成员变量
    private final String chineseName;
    private final String description;
    private final int order;

    // 构造方法必须是 private(默认就是 private)
    Season(String chineseName, String description, int order) {
        this.chineseName = chineseName;
        this.description = description;
        this.order = order;
    }

    // getter 方法
    public String getChineseName() { return chineseName; }
    public String getDescription() { return description; }
    public int getOrder() { return order; }

    // 自定义方法
    public boolean isHotSeason() {
        return this == SUMMER;
    }

    public boolean isColdSeason() {
        return this == WINTER;
    }
}

// 使用
public class Main {
    public static void main(String[] args) {
        Season s = Season.SUMMER;
        System.out.println(s.getChineseName());   // 夏天
        System.out.println(s.getDescription());   // 炎热
        System.out.println(s.isHotSeason());      // true
    }
}

枚举的常用方法

public class EnumMethodsDemo {
    public static void main(String[] args) {
        Day day = Day.FRIDAY;

        // name():返回枚举常量名(字符串)
        System.out.println(day.name());           // FRIDAY

        // ordinal():返回枚举常量的序数(从 0 开始)
        System.out.println(day.ordinal());        // 4(第 5 个枚举值)

        // toString():默认返回 name(),可重写
        System.out.println(day.toString());       // FRIDAY

        // valueOf(String):通过名称获取枚举常量
        Day d = Day.valueOf("MONDAY");
        System.out.println(d);                    // MONDAY
        // Day d2 = Day.valueOf("INVALID");       // 抛出 IllegalArgumentException

        // values():返回所有枚举常量的数组
        Day[] allDays = Day.values();
        for (Day each : allDays) {
            System.out.print(each + " ");
        }
        // 输出:MONDAY TUESDAY WEDNESDAY THURSDAY FRIDAY SATURDAY SUNDAY

        // compareTo():比较顺序(基于 ordinal)
        System.out.println(Day.MONDAY.compareTo(Day.FRIDAY));  // 负数(MONDAY 在 FRIDAY 之前)
        System.out.println(Day.FRIDAY.compareTo(Day.MONDAY));  // 正数
        System.out.println(Day.MONDAY.compareTo(Day.MONDAY));  // 0
    }
}

枚举比较

public class EnumComparison {
    public static void main(String[] args) {
        Day d1 = Day.MONDAY;
        Day d2 = Day.MONDAY;
        Day d3 = Day.FRIDAY;

        // == 和 equals() 都可以用于枚举比较(推荐使用 ==,因为枚举是单例)
        System.out.println(d1 == d2);           // true
        System.out.println(d1.equals(d2));      // true
        System.out.println(d1 == d3);           // false

        // 枚举实现了 Comparable 接口
        // 不能用 > 和 < 直接比较!
        // if (d1 < d3)   // 编译错误!

        // 使用 compareTo() 进行比较
        if (d1.compareTo(d3) < 0) {
            System.out.println(d1 + " 在 " + d3 + " 之前");
        }
    }
}

枚举实现接口

// 定义接口
interface Operator {
    double apply(double a, double b);
}

// 枚举实现接口
public enum MathOperator implements Operator {
    ADD {
        @Override
        public double apply(double a, double b) {
            return a + b;
        }
    },
    SUBTRACT {
        @Override
        public double apply(double a, double b) {
            return a - b;
        }
    },
    MULTIPLY {
        @Override
        public double apply(double a, double b) {
            return a * b;
        }
    },
    DIVIDE {
        @Override
        public double apply(double a, double b) {
            if (b == 0) throw new ArithmeticException("除数不能为0");
            return a / b;
        }
    };
}

// 使用
public class Main {
    public static void main(String[] args) {
        double result = MathOperator.ADD.apply(10, 5);
        System.out.println("10 + 5 = " + result);  // 15.0

        double result2 = MathOperator.MULTIPLY.apply(3, 4);
        System.out.println("3 * 4 = " + result2);  // 12.0
    }
}

枚举的注意事项

要点说明
构造方法必须是 private枚举构造方法默认就是 private,且不能改为 publicprotected
不能用 > 和 < 直接比较枚举不支持 < > 运算符,使用 compareTo() 方法
== 和 equals() 都可用枚举是单例模式,推荐使用 == 进行引用比较(更高效,且不会 NPE)
ordinal() 不建议在业务逻辑中使用ordinal() 依赖枚举声明顺序,调整顺序会导致逻辑错误。应使用自定义属性替代
可以实现接口枚举可以实现一个或多个接口
不能继承类枚举已经隐式继承 java.lang.Enum,Java 单继承限制导致不能再继承其他类
// 正确实践:用自定义属性替代 ordinal()
public enum ErrorCode {
    SUCCESS(200, "成功"),
    BAD_REQUEST(400, "错误的请求"),
    UNAUTHORIZED(401, "未授权"),
    NOT_FOUND(404, "未找到"),
    INTERNAL_ERROR(500, "服务器内部错误");

    private final int code;
    private final String message;

    ErrorCode(int code, String message) {
        this.code = code;
        this.message = message;
    }

    public int getCode() { return code; }
    public String getMessage() { return message; }

    // 通过 code 查找对应的枚举值
    public static ErrorCode fromCode(int code) {
        for (ErrorCode ec : values()) {
            if (ec.code == code) {
                return ec;
            }
        }
        throw new IllegalArgumentException("未知的错误码:" + code);
    }
}

2.16 递归

核心概念

递归(Recursion)是指一个方法在其方法体内调用自身来解决问题的编程技巧。递归通常将一个复杂问题分解为规模更小的同类子问题。

递归的三要素

1. 递归终止条件(Base Case):什么情况下停止递归
2. 递归调用(Recursive Call):如何将问题分解为更小的子问题
3. 递归前进(Progress):每次调用都要向终止条件靠近

线性递归

每次递归只产生一个子问题,递归深度与问题规模成正比 O(n)。

public class LinearRecursion {

    /**
     * 计算阶乘 n!
     * 递归公式:n! = n × (n−1)!,且 0! = 1
     */
    public static long factorial(int n) {
        // 终止条件(Base Case)
        if (n == 0 || n == 1) {
            return 1;
        }
        // 递归调用:n * (n-1)!
        return n * factorial(n - 1);
    }

    /**
     * 计算斐波那契数列第 n 项(线性递归优化版)
     * F(0) = 0, F(1) = 1, F(n) = F(n-1) + F(n-2)
     */
    public static long fibonacci(int n) {
        return fibonacciHelper(n, 0, 1);
    }

    // 尾递归辅助方法(Java 不优化尾递归,但逻辑清晰)
    private static long fibonacciHelper(int n, long a, long b) {
        if (n == 0) return a;
        if (n == 1) return b;
        return fibonacciHelper(n - 1, b, a + b);
    }

    /**
     * 计算数组元素之和
     */
    public static int arraySum(int[] arr, int index) {
        if (index >= arr.length) {
            return 0;  // 终止条件
        }
        return arr[index] + arraySum(arr, index + 1);
    }

    /**
     * 反转字符串
     */
    public static String reverse(String str) {
        if (str.isEmpty()) {
            return str;  // 终止条件
        }
        return reverse(str.substring(1)) + str.charAt(0);
    }

    public static void main(String[] args) {
        System.out.println("5! = " + factorial(5));                    // 120
        System.out.println("F(10) = " + fibonacci(10));                // 55
        System.out.println("数组和 = " + arraySum(new int[]{1,2,3,4,5}, 0));  // 15
        System.out.println("反转 = " + reverse("Hello"));              // olleH
    }
}

二分递归

每次递归产生两个子问题,可能导致大量重复计算。时间复杂度通常为 O(2^n)。

public class BinaryRecursion {

    /**
     * 斐波那契数列(朴素二分递归)
     * 问题:存在大量重复计算。F(5) 需要计算 F(3) 两次,F(2) 三次...
     */
    public static long fibonacciNaive(int n) {
        // 终止条件
        if (n <= 1) {
            return n;
        }
        // 二分递归:每次产生两个子问题
        return fibonacciNaive(n - 1) + fibonacciNaive(n - 2);
    }

    /**
     * 二分查找(递归实现)
     */
    public static int binarySearch(int[] arr, int target, int left, int right) {
        if (left > right) {
            return -1;  // 未找到
        }

        int mid = left + (right - left) / 2;

        if (arr[mid] == target) {
            return mid;
        } else if (arr[mid] > target) {
            return binarySearch(arr, target, left, mid - 1);
        } else {
            return binarySearch(arr, target, mid + 1, right);
        }
    }

    /**
     * 归并排序(递归实现)
     */
    public static void mergeSort(int[] arr, int left, int right) {
        if (left >= right) {
            return;  // 终止条件:单个元素或空区间
        }

        int mid = left + (right - left) / 2;

        // 二分递归
        mergeSort(arr, left, mid);
        mergeSort(arr, mid + 1, right);

        // 合并
        merge(arr, left, mid, right);
    }

    private static void merge(int[] arr, int left, int mid, int right) {
        int[] temp = new int[right - left + 1];
        int i = left, j = mid + 1, k = 0;

        while (i <= mid && j <= right) {
            temp[k++] = arr[i] <= arr[j] ? arr[i++] : arr[j++];
        }
        while (i <= mid) { temp[k++] = arr[i++]; }
        while (j <= right) { temp[k++] = arr[j++]; }

        System.arraycopy(temp, 0, arr, left, temp.length);
    }

    public static void main(String[] args) {
        // 斐波那契(注意:n=50 时朴素版本会很慢)
        System.out.println("F(10) 朴素 = " + fibonacciNaive(10));  // 55

        // 二分查找
        int[] arr = {1, 3, 5, 7, 9, 11, 13};
        int index = binarySearch(arr, 7, 0, arr.length - 1);
        System.out.println("7 的索引 = " + index);  // 3

        // 归并排序
        int[] data = {38, 27, 43, 3, 9, 82, 10};
        mergeSort(data, 0, data.length - 1);
        System.out.println("排序结果:" + Arrays.toString(data));
        // [3, 9, 10, 27, 38, 43, 82]
    }
}

线性递归 vs 二分递归对比

对比维度线性递归二分递归
子问题数量每次 1 个每次 2 个
时间复杂度通常 O(n)通常 O(2^n)(朴素)、O(n log n)(归并/快排)
重复计算一般不重复可能大量重复(如朴素斐波那契)
递归深度与输入规模成正比通常 log n 级别(分治)
典型应用阶乘、链表遍历、字符串处理斐波那契、归并排序、快速排序、二叉树遍历
优化策略可转为尾递归/迭代可使用记忆化搜索(Memoization)

递归优化:记忆化搜索

public class MemoizationDemo {
    // 记忆化搜索:缓存已计算的结果,避免重复计算
    private static Map<Integer, Long> memo = new HashMap<>();

    public static long fibonacciMemo(int n) {
        if (n <= 1) {
            return n;
        }

        // 如果已经计算过,直接返回缓存结果
        if (memo.containsKey(n)) {
            return memo.get(n);
        }

        // 计算并缓存
        long result = fibonacciMemo(n - 1) + fibonacciMemo(n - 2);
        memo.put(n, result);
        return result;
    }

    // 数组版本(更高效)
    public static long fibonacciMemoArray(int n) {
        long[] memo = new long[n + 1];
        return fib(n, memo);
    }

    private static long fib(int n, long[] memo) {
        if (n <= 1) return n;
        if (memo[n] != 0) return memo[n];
        memo[n] = fib(n - 1, memo) + fib(n - 2, memo);
        return memo[n];
    }

    public static void main(String[] args) {
        System.out.println("F(50) 记忆化 = " + fibonacciMemo(50));
        // 快速计算结果,而不是像朴素版本那样指数级耗时
    }
}

递归的注意事项

注意点说明
必须有终止条件否则会导致无限递归,最终触发 StackOverflowError
向终止条件靠近每次递归调用都应在问题规模上向 Base Case 靠近
注意栈溢出Java 栈深度有限(默认约 1MB),深度递归可能 StackOverflowError
警惕重复计算二分递归可能指数级重复,使用记忆化或动态规划优化
递归转迭代任何递归都可以转换为迭代实现,迭代更省内存
避免过深递归对于超大数据量,优先考虑迭代方案
// 递归转迭代示例:阶乘
public static long factorialIterative(int n) {
    long result = 1;
    for (int i = 2; i <= n; i++) {
        result *= i;
    }
    return result;
}

// 递归转迭代示例:斐波那契(动态规划)
public static long fibonacciDP(int n) {
    if (n <= 1) return n;
    long prev2 = 0, prev1 = 1, current = 0;
    for (int i = 2; i <= n; i++) {
        current = prev2 + prev1;
        prev2 = prev1;
        prev1 = current;
    }
    return current;
}

全文完。本笔记涵盖了 Java 面向对象编程的核心知识点,包括类与对象、方法、构造方法、访问修饰符、this/super/static/final 关键字、继承、封装、多态、抽象类与接口、内部类、枚举和递归等 16 个主题,适合作为 Java OOP 学习与复习的参考资料。
1

评论

博主关闭了所有页面的评论