文章目录:
  1. Lesson 1 概述
    1. 1. 各种编程语言区别
    2. 2. 面向对象编程概念
    3. 3. Java特性
  2. Lesson 2 面向对象的思维模式
    1. 1. 面向对象的思维
    2. 2. 类之间的关系
    3. 3. Java中的内存与变量
  3. Lesson 3 类的基本知识
    1. 1. 类的设计原则
    2. 2. 类的定义与成员变量
    3. 3. 类的内存模型与参数传递
    4. 4.方法重载与构造函数
    5. 5. 对象的生命周期与垃圾回收
    6. 6.变量的作用域与初始化
  4. Lesson 4_1 Static关键字
    1. 1. 内存中的存储
    2. 2. 静态变量与静态方法
    3. 3. 代码块的执行顺序
    4. 4. 静态内部类
  5. Lesson 4_2 封装
    1. 1. 封装的概念
    2. 2. 访问控制修饰符
    3. 3. 单例模式
    4. 4. 高内聚,低耦合
  6. Lesson 5 继承
    1. 1. Java的继承规则
    2. 2. 构造方法
    3. 3. 覆盖与隐藏
    4. 4. 向上转型
    5. 5. 继承与组合对比
  7. Lesson 6 多态
    1. 1. 多态的两种分类
    2. 2. 动多态的实现
    3. 3. 抽象类
    4. 4. 部分总结
  8. Lesson 7_1 接口
    1. 1. 接口的语法规则
    2. 2. 为什么要用接口
    3. 3. 接口回调(接口多态)
    4. 4. 接口与抽象类对比
  9. Lesson 7_2 复合和双向关联
    1. 1.继承 vs 复合(Is-a vs Has-a)
    2. 2. 双向关联

以申雪萍老师PPT标号为章节。

部分内容来源于AI总结。(每个PPT都接近100页,实在看不完了)

system提示词:

目前你是北航面向对象(OOP Java)课程的老师,你有一些学生因为一些原因没有来上课,但是马上就要期末考试了,这些同学来问你,你一定要把所有知识点都教给他们,让他们听完你的讲授都能拿100分。

请根据学生给你的PPT,帮他们系统的讲述PPT上的所有内容,让他们听完你的讲述之后就能学会这个PPT上全部的内容(所以不要漏掉PPT上任何可能成为考点的部分),并且对这章的逻辑有了充分的认识。

你的学生都很聪明,所以不要用弱智般的比喻或拟人来讲解,但可以用一些非常贴合或者常用的例子或者比喻。

68776b7f9e8d55f4b6420ca5138ac0ef

Lesson 1 概述

1. 各种编程语言区别

执行方式的区别

2. 面向对象编程概念

数据(属性)和操作(行为)封装在一起。数据是主语,函数是谓语。

三要素:

类(Class)与对象(Object)的关系:

3. Java特性

1. Java的历史与版本

2. Java的特性(Features)

3. 运行机制:JVM(Java Virtual Machine) 这是理解“跨平台”的关键:

Lesson 2 面向对象的思维模式

1. 面向对象的思维

我们要把现实世界的东西变成代码,需要经过“抽象”。PPT里提到了静态属性动态属性

面向对象的方法论

  1. 1个工具:抽象 (Abstract)。摒弃细节(比如收银员今天穿什么袜子我们不关心),提取共性(我们需要她的工号和收款能力)。
  2. 2个概念:类 (Class)、对象 (Object)
  3. 3个特性:封装 (Encapsulation)、继承 (Inheritance)、多态 (Polymorphism)。这是OOP的三大支柱。
  4. 4个步骤

    • OOA (分析):做什么?(What)
    • OOD (设计):怎么做?(How - 设计类、接口)
    • OOP (实现):写代码 (Coding)。
    • OOT (测试):验证功能。

2. 类之间的关系

这是考试中最容易混淆的部分。一定要分清以下关系,并在UML图和代码中识别出来:

1. 关联 (Association)

2. 依赖 (Dependency)

3. 聚集/聚合 (Aggregation)

public class ParkingLot {
    private Car[] cars;

    // 考点:Car是在外面创建好,传进来的。
    // 停车场没了,Car对象在外面依然活着。
    public void setCars(Car[] cars) {
        this.cars = cars;
    }
}

4. 组合 (Composition) (PPT P75提到)

public class Person {
    private Heart heart;

