M03:面向对象编程(OOP)

📖 所属阶段 :W1 · Java 语言基础(共 3 个模块:M01 ✅ + M02 ✅ + M03


1. 模块概览

项目 内容
一句话总结 掌握 Java OOP 四大支柱------封装、继承、多态、抽象,以及访问修饰符、static、this/super
前置依赖 M02(Java 语法基础)
预计耗时 2 小时
评估标准 能设计一个包含继承和接口的小型类体系,正确使用访问修饰符

1.1 概念映射速查表

前端概念 Java 对应 说明
class / constructor class / 构造器 语法几乎一样,但 Java 更严格
this this Java 的 this 不会丢失(不像 JS 会绑定问题)
extends extends 语法一模一样! 但 Java 只能单继承
#private 字段 private 修饰符 Java 强制私有,必须通过 getter/setter 访问
static 方法/属性 static JS 和 Java 都支持,含义相同
TS interface Java interface 区别大!Java 接口可以有 default 方法
TS 泛型接口 Java 接口 + 多实现 Java 可以 implements A, B, C 多个接口
super super 父类构造器 / 父类方法调用
没有等价 protected Java 独有------子类可访问,外部不行

1.2 JS class vs Java class 差异速查

特性 JavaScript Java
类声明 class A { } public class A { }(必须加修饰符)
构造器 constructor() { } 与类同名的方法
私有字段 #name(提案)或 _name(约定) private String name(强制)
继承 extends(单继承) extends(单继承)+ implements(多接口)
接口 没有(TS 有,但只是类型) interface(运行时存在)
方法重写 直接写同名方法 @Override 注解
静态成员 static prop / static method() 完全一样
this 绑定 可能丢失(箭头函数解决) 永远指向当前对象,不会丢
默认值 字段会自动赋值 undefined 字段会自动赋值 0/false/null

2. 核心知识点

知识点 1:访问修饰符 ------ Java 的"权限四象限"

前端类比

typescript 复制代码
// TypeScript 的访问修饰符(编译后消失!)
class User {
    public name: string;      // 默认 public,谁都能访问
    private password: string;  // 只有类内部能访问(编译时检查)
}

Java 原理

java 复制代码
// Java 的访问修饰符(运行时生效!)
public class User {
    public String name;       // 公开:谁都能访问
    private String password;  // 私有:只有 User 类内部能访问
    protected String email;   // 保护:子类 + 同包能访问(JS 没有!)
    // 默认(不加修饰符):同包能访问
}

四种修饰符可见性:

修饰符 同一个类 同包 子类(不同包) 全局
public
protected
默认(无)
private

💡 实战建议 :字段一律用 private,通过 public 的 getter/setter 暴露。这就是封装


知识点 2:构造器(Constructor)------ 和 JS 的 constructor 不一样

前端类比

typescript 复制代码
// JS 的 constructor 是 class 里的一个特殊方法
class Person {
    constructor(public name: string, public age: number) {
        // 构造器完成后对象就创建好了
    }
}

Java 原理

java 复制代码
// Java 的构造器:和类同名,没有返回值类型(连 void 都不能写!)
public class Person {
    private String name;
    private int age;

    // 无参构造器
    public Person() { }

    // 有参构造器(重载)
    public Person(String name, int age) {
        this.name = name;   // this 区分字段和参数
        this.age = age;
    }

    // 构造器可以互相调用(用 this()),但必须放在第一行
    public Person(String name) {
        this(name, 0);  // 调用上面的两参数构造器
    }
}

构造器规则:

  1. 类没有写任何构造器 → 编译器自动加一个无参构造器
  2. 自己写了有参构造器 → 编译器不再提供默认无参构造器!
  3. 构造器不能被 staticfinalabstract 修饰
  4. 子类构造器第一行默认调用 super()(父类无参构造器)

🚨 坑预警 :父类如果没有无参构造器,子类必须显式写 super(...) 调用父类的有参构造器。


知识点 3:static 关键字 ------ 属于类,不属于对象

前端类比

javascript 复制代码
// JS 的 static 方法
class MathUtil {
    static PI = 3.14;
    static add(a, b) { return a + b; }
}
// 调用:MathUtil.add(1, 2) ------ 不需要 new MathUtil()

Java 原理

java 复制代码
public class MathUtil {
    // static 变量:所有对象共享一份
    public static final double PI = 3.14;  // static + final = 常量

    // static 方法:不需要 new 对象就能调用
    public static int add(int a, int b) {
        return a + b;
    }

    // 普通变量:每个对象各有一份
    private int instanceCount;
}

// 调用
MathUtil.add(1, 2);       // ✅ 直接用类名调用
System.out.println(MathUtil.PI);  // ✅ 直接访问
// new MathUtil().add(1, 2);    // ❌ 编译警告(不推荐)

static 的限制:

  • static 方法里不能用 this(因为没有对象)
  • static 方法里不能直接访问非 static 字段
  • static 方法不能被重写(但可以被继承)

知识点 4:继承 extends ------ Java 单继承

前端类比

typescript 复制代码
class Person { name: string; }
class Student extends Person { studentId: string; }
// 语法完全一样!

Java 原理

java 复制代码
// Java 单继承:一个类只能有一个父类
public class Student extends Person {
    private String studentId;

    // 子类构造器第一行必须调用父类构造器
    public Student(String name, int age, String studentId) {
        super(name, age);   // ✅ 调用父类 Person(name, age)
        this.studentId = studentId;
    }
}

继承规则:

  1. Java 只有单继承class A extends B,B 还可以 extends C,形成继承链
  2. 子类继承父类的 publicprotected 成员(private 不继承)
  3. 子类可以**重写(Override)**父类方法,加 @Override 注解
  4. 子类用 super.method() 调用父类被重写的方法
java 复制代码
// 方法重写示例
@Override  // 编译器帮你检查是不是真的在重写
public void sayHello() {
    super.sayHello();  // 先执行父类的逻辑
    System.out.println("我是学生!");  // 再加自己的逻辑
}

知识点 5:接口 interface ------ Java 和 TS 最大的区别之一

前端类比

typescript 复制代码
// TS interface 只是类型约定,编译后消失
interface Flyable {
    fly(): void;
    // ❌ 不能有实现!
}
class Bird implements Flyable {
    fly() { console.log("飞"); }
}

Java 原理

java 复制代码
// Java interface 是运行时存在的,可以有 default 方法
public interface Flyable {
    // 抽象方法(默认 public abstract)------ 实现类必须实现
    void fly();

    // default 方法(Java 8+)------ 接口可以有默认实现!
    default void land() {
        System.out.println("平稳降落...");
    }

    // static 方法(Java 8+)------ 通过接口名调用
    static void info() {
        System.out.println("这是飞行接口");
    }
}

// 一个类可以 implements 多个接口(弥补单继承的不足)
public class Bird implements Flyable {
    @Override
    public void fly() { System.out.println("鸟儿飞"); }

    // 可选:重写 default 方法
    @Override
    public void land() { System.out.println("轻盈落下"); }
}

// 调用
Flyable.info();   // ✅ 静态方法通过接口名调用
Bird b = new Bird();
b.fly();          // ✅ 自己的实现
b.land();         // ✅ 重写后的实现

接口 vs 抽象类(先有印象,M08 讲 Spring 时再深入):

特性 interface abstract class
多实现 ✅ 可以多个 ❌ 只能一个
构造器 ❌ 没有 ✅ 有
字段 只能是 public static final 任意
方法实现 default / static 可以 任意
设计意图 "能做什么"(能力) "是什么"(身份)

知识点 6:多态(Polymorphism)------ 同一接口,不同实现

前端类比

typescript 复制代码
// TS 的多态------父类引用指向子类对象
const animals: Animal[] = [
    new Dog("旺财"),
    new Cat("咪咪"),
];
animals.forEach(a => a.speak()); // 各自有不同的实现

Java 原理

java 复制代码
// 父类/接口引用可以指向子类对象
Person p = new Student("小明", 20, "001");
p.sayHello();  // 调用的是 Student 重写的 sayHello(动态绑定)

// 多态数组
Flyable[] flyers = {
    new Bird("麻雀", "灰色"),
    new Bird("鹦鹉", "彩色"),
};
for (Flyable f : flyers) {
    f.fly();  // 各自的 fly() 实现
}

// 向下转型 + instanceof
if (p instanceof Student) {
    Student s = (Student) p;  // 强转
    s.study();               // 现在可以调用 Student 独有方法了
}

3. 完整示例代码

文件清单

文件 说明
Person.java 父类:构造器重载、getter/setter、static、toString、equals
Student.java 子类:extends Person、super、方法重写
Flyable.java 接口:抽象方法 + default 方法 + static 方法
Bird.java 实现类:implements Flyable
OOPDemo.java 主程序:综合演示所有特性

运行方式

bash 复制代码
cd M03-OOP

# 编译(编译所有 .java 文件)
javac -encoding UTF-8 -d out src/com/example/learn/*.java

# 运行主程序
java -cp out com.example.learn.OOPDemo

💡 如果控制台中文乱码,加上 -Dfile.encoding=UTF-8java -Dfile.encoding=UTF-8 -cp out com.example.learn.OOPDemo

预期输出

csharp 复制代码
========================================
  M03 面向对象编程(OOP)综合演示
========================================

===== 1. 创建 Person 对象 =====
👋 你好,我是 张三,今年 25 岁
👋 你好,我是 李四,今年 30 岁

===== 2. getter / setter 访问 private 字段 =====
⚠️ 年龄不合法: -5,已忽略
张三 现在 26 岁

===== 3. static 静态变量(所有对象共享) =====
总共创建了 3 个 Person 对象
...

===== 10. equals() 比较对象内容 =====
pa == pb ? false
pa.equals(pb) ? true
🚨 记住:对象比较永远用 .equals(),不要用 ==

4. 练习题

题目 1:设计 Shape 接口(基础)

要求:

  1. 定义一个 Shape 接口,包含:
    • double getArea() ------ 计算面积
    • default 方法 String getInfo() ------ 返回 "这是一个形状"
  2. 实现 Rectangle 类(矩形)------ 实现 Shape 接口,有 width 和 height
  3. 实现 Circle 类(圆形)------ 实现 Shape 接口,有 radius(π 用 3.14)
  4. 在主方法中创建 Rectangle(5, 3) 和 Circle(2),打印它们的面积

参考答案:

java 复制代码
// Shape.java
package com.example.learn;

public interface Shape {
    double getArea();

    default String getInfo() {
        return "这是一个形状";
    }
}

// Rectangle.java
package com.example.learn;

public class Rectangle implements 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;
    }
}

// Circle.java
package com.example.learn;

public class Circle implements Shape {
    private double radius;

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

    @Override
    public double getArea() {
        return 3.14 * radius * radius;
    }
}

// 主方法
public class ShapeTest {
    public static void main(String[] args) {
        Shape[] shapes = {
            new Rectangle(5, 3),
            new Circle(2),
        };
        for (Shape s : shapes) {
            System.out.println(s.getInfo() + ",面积 = " + s.getArea());
        }
    }
}

运行结果:

复制代码
这是一个形状,面积 = 15.0
这是一个形状,面积 = 12.56

题目 2:Animal 继承体系(综合)

要求:

  1. 创建抽象类 Animal(用 abstract 关键字),包含:
    • protected String name
    • 抽象方法 abstract void speak()
    • 普通方法 void eat() 打印 "xxx 正在吃东西"
  2. 创建 Dog extends Animal ------ speak() 打印 "汪汪"
  3. 创建 Cat extends Animal ------ speak() 打印 "喵喵"
  4. 创建 Robot 类,实现 Flyable 接口 + 有 work() 方法
  5. 主方法演示多态:Animal 数组 + Flyable 数组

参考答案:

java 复制代码
// Animal.java(抽象类)
package com.example.learn;

public abstract class Animal {
    protected String name;

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

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

    // 普通方法:抽象类可以有实现
    public void eat() {
        System.out.println(name + " 正在吃东西 🍖");
    }
}

// Dog.java
public class Dog extends Animal {
    public Dog(String name) { super(name); }

    @Override
    public void speak() {
        System.out.println("🐕 " + name + ":汪汪!");
    }
}

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

    @Override
    public void speak() {
        System.out.println("🐱 " + name + ":喵喵~");
    }
}

// Robot.java
public class Robot implements Flyable {
    private String model;

    public Robot(String model) { this.model = model; }

    @Override
    public void fly() {
        System.out.println("🤖 " + model + " 启动喷射推进器,飞起来了!");
    }

    public void work() {
        System.out.println("🤖 " + model + " 正在工作 ⚙️");
    }
}

// 主方法
public class AnimalTest {
    public static void main(String[] args) {
        // 多态 1:抽象类引用指向具体子类
        Animal[] animals = {
            new Dog("旺财"),
            new Cat("咪咪"),
            new Dog("小黑"),
        };
        for (Animal a : animals) {
            a.speak();    // 动态绑定到子类的 speak()
            a.eat();      // 抽象类的普通方法
        }

        // 多态 2:接口引用指向实现类
        Flyable[] flyables = {
            new Bird("雄鹰", "棕色"),
            new Robot("X-7"),
        };
        for (Flyable f : flyables) {
            f.fly();
        }
    }
}

题目 3:final 关键字的三种用法(理解)

要求: 说明 final 在以下三种场景中的作用,并写代码验证:

  1. final 修饰变量
  2. final 修饰方法
  3. final 修饰

参考答案:

java 复制代码
public class FinalDemo {
    public static void main(String[] args) {
        // 1. final 变量 = 常量
        final int MAX_AGE = 150;
        // MAX_AGE = 200;  ← ❌ 编译报错,不能修改
        System.out.println("MAX_AGE = " + MAX_AGE);

        // final 修饰引用类型------引用不能变,但内容可以变
        final List<String> names = new ArrayList<>();
        names.add("张三");   // ✅ 可以!内容可变
        // names = new ArrayList<>();  ← ❌ 编译报错,引用不能变
    }

    // 2. final 方法------子类不能重写
    public final void importantMethod() {
        System.out.println("这个方法不允许子类重写");
    }

    // 3. final 类------不能被继承(如 String 类就是 final!)
    // public final class Utility { ... }
}

// ❌ 编译报错:无法继承 final 类
// class Bad extends FinalDemo { }

final 三种用法总结:

修饰对象 效果
变量 值/引用不可变(= 常量)
方法 子类不能重写
不能被继承

💡 String 类是 final 的------防止你继承它改变字符串的不可变性。


5. 常见坑

坑 1:子类没有显式调用 super(),但父类没有无参构造器

java 复制代码
// ❌ 编译报错
class Parent {
    public Parent(String name) { }  // 只有有参构造器
}

class Child extends Parent {
    // 编译器默认加 Child() { super(); }
    // 但 Parent 没有无参构造器!→ 编译报错
}

// ✅ 修复方案:子类显式调用父类有参构造器
class Child extends Parent {
    public Child() {
        super("默认名字");  // 显式调用
    }
}

💡 最佳实践 :如果类有子类倾向,要么保留无参构造器,要么在子类中显式 super(...)


坑 2:private 字段没有 getter/setter 导致子类访问不到

java 复制代码
public class Person {
    private String name;  // private:子类也访问不到!
}

public class Student extends Person {
    public void test() {
        // this.name = "小明";  ← ❌ 编译报错!private 子类也看不到
    }
}

// ✅ 方案 1:用 protected
protected String name;   // 子类可以直接访问

// ✅ 方案 2:保留 private + getter/setter
// 在 Person 里加:
public String getName() { return name; }
public void setName(String name) { this.name = name; }

坑 3:Java 单继承,一个类不能 extends 两个类

java 复制代码
// ❌ 编译报错
class A { }
class B { }
class C extends A, B { }   // Java 不支持多继承!

// ✅ 解决方案:用接口弥补
interface BInterface { }
class C extends A implements BInterface { }

// 一个类可以 implements 多个接口
class MultiImpl implements A, B, C { }  // ✅

坑 4:== 比较对象永远是 false

java 复制代码
Person p1 = new Person("张三", 25);
Person p2 = new Person("张三", 25);

// ❌ 永远是 false(不同对象,地址不同)
if (p1 == p2) { }

// ✅ 比较内容用 equals()
if (p1.equals(p2)) { }

// 记得:自己写的类要重写 equals(),否则默认和 == 一样

6. JS class vs Java class 完整对照表

能力 JavaScript Java 备注
定义类 class A {} public class A {} Java 必须加 public/package-private
构造器 constructor(x) {} A(x) {} Java 构造器 = 类同名方法
重载构造器 ❌ 不支持 ✅ 支持多个 Java 可以写多个构造器
继承 extends extends 语法完全一样
多实现 implements A, B Java 用接口弥补单继承
private 字段 #field private Java 强制,JS 是提案
protected 子类可见,外部不可
this 可能丢失 不会丢 JS 的 this 绑定是出了名的坑
super super() / super.method() 一样
static static static 含义相同
final 类 final class 不能被继承
抽象类 abstract class TS 有 abstract class
接口 ❌(TS 有) interface Java 接口可有 default 方法
toString 无原生([object Object] toString() 打印对象自动调用

7. 知识小结

java 复制代码
M03 你学到了什么?
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

✅ 访问修饰符:public / protected / 默认 / private
   → private 字段必须通过 getter/setter 访问(封装)
   → protected 子类可见(JS 没有!)

✅ 构造器:和类同名、无返回值、可以重载
   → 子类默认调用 super()
   → 父类没有无参构造器时,子类必须显式 super(...)

✅ static:属于类,不属于对象
   → static 变量所有对象共享
   → static 方法不用 new 就能调用
   → static 里不能用 this

✅ 继承:extends,Java 单继承
   → 重写方法加 @Override 注解
   → super() 调父类构造器,super.method() 调父类方法

✅ 接口:interface + implements,可多实现
   → Java 接口可有 default / static 方法
   → TS 接口只是类型约定,Java 接口运行时存在

✅ 多态:父类/接口引用指向子类对象
   → 动态绑定:调用子类重写后的方法
   → instanceof 判断类型 + 向下转型

✅ final 三种用法:变量(常量)、方法(不能重写)、类(不能继承)

下一个模块 → M04:集合框架

相关文件:JAVA-LEARN-SPEC.md | M01 笔记 | M02 笔记

相关推荐
回家路上绕了弯1 小时前
ZCode 入门教程:从打开项目到完成一次 Java 代码修改
后端
SamDeepThinking1 小时前
new Thread()之后发生了什么?
java·后端·面试
亦暖筑序1 小时前
AgentScope Java 实战:Agent 的状态存在哪、怎么恢复、怎么隔离?
人工智能·后端·agent
斯维赤1 小时前
Spring AI | Function Calling 是什么?
java·后端
yunwei371 小时前
eBPF 教程:BPF 调度器入门
linux·后端·性能优化
旺仔不是程序员1 小时前
判断存在用 LIMIT 1:PostgreSQL 五种 count 计数方式与 NULL 语义
数据库·后端·sql
旺仔不是程序员1 小时前
IN 操作规范:PostgreSQL 元素数量、EXISTS 替代与 = ANY 写法
数据库·后端·sql
晚安日记wanna2 小时前
一条 SMEMBERS 干瘫 Redis 节点:单线程的真正边界在哪
redis·后端·面试