Java(三)

38 阅读7分钟

本章讲解 Java 面向对象核心:类与对象、封装、继承、多态、抽象类与接口。Spring 里 @Service、@RestController 本质都是类的实例与接口实现。


1. 类与对象

概念

  • 类(Class):对象的蓝图,定义字段(状态)与方法(行为)。
  • 对象(Object / Instance):new 出来的具体实体。
  • 字段(Field)、方法(Method)、构造器(Constructor)。

原理

类加载后,Metaspace 存类元数据;new ClassName() 在堆上分配对象,构造器完成初始化。

完整示例

package com.example.demo;

public class User {
    private Long id;
    private String username;

    public User(Long id, String username) {
        this.id = id;
        this.username = username;
    }

    public String displayName() {
        return username + " (#" + id + ")";
    }

    public Long getId() { return id; }
    public String getUsername() { return username; }

    public static void main(String[] args) {
        User u = new User(1L, "alice");
        System.out.println(u.displayName());
    }
}

练习

定义 Order:字段 id、amount、status;方法 isPaid() 返回 status 是否为 "PAID"。

完整例子

package com.example.demo;

public class Order {
    private Long id;
    private double amount;
    private String status;

    public Order(Long id, double amount, String status) {
        this.id = id;
        this.amount = amount;
        this.status = status;
    }

    public boolean isPaid() {
        return "PAID".equals(status);
    }

    public static void main(String[] args) {
        Order o = new Order(1L, 99.0, "PAID");
        System.out.println(o.isPaid()); // true
    }
}

常见坑

  • 忘记写构造器时,编译器生成无参 public 构造器;一旦自定义构造器,默认无参不再自动提供。
  • 字段未初始化:基本类型默认 0/false,引用类型默认 null。

2. 封装(Encapsulation)

概念

隐藏内部状态,通过 getter/setter 或行为方法 controlled 访问。private 字段 + 公开方法。

原理

限制外部直接改字段,便于校验、审计、兼容后续重构(Spring Entity 常见 private 字段 + getter/setter 或 Lombok @Data)。

完整示例

package com.example.demo;

public class BankAccount {
    private final String accountNo;
    private double balance;

    public BankAccount(String accountNo, double initial) {
        if (initial < 0) throw new IllegalArgumentException("initial < 0");
        this.accountNo = accountNo;
        this.balance = initial;
    }

    public void deposit(double amount) {
        if (amount <= 0) throw new IllegalArgumentException("amount <= 0");
        balance += amount;
    }

    public boolean withdraw(double amount) {
        if (amount <= 0 || amount > balance) return false;
        balance -= amount;
        return true;
    }

    public double getBalance() { return balance; }
}

练习

给 Order 加 pay():仅 status 为 CREATED 时可变为 PAID,非法状态抛 IllegalStateException。

完整例子

package com.example.demo;

public class Order {
    private Long id;
    private double amount;
    private String status;

    public Order(Long id, double amount, String status) {
        this.id = id;
        this.amount = amount;
        this.status = status;
    }

    public void pay() {
        if (!"CREATED".equals(status)) {
            throw new IllegalStateException("cannot pay when status=" + status);
        }
        status = "PAID";
    }

    public String getStatus() { return status; }

    public static void main(String[] args) {
        Order o = new Order(1L, 99.0, "CREATED");
        o.pay();
        System.out.println(o.getStatus()); // PAID
        // o.pay(); // 再调会抛 IllegalStateException
    }
}

常见坑

  • public 字段导致任何人可改,破坏不变量。
  • setter 里不做校验,负数金额等脏数据进入系统。

3. 继承(Inheritance)

概念

子类 extends 父类,获得非 private 成员,可**重写(Override)**方法。Java 单继承(一个类只能 extends 一个类)。

原理

子类对象内存布局包含父类部分;动态分派在运行时按实际对象类型调用方法(多态基础)。

完整示例

package com.example.demo;

public class Animal {
    protected String name;

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

    public String speak() { return "..."; }
}

public class Dog extends Animal {
    public Dog(String name) { super(name); }  // 必须先调 super(...)

    @Override
    public String speak() { return name + ": 汪汪"; }
}

练习

Employee 父类字段 name、baseSalary;Manager 子类加 bonus,重写 totalPay()。

完整例子

package com.example.demo;

public class Employee {
    protected String name;
    protected double baseSalary;

    public Employee(String name, double baseSalary) {
        this.name = name;
        this.baseSalary = baseSalary;
    }

    public double totalPay() {
        return baseSalary;
    }
}

class Manager extends Employee {
    private double bonus;

    public Manager(String name, double baseSalary, double bonus) {
        super(name, baseSalary);
        this.bonus = bonus;
    }