    public Person() {
        // 考点:在内部直接new,同生共死!
        // Person对象创建,Heart就创建;Person销毁,Heart也就没了引用被回收。
        this.heart = new Heart(); 
    }
}

5. 泛化 (Generalization)

6. 实现 (Realization)

其中,聚集和组合属于关联,关联的其他情况:并非是整体-部分的关系。只要看到 new 出现在构造方法里面,那就是组合;如果看到对象是 作为参数传进来赋值给成员变量的,那基本就是聚集或一般关联(再根据语境判断是否是整体部分关系),一般关联可能是非构造方法也能修改的。

按照这个流程图:

  1. Q1: 它是成员变量吗?(长期持有)

    • No (是局部变量) $\rightarrow$ 依赖 (Dependency)
    • Yes $\rightarrow$ 进入关联家族判定 (继续Q2)
  2. Q2: 它们是“整体与部分” (Whole-Part) 的关系吗?

    • No (它们是平等的,如老师和学生) $\rightarrow$ 一般关联 (Association)
    • Yes $\rightarrow$ 进入Q3
  3. Q3: 部分(Part)能否脱离整体(Whole)独立生存?

    • Yes (球队解散,球员还在) $\rightarrow$ 聚集 (Aggregation)
    • No (人死灯灭,心脏停止) $\rightarrow$ 组合 (Composition)

3. Java中的内存与变量

内存管理:

image-20251229173301330

数据类型

image-20251229173216861

默认初始值:

数据类型默认初始值备注
byte0注意是数字0
short0
int0高频考点
long0L也是0
float0.0f
double0.0d高频考点
char'\u0000'这是一个空字符(NUL),打印出来看不见,对应的int值是0
booleanfalse高频考点(千万别记成true)

所有引用类型(类、接口、数组),默认值统统是 null

数据类型默认初始值例子
StringnullString s; // s是null
数组nullint[] arr; // arr本身是null
自定义类nullStudent s; // s是null

Lesson 3 类的基本知识

1. 类的设计原则

2. 类的定义与成员变量

1. 类的定义

image-20251229174953476

警告:一个 .java 源文件中可以写多个 class,但是只能有一个 public class,而且这个 public 类的名字必须和文件名完全一致。其他的类(非 public)只能在包内被访问。

类的类型说明符主要有两个:final、abstract

Question:抽象类和接口什么区别?

维度抽象类 (Abstract Class)接口 (Interface)
关键字extendsimplements
继承数量只能继承一个 (单继承)可以实现多个 (多实现)
成员变量可以有各种类型的变量 (private, protected等)全是常量 (默认 public static final)
构造函数 (用于子类初始化父类属性) (接口没有对象的概念,无法实例化)
方法实现可以有抽象方法,也可以有具体实现的普通方法传统上只有抽象方法 (public abstract)
设计目的代码复用 (子类共用父类代码)解耦、定义标准 (你只要符合接口标准,我就能用你)

2. 成员变量与修饰符 (PPT 8-14)

3. this关键字

3. 类的内存模型与参数传递

1. 基本类型 vs 引用类型

2. 内存

(同Lesson 2)

4.方法重载与构造函数

1. 方法重载 (Overloading)

2. 构造函数 (Constructor)

5. 对象的生命周期与垃圾回收

1. 垃圾回收机制 (GC)

2. 匿名对象

6.变量的作用域与初始化

1. 成员变量 vs 局部变量

特性成员变量 (Instance Variable)局部变量 (Local Variable)
定义位置类中,方法外方法内或代码块内
内存位置堆 (Heap)栈 (Stack)
生命周期随对象创建而生,对象消亡而死方法执行时生,方法结束弹栈即死
默认值 (int=0, boolean=false, Object=null) (必须显式赋值才能使用,否则编译报错)

2. tostring方法

Lesson 4_1 Static关键字

这一章节主要关于static关键字的。

1. 内存中的存储

内存图:

  1. 栈 (Stack):存局部变量、引用。
  2. 堆 (Heap):存 new 出来的对象(实例变量)。
  3. 数据区/方法区 (Data/Code Area):存类的元数据、静态变量、代码本身。

核心逻辑:

使用static原因

  1. 共享数据:比如统计“一共造了多少个 Person 对象”?这个计数器不能属于某个人,必须属于“人类”这个概念。
  2. 独立工具:有些方法不需要依赖对象就能用,比如 Math.sqrt(),你不需要造一个 Math 对象就能算平方根。