    @Override
    public double totalPay() {
        return baseSalary + bonus;
    }

    public static void main(String[] args) {
        Employee e = new Manager("bob", 10000, 3000);
        System.out.println(e.totalPay()); // 13000(多态)
    }
}

常见坑

  • 子类构造器第一行必须是 this(...) 或 super(...)。
  • 误用继承表达「has-a」关系 — 应优先组合(见后文)。

4. 多态(Polymorphism)

概念

编译类型(引用声明的类型)与运行类型(实际对象类型)可不同。同一父类/接口引用,调用被重写的方法时,执行子类实现。

原理(直觉)

Payment p = new AlipayPayment();
p.pay(100);   // 运行时找 AlipayPayment.pay,而非 Payment.pay

JVM 虚方法表(vtable) 在运行时解析具体实现。设计意义:依赖抽象(接口/父类),便于扩展与测试(Spring 注入接口)。

完整示例

package com.example.demo;

interface Payment {
    String channel();
    void pay(double amount);
}

class AlipayPayment implements Payment {
    public String channel() { return "ALIPAY"; }
    public void pay(double amount) { System.out.println("支付宝: " + amount); }
}

class WechatPayment implements Payment {
    public String channel() { return "WECHAT"; }
    public void pay(double amount) { System.out.println("微信: " + amount); }
}

public class PolymorphismDemo {
    static void checkout(Payment payment, double amount) {
        System.out.println("渠道=" + payment.channel());
        payment.pay(amount);
    }

    public static void main(String[] args) {
        Payment p1 = new AlipayPayment();
        Payment p2 = new WechatPayment();
        checkout(p1, 99.0);
        checkout(p2, 99.0);
    }
}

练习

用户角色:定义 Role 接口 permissions();Admin、Guest 实现;User 持有 Role,方法 canDelete() 根据角色返回 boolean。详见 exercises.md。

要点:接口 Role 提供 Set<String> permissions() 或 boolean can(String action);AdminRole / GuestRole 权限不同;User 委托给 role,不要写死 if (isAdmin);main 里用多态 Role role = new AdminRole()。

完整例子

package com.example.demo.exercise;

import java.util.Set;

interface Role {
    Set<String> permissions();
    default boolean can(String action) {
        return permissions().contains(action);
    }
}

class AdminRole implements Role {
    public Set<String> permissions() {
        return Set.of("READ", "WRITE", "DELETE");
    }
}

class GuestRole implements Role {
    public Set<String> permissions() {
        return Set.of("READ");
    }
}

public class User {
    private final String name;
    private final Role role;

    public User(String name, Role role) {
        this.name = name;
        this.role = role;
    }

    public boolean canDelete() { return role.can("DELETE"); }
    public boolean canRead() { return role.can("READ"); }

    public static void main(String[] args) {
        User admin = new User("alice", new AdminRole());
        User guest = new User("bob", new GuestRole());
        System.out.println(admin.canDelete()); // true
        System.out.println(guest.canDelete()); // false
    }
}

常见坑

  • 多态只针对实例方法;static、 final、 private 方法不参与重写意义上的多态。
  • 强转前用 instanceof:((Dog) animal).fetch() 错类型会 ClassCastException。

5. 抽象类(Abstract Class)

概念

abstract class 可含抽象方法(无方法体)与具体方法;不能 new,须子类实现抽象方法。

原理

介于「纯接口」与「普通类」之间:共享代码 + 强制子类实现差异部分。模板方法模式常用抽象类。

完整示例

package com.example.demo;

abstract class Notification {
    protected String to;

    Notification(String to) { this.to = to; }

    public final void send(String content) {  // 模板方法
        String body = render(content);
        doSend(body);
    }

    protected abstract String render(String content);
    protected abstract void doSend(String body);
}

class EmailNotification extends Notification {
    EmailNotification(String to) { super(to); }
    protected String render(String content) { return "[Email to " + to + "] " + content; }
    protected void doSend(String body) { System.out.println(body); }
}

练习

抽象类 BaseExporter 定义 export(List<String> lines) 模板:校验 → format 抽象 → 写文件抽象。

完整例子

package com.example.demo;

import java.util.List;

abstract class BaseExporter {
    public final void export(List<String> lines) {
        if (lines == null || lines.isEmpty()) {
            throw new IllegalArgumentException("lines empty");
        }
        String body = format(lines);
        write(body);
    }

    protected abstract String format(List<String> lines);
    protected abstract void write(String body);
}

class ConsoleCsvExporter extends BaseExporter {
    protected String format(List<String> lines) {
        return String.join(",", lines);
    }

    protected void write(String body) {
        System.out.println(body);
    }

    public static void main(String[] args) {
        new ConsoleCsvExporter().export(List.of("a", "b", "c"));
    }
}

常见坑

  • 抽象类可有构造器,供子类 super() 调用。
  • 与接口同时出现时,类 extends 只能一个,但可 implements 多个接口。

6. 接口(Interface)

概念

Java 8+ 接口可有 default、static 方法;接口是能力契约,类 implements 可多实现。

原理

面向接口编程解耦实现。Spring 中大量 @Autowired SomeService,注入的是接口或具体类的代理。

完整示例

package com.example.demo;

interface Repository<T> {
    T findById(Long id);
    void save(T entity);
}

class InMemoryUserRepository implements Repository<User> {
    // 简化:Map 存储
    public User findById(Long id) { return null; }
    public void save(User entity) { }
}

// default 方法示例
interface Auditable {
    default void audit(String action) {
        System.out.println("[AUDIT] " + action);
    }
}

练习

支付接口:Payment 含 pay(Money)、refund(Money);实现 MockPayment 打日志即可。见 exercises.md。

要点:接口 Payment 含 channel()、pay(double)、refund(double)(金额可用 double,日后可换 BigDecimal);编写 checkout(Payment p, double amount) 体现多态;新增渠道只加实现类。

完整例子

package com.example.demo.exercise;

interface Payment {
    String channel();
    void pay(double amount);
    void refund(double amount);
}

class MockPayment implements Payment {
    public String channel() { return "MOCK"; }
    public void pay(double amount) {
        System.out.println("[MOCK] pay " + amount);
    }
    public void refund(double amount) {
        System.out.println("[MOCK] refund " + amount);
    }
}

public class PaymentDemo {
    static void checkout(Payment p, double amount) {
        System.out.println("channel=" + p.channel());
        p.pay(amount);
    }

    public static void main(String[] args) {
        checkout(new MockPayment(), 99.0);
    }
}

常见坑

  • 接口字段默认 public static final。
  • default 方法菱形继承冲突时需子类 override 解决。

7. this 与 super

概念

  • this:当前实例;区分字段与参数同名;构造器链 this(...)。
  • super:父类部分;调父类构造 super(...);调被覆盖的父类方法 super.method()。

完整示例

package com.example.demo;

public class Point {
    private int x, y;

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public Point(int xy) {
        this(xy, xy);  // 委托其他构造器
    }
}

class LabelPoint extends Point {
    private String label;

    LabelPoint(int x, int y, String label) {
        super(x, y);
        this.label = label;
    }
}

常见坑

  • this() / super() 必须放在构造器首行,二者不能同构造器并存。
  • 在 static 方法里不能用 this(无当前实例)。

8. 访问修饰符(Access Modifiers)

概念

修饰符同类同包子类任意
private✓
(包私有)✓✓
protected✓✓✓
public✓✓✓✓

原理

封装边界 + 包内协作。Spring 组件通常 public 类;字段倾向 private。

完整示例

package com.example.demo;

public class AccessDemo {
    private void internal() {}
    void packagePrivate() {}
    protected void forSubclass() {}
    public void api() {}
}

练习

将 BankAccount.balance 保持 private,同包测试类只能通过 deposit/withdraw 改余额。

完整例子

package com.example.demo;

// BankAccount.java — balance 必须 private(见上文完整示例)

public class BankAccountAccessTest {
    public static void main(String[] args) {
        BankAccount acc = new BankAccount("A001", 100);
        acc.deposit(50);
        // acc.balance = 999;  // 编译错误:private
        System.out.println(acc.getBalance()); // 150
        System.out.println(acc.withdraw(200)); // false,余额不变
        System.out.println(acc.getBalance()); // 150
    }
}

常见坑

  • protected 对不同包子类可见,但对 random 外部类不可见。
  • 误以为「getter public = 封装完美」— 若返回可变对象引用(如 List),仍可能外泄(应返回副本或不可变视图,见 04 章)。

9. 组合优于继承

概念

「有一个」用组合(持有其他类引用),「是一个」才考虑继承。

完整示例

package com.example.demo;

class Engine {
    void start() { System.out.println("engine on"); }
}

class Car {
    private final Engine engine = new Engine();  // 组合

    void start() { engine.start(); }
}

常见坑

  • 深层继承链难维护;优先接口 + 组合(Spring 里也常见 Delegate 模式)。

本章小结

主题一句话
封装private 状态 + 公开行为
继承extends,单继承,super
多态接口/父类引用,运行时子类方法
抽象类共享实现 + 抽象步骤
接口契约,多实现,default/static