2. 静态变量与静态方法

1. 静态属性(类变量)

2. 静态方法(类方法)

3. 代码块的执行顺序

  1. 静态代码块 (Static Block)

    • 写法:static { ... }
    • 时机:类加载时执行。
    • 频率:这辈子只执行一次。不管你 new 了多少个对象,它只在第一次用到这个类时跑一次。
    • 作用:通常用来给静态变量赋初值。
  2. 非静态代码块 (Instance Block)

    • 写法:{ ... } (没名字,没 static)
    • 时机:new 对象时,在构造函数之前执行。
    • 频率:每 new 一次,执行一次。
  3. 构造函数 (Constructor)

    • 时机:new 对象时,在非静态代码块之后执行。
    • 频率:每 new 一次,执行一次。

考试口诀(执行顺序): 父类静态 -> 子类静态 -> 父类非静态 -> 父类构造 -> 子类非静态 -> 子类构造

为什么 main 方法是 static 的?

  • public static void main(String[] args)
  • 因为 main 是程序的入口。在程序开始跑的时候,还没有任何对象被创建出来。JVM 必须能通过 类名.main() 直接把程序拉起来。如果它不是 static,JVM 就得先 new 一个主类对象,那由谁来 new 呢?这就陷入死循环了。

4. 静态内部类

static class(静态内部类)。

Lesson 4_2 封装

1. 封装的概念

封装不仅仅是为了“藏”,更是为了“控”。它保证了数据的安全性、一致性,并且让代码更易维护

1. 封装的本质:隐藏内部实现细节,暴露接口

PPT里用了一个很好的例子:台式机箱

2. 封装分为三个层次

封装不仅仅是类级别的,它是系统级的概念:

2. 访问控制修饰符

Java有四种权限:

修饰符同一个类同一个包子类 (不同包)全局 (不同包非子类)
private
(default)
protected✅ (有条件)
public

3. 单例模式

保证一个类在内存中只有唯一一个实例(比如配置文件读取器、日志管理器)。

实现三板斧:

  1. 构造方法私有化 (private Constructor):外界不能 new 了。
  2. 内部持有静态实例 (private static Variable):自己持有自己。
  3. 提供静态公有方法 (public static Method):外界获取实例的唯一入口。

两种写法:

1. 饿汉式 (Hungry Mode) —— 推荐,简单粗暴

public class Singleton {
    // 类加载时直接new出来,"我很饿,先吃为敬"
    private static Singleton instance = new Singleton(); 
    
    private Singleton() {} // 封死构造口
    
    public static Singleton getInstance() {
        return instance;
    }
}

2. 懒汉式 (Lazy Mode)

public class Singleton {
    private static Singleton instance = null; // 先不急着new
    private Singleton() {}
    
    public static Singleton getInstance() {
        if (instance == null) { // 等有人要用了,再new,线程不安全
            instance = new Singleton();
        }
        return instance;
    }
}

4. 高内聚,低耦合

封装的好:

Lesson 5 继承

举例:我们要把公用的特征(属性如age, weight)和行为(方法如eat, sleep)抽取出来,放在一个基类(父类/SuperClass)里,比如Animal。然后让具体的动物(子类/SubClass)去继承它。

1. Java的继承规则

1. 单继承(Single Inheritance)

2. 关键字 extends

3. 子类继承了什么? 这是一个经典的判断题考点

2. 构造方法

1. 初始化的顺序 当你 new 一个子类对象时,内存里发生的事情是这样的:

2. super 关键字在构造器中的用法

因为构造方法里面也可以调用自己的其他构造方法,有this关键字:

3. 覆盖与隐藏

1. 变量隐藏 (Field Hiding)

Parent p = new Child();
System.out.println(p.x); // 输出的是 Parent 的 x,不是 Child 的 x

记住:看左边。引用类型是谁,就访问谁的变量。

2. 方法覆盖 (Method Overriding) (or 重写)

Parent p = new Child();
p.eat(); // 此时运行的是 Child 的 eat() 方法!

记住:看右边。实际对象是谁,就调用谁的方法(动态绑定)。

4. 向上转型

举例:

口诀:“编译看左边,运行(多态)看右边(,运行非多态看左边)”

  1. 调用重写(Override)的方法 —— 看右边

    这是多态的核心!

    • 规则:如果父类有一个方法 eat(),子类重写了 eat()
    • 分析

      • 编译阶段(看左边):编译器检查 Animal 类里有没有 eat() 方法?有,编译通过。
      • 运行阶段(看右边):JVM 发现 a 指向的实际内存对象是 Lion,所以会动态绑定到 Lioneat() 方法。
    • 结果:执行 子类(Lion) 的方法。
  2. 调用隐藏的变量(同名成员变量) —— 看左边

    这是一个巨大的陷阱!请打三个星号。Java中,变量没有多态性!

    • 规则:父类有 String name = "Animal", 子类也有 String name = "Lion"
    • 分析

      • 变量的绑定是静态的,在编译时就确定了。
      • 编译器只看 a 是什么类型?是 Animal。好,那就取 Animalname
    • 结果:获取 父类(Animal) 的变量值。
  3. 调用子类独有的方法 —— 编译报错

    • 规则:子类有 drillHole()(钻火圈),父类没有。
    • 分析

      • 编译阶段(看左边):编译器去查 Animal 类,发现根本没有 drillHole() 这个方法。
      • 编译器会直接报错:Cannot find symbol。它不管你右边是不是狮子,它只知道你现在的身份是“动物”。
    • 结果编译不通过
    • 解决:必须先向下转型(强制类型转换):((Lion)a).drillHole();
  4. 调用子类独有的变量 —— 编译报错

    • 规则:子类有 furColor,父类没有。
    • 分析:同上。编译器看左边 Animal 类里没有这个属性。
    • 结果编译不通过
  5. 调用静态方法(Static Method) —— 看左边

    这是另一个陷阱。如果父类和子类都有同名的 static 方法,这不叫重写,叫隐藏

    • 规则:父类有 static void test(),子类也有 static void test()
    • 分析:静态方法属于,不属于对象。它不参与多态的动态绑定。
    • 编译器看引用类型是 Animal,就直接绑定到 Animal.test()
    • 结果:执行 父类(Animal) 的静态方法。
  6. 调用父类独有的方法 —— 看左边

    • 规则:父类有 breathe(),子类没有重写,也没有删掉(当然也不能删)。
    • 分析:这属于继承的基本功能。子类继承了父类的方法。
    • 结果:执行 父类 的方法(因为子类直接继承过来了,实际上也是子类的方法)。
成员类型检查规则最终执行/访问是谁的?备注
普通实例方法编译看左,运行看右子类 (Lion)这是唯一体现多态的地方!
成员变量编译看左,运行看左父类 (Animal)变量没有多态!
静态方法编译看左,运行看左父类 (Animal)静态方法不看对象!
子类独有方法/变量编译看左编译报错父类里找不到,必须强转

5. 继承与组合对比

PPT最后对比了继承与组合(Composition)。

1. Is-a (继承)

2. Has-a (组合/聚合)

Lesson 6 多态

举例:

1. 多态的两种分类

静多态(编译时多态)

动多态(运行时多态)—— 这是本章的核心!

2. 动多态的实现

三大要素

  1. 继承:必须有父子类关系(或者接口实现)。
  2. 覆盖(Override):子类必须重写父类的方法。
  3. 向上转型父类的引用指向子类的对象

    • 代码长这样:Father f = new Son();

核心:

Father f = new Son(); 时:

(上面Lesson 5里详细阐述了这一点,所以能看出来只有类里面的非静态方法具有多态性)

3. 抽象类

抽象类的使用规则:

  1. 如果有抽象方法,类必须是抽象类。
  2. 抽象类不能被实例化(不能 new Animal()),它只能用来当爹(被继承)或者当引用类型(Animal a = ...)。
  3. 子类继承抽象类,必须重写所有的抽象方法,除非子类自己也是个抽象类。
  4. 抽象方法 不能被 private 、 final 或 static 修饰

4. 部分总结

(1)方法覆盖(Override)的“三一原则”

子类重写父类方法时:

(2)“隐藏”与“覆盖”的区别

多态只对普通实例方法有效。以下三种情况不发生多态(即:不看右边,只看左边引用类型):

(3)构造方法

Lesson 7_1 接口

举例:

1. 接口的语法规则

  1. 定义:用 interface 代替 class
  2. 成员变量:接口里没有变量,只有常量

    • 你写 int a = 1;
    • 编译器自动补全为 public static final int a = 1;(全局静态常量)。
  3. 方法:接口里全是抽象方法(没有方法体)。

    • 你写 void run();
    • 编译器自动补全为 public abstract void run();
    • 注意:考试时问你访问权限,永远是 public
  4. 构造方法:接口没有构造方法(因为不能 new 接口)。
  5. 实现:用 implements 关键字。

    • 一个类可以实现多个接口(implements A, B, C)。
    • 铁律:如果你实现了接口,就必须重写里面所有的方法,否则你这个类就必须变成抽象类。

2. 为什么要用接口

PPT第25页举了 PersonRunnerSwimmer 的例子。

3. 接口回调(接口多态)

原理:

  1. 我定义一个接口 Runner,里面有个 run()
  2. Person 实现了 Runner
  3. 代码这么写:
Runner r = new Person(); // 接口引用指向实现类的对象
r.run();                 // 实际上调用的是 Person 的 run()

这就叫接口回调

应用场景(PPT里的SoundMaker案例):

4. 接口与抽象类对比

特性抽象类 (Abstract Class)接口 (Interface)
关系IS-A (它是...)HAS-A / CAN-DO (它能...)
本质家族血统(亲爹)契约、标准、能力(资格证)
继承限制只能继承一个 (extends)可以实现多个 (implements)
成员变量各种类型都可以只能是 public static final 常量
构造方法 (给子类用)
方法可以有普通方法,也可以有抽象方法全是抽象方法 (Java 8前)

另外的知识:

Lesson 7_2 复合和双向关联

1.继承 vs 复合(Is-a vs Has-a)

(此处复合 == Lesson 2写的组合)

  1. 继承 (Inheritance) —— "Is-a" 关系

    • 定义:子类继承父类,是“是一个”的关系。
    • 例子Student extends Person。学生 人。
    • 特点:耦合度很高。老爸有的(非私有),儿子都有。这是一种很强的绑定。
  2. 复合/包容 (Composite/Aggregation) —— "Has-a" 关系

    • 定义:一个类把另一个类的对象当作自己的成员变量。是“有一个”的关系。
    • 例子:PPT里的电脑(PC)。

      • PC 不是 CPU,也不是 HardDisk
      • 但是 PC 里有 CPU里有 HardDisk
      class CPU { ... }
      class HardDisk { ... }
      
      class PC {
          // 这就叫复合(Has-a)
          private CPU cpu;
          private HardDisk hd;
      
          public void setCPU(CPU c) { this.cpu = c; }
      }

2. 双向关联

PPT用了“学生选课(Student & Subject)”这个案例,演示了代码是如何一步步进化的。这是本章的重难点

场景设定

第一阶段:单向关联(One-way)

第二阶段:双向关联(Two-way)—— "互相拥有"


但是,上面的写法需要添加关系两次(两个类各一次),容易出现数据不一致问题。所以给出了另外一种处理方案:

目标:我只要设置一边,另一边自动同步。 比如,我只要执行 zhangsan.setSubject(csDept),系统自动帮我在 csDept 的名单里加上张三。

Student 类的 setSubject 方法里,不能只做简单的赋值,要多做两件事:

  1. 清理旧关系:如果张三原来是物理系的,得先从物理系名单里把他划掉。
  2. 建立新关系:把张三加到新系(计算机系)的名单里。

参考代码:

// 在 Student 类中
public void setSubject(Subject newSubject) {
    // 1. 如果新系和旧系一样,啥也不用做,省得死循环
    if (this.subject == newSubject) return;

    // 2. 如果原来有系(比如从物理转系),先从旧系的名单里把自己删掉
    if (this.subject != null) {
        this.subject.removeStudent(this);
    }

    // 3. 正常赋值
    this.subject = newSubject;

    // 4. 【关键一步】在新系的名单里,把自己加上
    // 这里的 this 就是当前的 Student 对象
    if (newSubject != null) {
        newSubject.addStudent(this); 
    }
}
// 在 Subject 类中
public void addStudent(Student s) {
    // 1. 把学生加入数组/集合
    this.students.add(s);
    
    // 2. 【回马枪】确保学生那边也指向了这个系
    // 如果学生现在的系不是我,我得让他改成我
    if (s.getSubject() != this) {
        s.setSubject(this);
    }
}