文章目录:
  1. Lesson 8 完善类的设计
    1. 1. 多态
    2. 2. 转型
    3. 3. Object 类与相等性判断
    4. 4.内部类
    5. 5. final关键字用法
  2. Lesson 8.5 UML图
    1. 1. 类:矩形框
    2. 2. 类里面的权限符号
    3. 3. 线条和箭头:类与类的关系
  3. Lesson 9 设计原则与设计模式
    1. 一、 设计原则
      1. 1. UML类图复习
      2. 2. 面向接口/抽象编程
      3. 3. 优先使用组合,少用继承
      4. 4. 开-闭原则 (OCP)
      5. 5. 高内聚-低耦合 (P29-31)
    2. 二、设计模式
      1. 1. 策略模式 (Strategy Pattern) —— 算法的动态切换
      2. 2. 访问者模式 (Visitor Pattern) —— 稳定的数据 vs 变化的操作
      3. 3. 装饰模式 (Decorator Pattern) —— 动态套娃加功能
      4. 4. 适配器模式 (Adapter Pattern) —— 接口转换器
      5. 5. 责任链模式 (Chain of Responsibility) —— 踢皮球/层层审批
      6. 6. 门面/外观模式 (Facade Pattern) —— 统一入口管家
      7. 7. 工厂模式家族 (Factory Family) —— 必考的“造人/造物”
        1. A. 简单工厂 (Simple Factory)
        2. B. 工厂方法 (Factory Method) —— 最常用的标准工厂
        3. C. 抽象工厂 (Abstract Factory) —— 针对“产品族”
  4. Lesson 10 异常
    1. 1. 什么是异常
    2. 2. 异常的结构分类
    3. 3. 如何处理异常
    4. 4. 自定义异常
    5. 5. 考点
  5. Lesson 11 Java集合框架
    1. 1. 集合框架的宏观架构
    2. 2. Collection接口与其子接口(List & Set)
      1. 1. List 接口(有序列表)
      2. 2. Set 接口(唯一集合)
    3. 3. Map 接口(键值对)
    4. 4. 迭代器(Iterator)——如何遍历?
    5. 5. Collections 工具类(注意有个s)
    6. 6. 泛型(Generics)
  6. Lesson 12 GUI设计
  7. Lesson 13 IO
    1. 1. 什么是流
    2. 2. File类
    3. 3. 字节流
    4. 4. 装饰者模式
    5. 5. 字符流与转换
    6. 6. 对象序列化
    7. 7. 基础输入输出
  8. Lesson 14 多线程
    1. 1. 概念
    2. 2.线程的生命周期
    3. 3. 控制线程的API
    4. 4. 创建线程的三种方法
  9. Lesson 14.2 多线程-2
    1. 1. synchronized锁
    2. 2. 线程通信
    3. 3. 零碎考点
    4. 4. 生产者/消费者
  10. Lesson 15 网络
    1. 1. 网络编程的底层逻辑
    2. 2. 基于TCP的网络编程
    3. 3.基于UDP的网络编程
    4. 4. 总结
(一):大二上 面向对象课程 复习

先占位,复习中

Lesson 8 完善类的设计

感觉是又总结了一遍前面的知识,主要想说如何设计代码

1. 多态

多态简而言之就是“同一个接口,使用不同的实例而执行不同操作”。 多态存在的三个必要条件:

核心考点(出现n遍了)

多态的三种形式

2. 转型

向上转型

向下转型

安全锁:instanceof 关键字 :是否为类的实例

为了避免运行时异常,在向下转型前必须判断。

老师的建议:在写代码题时,涉及向下转型,一定要写 if (p instanceof Student) 判断,这能体现你的代码健壮性,是加分项。

3. Object 类与相等性判断

Object 的地位 :它是所有类的根父类。任何类如果没有显式继承别的类,默认继承 Object。

考点:== 与 equals() 的区别

4.内部类

四种内部类及其特点

内部类的应用:(这个我觉得有点难理解)

PPT的例子:

PPT 里的场景是这样的: 有一个父类 Base,它有一个方法叫 adjust(int speed)(调节速度)。 现在你要写一个子类 Sub,你希望它既能调节速度,又能调节温度(实现 Adjustable 接口的 adjust 方法)。

冲突来了

  • 父类有一个 void adjust(int)
  • 接口也有一个 void adjust(int)
  • 如果你的子类 Sub 直接 extends Base implements Adjustable,那么类里只能有一个 adjust 方法。你无法区分你是要调速度还是调温度,因为方法签名一模一样!

内部类如何解决这个问题(PPT 第 87 页逻辑):

  1. 外部类 Sub 继承 Base,保留了调节速度的 adjust
  2. 内部类 Closure 实现接口 Adjustable,在它的 adjust 里专门处理温度。
  3. 对外暴露Sub 提供一个方法 getCallBackReference(),返回这个内部类对象。

这也就是 PPT 想告诉你的核心考点: 通过内部类,我们可以模拟“多重继承”,并且解决“方法名冲突”的问题。

5. final关键字用法

Lesson 8.5 UML图

1. 类:矩形框

在UML里,一个矩形框就代表一个 类(Class)。这个框通常被横线分成三层(三格):

  1. 第一格(最上面):类名 (Class Name)

    • 内容:写这个类的名字。
    • 特殊情况

      • 如果名字是 斜体 的(或者上面有 <<interface>> 字样),说明这是一个 抽象类 (Abstract Class) 或者 接口 (Interface)
      • 如果是正体字,说明是普通的 具体类 (Concrete Class)
  2. 第二格(中间):属性 (Attributes/Fields)

    • 内容:这里写类的成员变量(即代码里的 int age;String name; 这种)。
    • 格式权限符号 变量名 : 变量类型

      • 例如:- height : double
      • 翻译成Java代码就是:private double height;
  3. 第三格(最下面):方法 (Methods/Functions)

    • 内容:这里写类的成员方法。
    • 格式权限符号 方法名(参数名:参数类型) : 返回值类型

      • 例如:+ getArea() : double
      • 翻译成Java代码就是:public double getArea() { ... }

2. 类里面的权限符号

这些符号放在变量名或方法名的最前面,代表这个东西谁能用(即Java中的访问权限):

如果在图里看到方法名下面有一条下划线(比如 main),那代表它是 Static(静态)的。

3. 线条和箭头:类与类的关系

请死记硬背以下四种线条的形状和含义:

  1. 泛化关系 (Generalization) —— “我是你儿子”

    • 图形实线 + 空心三角形箭头(▷)。
    • 含义继承。箭头指向父类。
    • 代码对应extends
    • 例子Circle(圆)连向 Geometry(几何体)。

      class Circle extends Geometry { ... }
  2. 实现关系 (Realization) —— “我遵守合同”

    • 图形虚线 + 空心三角形箭头(▷)。
    • 含义实现接口。箭头指向接口。
    • 代码对应implements
    • 例子Dog 连向 Animal(如果Animal是接口)。

      class Dog implements Animal { ... }

      (注:PPT第17页的 Geometry 如果是接口,就该用虚线箭头)

  3. 关联关系 (Association) —— “我有你”

    • 图形实线箭头(➝)。
    • 含义引用。一个类把另一个类作为成员变量(属性)长期持有。这是一种强关系。
    • 代码对应:成员变量 (Field)。
    • PPT例子(第15页):Pillar(柱子)类里有一个 bottom(底面)变量。图中从 Pillar 画一根实线箭头指向 Circle

      class Pillar {
          private Circle bottom; // 实线箭头指向Circle
      }
  4. 依赖关系 (Dependency) —— “我用到你”

    • 图形虚线箭头(⇢)。
    • 含义临时使用。一个类在某个方法里“临时”用到了另一个类(比如作为参数传进来,或者在方法里new了一个局部变量)。这是一种弱关系,用完就扔。
    • 代码对应:方法参数 (Parameter) 或 局部变量 (Local Variable)。
    • 例子:人(Person)需要用手机(Phone)打电话。

      class Person {
          public void call(Phone p) { // 虚线箭头指向Phone
              p.dial();
          }
      }

Lesson 9 设计原则与设计模式

一、 设计原则

最重要一张图:

image-20260101232524376

1. UML类图复习

考点:给你一张图,你要能认出线条代表什么关系。

2. 面向接口/抽象编程

逻辑:客户代码(调用者)应该只和接口打交道,不和具体类打交道。

3. 优先使用组合,少用继承

逻辑:继承是“白盒复用”,父类细节对子类可见,耦合度太高(父类变,子类必变)。组合是“黑箱复用”,通过持有对象的引用来使用功能,耦合度低。

4. 开-闭原则 (OCP)
5. 高内聚-低耦合 (P29-31)

七个原则没细讲,AI举例:


一、单一职责原则 (SRP - Single Responsibility Principle)

1. 定义:
一个类应该只有一个引起它变化的原因 (There should never be more than one reason for a class to change)。

2. 核心逻辑:
不要把不同的职责揉在一个类里。注意,这里的“职责”不仅仅是“干一件事”,而是指业务上的功能归属

  • 例子: 假设你写了一个 Employee 类,里面既有 calculatePay() (计算薪水),又有 saveToDatabase() (持久化),还有 reportHours() (生成工时报表)。
  • 问题: 财务部门改了薪资算法,你要改这个类;DBA改了数据库Schema,你要改这个类;HR要改报表格式,你还要改这个类。这个类太脆弱了,牵一发而动全身。
  • 修正: 拆分。Employee 只负责数据结构,PayCalculator 负责薪资,EmployeeRepository 负责数据库,HourReporter 负责报表。

3. 考点提醒:
考试如果给你一段代码,发现一个类既在做逻辑计算,又在处理IO(输入输出),或者既在做业务,又在做UI显示,请毫不犹豫地指出它违反了SRP。


二、开闭原则 (OCP - Open/Closed Principle) —— 考试必考

1. 定义:
软件实体(类、模块、函数)应该对扩展开放,对修改关闭 (Open for extension, closed for modification)。

2. 核心逻辑:
这是OOP最核心的原则。当需求变更时,我们希望通过增加新的代码来实现,而不是修改已有的代码

  • 怎么做? 关键在于抽象 (Abstraction)多态 (Polymorphism)
  • 反例: 你写了一个 GraphicEditor,里面有个方法 drawShape(Shape s)

    if (s.type == 1) drawCircle(s);
    else if (s.type == 2) drawRectangle(s);

    如果今天要加一个三角形,你必须去改这个 if-else,这就违反了OCP,因为你“修改”了原有代码。

  • 正例: 定义一个抽象类或接口 Shape,里面有一个抽象方法 draw()。Circle和Rectangle去实现它。GraphicEditor 只需要调用 s.draw()。当你要加三角形时,新建一个 Triangle 类即可,GraphicEditor 完全不用动。

3. 考点提醒:
在设计模式题中,凡是用到“策略模式”、“工厂模式”的地方,通常都是为了满足OCP。如果让你重构一段充斥着 switch-case 或 if-else 的代码,就是让你用多态来实现OCP。


三、里氏替换原则 (LSP - Liskov Substitution Principle) —— 最难理解

1. 定义:
所有引用基类(父类)的地方必须能透明地使用其子类的对象。
换句话说:子类必须能够替换掉父类,并且不改变程序的正确性

2. 核心逻辑:
继承不仅仅是语法上的复用,更是行为上的承诺

  • 经典反例(正方形不是长方形):
    数学上正方形是长方形,但在程序里不是。
    假设 RectanglesetWidth()setHeight()。如果你让 Square 继承 Rectangle,当你重写 setWidth() 时,你被迫也要修改 height(为了保证正方形的性质)。
    此时,如果有一个测试用例:

    void test(Rectangle r) {
        r.setWidth(5);
        r.setHeight(4);
        assert(r.area() == 20); // 如果传入的是Square,这里会失败,因为面积可能是16或25
    }

    这就违反了LSP。

3. 考点提醒 (高分关键):
考试可能会涉及到契约设计 (Design by Contract) 的概念:
为了满足LSP,子类在重写父类方法时:

  • 前置条件 (Preconditions) 不能被加强(不能要求更苛刻的输入)。
  • 后置条件 (Postconditions) 不能被削弱(不能输出更烂的结果)。
  • 不变式 (Invariants) 必须保持。

四、接口隔离原则 (ISP - Interface Segregation Principle)

1. 定义:
客户端不应该依赖它不需要的接口;一个类对另一个类的依赖应该建立在最小的接口上。

2. 核心逻辑:
拒绝“胖接口” (Fat Interface)。

  • 例子: 你有一个 SmartDevice 接口,里面有 print(), fax(), scan()
    现在有一台老式打印机 OldPrinter 实现了这个接口,但它只能打印,不能传真。你被迫在 fax() 里写个空实现或者抛异常。
  • 修正: 拆分成 Printable, Faxable, Scannable 三个接口。OldPrinter 只实现 Printable

3. 考点提醒:
如果考题中出现一个接口里有十几个方法,而实现类只用到了其中两三个,其余都是空实现,这就是违反了ISP。


五、依赖倒置原则 (DIP - Dependency Inversion Principle) —— 架构核心

1. 定义:

  • 高层模块不应该依赖低层模块,二者都应该依赖其抽象。
  • 抽象不应该依赖细节,细节应该依赖抽象。

2. 核心逻辑:
面向接口编程

  • 场景: 既然是面向对象,我们要解耦。

    • 错误: Customer 类直接 new 了一个 SQLServerDAO 类来读取数据。此时高层业务(Customer)依赖了低层细节(SQLServer)。如果换成MySQL,就要改代码。
    • 正确: Customer 依赖一个接口 ICustomerDAOSQLServerDAO 实现这个接口。具体的注入过程(Dependency Injection)交给外部容器或工厂。

3. 考点提醒:
DIP是实现OCP的重要手段。考试画UML图时,注意箭头方向:实现类指向接口,调用者也指向接口。这就是所谓的“倒置”——依赖关系指向了抽象,而不是具体的实现。


六、迪米特法则 (LoD - Law of Demeter) / 最少知识原则

1. 定义:
一个对象应该对其他对象有最少的了解。只与你的直接朋友交谈 (Talk only to your immediate friends)。

2. 核心逻辑:
降低耦合度,避免链式调用。

  • 代码坏味道: objectA.getObjectB().getObjectC().doSomething()
    这叫“由于路过踢了狗一眼”。A严重依赖了B和C的内部结构。如果C变了,A可能都要崩。
  • 修正: 在B里面加一个包装方法。A调用 objectA.getObjectB().doSomethingWrapped()

3. 考点提醒:
看到一长串的点号调用(链式调用除外,特指跨层级访问),基本就是违反LoD。


七、合成复用原则 (CRP - Composite Reuse Principle)

1. 定义:
尽量使用对象组合(Composition)/聚合(Aggregation),而不是继承关系达到软件复用的目的。

2. 核心逻辑:

  • 继承 (Inheritance) 是“白箱复用”,破坏了封装性,父类变了子类不得不变,且静态,编译期就定死了。
  • 组合 (Composition) 是“黑箱复用”,耦合度低,且动态,运行时可以切换(比如通过Setter注入不同的策略对象)。

3. 考点提醒:
当考题问你“如何设计一个既能是电动车又能是燃油车,既能是红色又能是蓝色的车”时,千万别写 RedElectricCar, BlueGasCar 这种类爆炸的继承结构。请用组合:Car 类里包含一个 Engine 成员和一个 Color 成员。

二、设计模式

image-20260101233547747

这也是最有效率的复习方式:理论对应代码,一眼看穿本质

既然马上要期末考试了,我们不玩虚的。我把PPT里的核心文字考点(场景、案例、结构、优缺点)和我写的辅助理解代码完美融合在一起。

你只要把下面这 7 个模块看懂,Lesson 9 这章的设计模式大题(通常是画图或填空写代码)你就能拿满分。


1. 策略模式 (Strategy Pattern) —— 算法的动态切换

【核心文字考点】


2. 访问者模式 (Visitor Pattern) —— 稳定的数据 vs 变化的操作

【核心文字考点】


3. 装饰模式 (Decorator Pattern) —— 动态套娃加功能

【核心文字考点】

【代码辅助理解】

// 1. 抽象组件 (Component)
interface Bird {
    void fly();
}

// 2. 具体组件 (ConcreteComponent):普通的麻雀
class Sparrow implements Bird {
    public void fly() { System.out.println("普通飞行:飞了100米"); }
}

// 3. 装饰器基类 (Decorator) —— 考试画图重点
// 【继承】:为了保持接口一致,能当鸟用
abstract class Decorator implements Bird { 
    // 【组合】:为了持有原对象
    protected Bird bird; 
  
    public Decorator(Bird bird) { this.bird = bird; }
  
    public void fly() { 
        bird.fly(); // 默认行为:还是调原来的飞
    } 
}

// 4. 具体装饰 (ConcreteDecorator):电子翅膀
class WingDecorator extends Decorator {
    public WingDecorator(Bird bird) { super(bird); }
  
    // 重写方法,增加功能
    public void fly() {
        super.fly(); // 先执行原来的飞行(飞100米)
        eleFly();    // 再执行增强的功能
    }
  
    private void eleFly() { System.out.println("电子助推:额外飞了50米"); }
}

// --- 考试时的用法演示 ---
Bird b = new Sparrow(); // 1. 创建一只普通鸟
Bird superBird = new WingDecorator(b); // 2. 把它包装进装饰器
superBird.fly(); 
// 输出结果:
// 普通飞行:飞了100米
// 电子助推:额外飞了50米

// 如果想装两个翅膀?继续套娃:
Bird ultraBird = new WingDecorator(new WingDecorator(new Sparrow()));

4. 适配器模式 (Adapter Pattern) —— 接口转换器

【核心文字考点】


5. 责任链模式 (Chain of Responsibility) —— 踢皮球/层层审批

【核心文字考点】

【代码辅助理解】

// 1. 处理者抽象类 (Handler)
abstract class Leader {
    // 【核心结构】:持有下一个领导的引用
    protected Leader nextLeader; 
  
    // 设置链条的下一环
    public void setNext(Leader next) { this.nextLeader = next; }
  
    // 抽象的处理方法
    public abstract void handleRequest(int days);
}

// 2. 具体处理者:班主任
class Teacher extends Leader {
    public void handleRequest(int days) {
        if (days <= 2) {
            System.out.println("班主任:准了!");
        } else {
            // 【核心逻辑】:搞不定,传给下家
            if (nextLeader != null) {
                System.out.println("班主任:权限不够,转交给上一级...");
                nextLeader.handleRequest(days);
            }
        }
    }
}

// 3. 具体处理者:院长
class Dean extends Leader {
    public void handleRequest(int days) {
        if (days <= 10) {
            System.out.println("院长:准了!");
        } else {
            System.out.println("院长:这假太长了,退学吧!");
        }
    }
}

// --- 考试时的用法演示 ---
Leader teacher = new Teacher();
Leader dean = new Dean();
// 组装责任链:班主任 -> 院长
teacher.setNext(dean); 

// 学生直接找班主任请5天假
// 班主任处理不了,会自动转给院长
teacher.handleRequest(5); 

6. 门面/外观模式 (Facade Pattern) —— 统一入口管家

【核心文字考点】


7. 工厂模式家族 (Factory Family) —— 必考的“造人/造物”

这里有三个递进的模式,考试时一定要分清题目描述的是哪一种!

A. 简单工厂 (Simple Factory)
class NvwaFactory {
    public static Person makePerson(String type) {
        if (type.equals("M")) return new Man();
        else if (type.equals("W")) return new Woman();
        // 缺点:想造 Robot?必须在这里改代码加 else if
        else return null; 
    }
}
B. 工厂方法 (Factory Method) —— 最常用的标准工厂
// 1. 工厂接口
interface PenCoreFactory {
    PenCore createCore(); // 不传参,由子类决定造啥
}

// 2. 具体工厂:红笔工厂
class RedCoreFactory implements PenCoreFactory {
    public PenCore createCore() { return new RedPenCore(); }
}

// 3. 具体工厂:蓝笔工厂
class BlueCoreFactory implements PenCoreFactory {
    public PenCore createCore() { return new BluePenCore(); }
}
C. 抽象工厂 (Abstract Factory) —— 针对“产品族”
// 1. 抽象工厂:能生产一套东西(电视+空调)
interface AppFactory {
    TV createTV();   // 生产电视
    AC createAC();   // 生产空调
}

// 2. 具体工厂:海尔工厂 (Haier Family)
// 保证了造出来的电视和空调是配套的
class HaierFactory implements AppFactory {
    public TV createTV() { return new HaierTV(); }
    public AC createAC() { return new HaierAC(); }
}

// 3. 具体工厂:TCL工厂 (TCL Family)
class TCLFactory implements AppFactory {
    public TV createTV() { return new TCLTV(); }
    public AC createAC() { return new TCLAC(); }
}

区别:


如果不确定用什么模式:先看需求。

Lesson 10 异常

1. 什么是异常

在Java中,异常(Exception)就是程序正常指令流被中断的事件。

三种错误:

2. 异常的结构分类

1. 祖宗类:Throwable 所有的异常和错误都继承自 java.lang.Throwable。它有两个直系儿子:ErrorException

2. 大儿子:Error(不可抗力)

3. 二儿子:Exception(程序可处理) 这是我们写代码要关心的。它又分为两派,这是重中之重的区别:

java.lang.Object
   └── java.lang.Throwable
        ├── java.lang.Error (天灾,不管它)
        │      ├── OutOfMemoryError
        │      └── StackOverflowError
        │
        └── java.lang.Exception (我们要处理的)
               │
               ├── RuntimeException (逻辑错误,编译器不强制查)
               │      ├── NullPointerException
               │      ├── ArrayIndexOutOfBoundsException
               │      ├── ArithmeticException
               │      ├── ClassCastException
               │      └── NumberFormatException
               │
               └── (其他Checked Exception) (外部意外,编译器强制查)
                      ├── IOException
                      │      └── FileNotFoundException
                      ├── SQLException
                      └── ClassNotFoundException

3. 如何处理异常

机制叫抓抛模型

1. 捕获异常:try-catch-finally 这是处理异常的“盾牌”。

try {
    // 监控区:把可能出事的代码放这里
    // 一旦这里某行代码报错,程序立即跳到catch,后续try里的代码不执行
} catch (ExceptionType1 e) {
    // 捕获区:出了ExceptionType1这种错,怎么办
} catch (ExceptionType2 e) {
    // 捕获区:出了ExceptionType2这种错,怎么办
} finally {
    // 终结区:不管有没有错,这里一定会被执行
}

关键考点(必须记住):

  1. Catch的顺序:如果有多个catch块,子类异常必须放在父类异常前面

    • 比喻:如果你先catch了 Exception(万能捕获),后面专门catch IOException 的代码就永远执行不到了(因为被万能的截胡了)。编译器会报错。
  2. Finally的作用:通常用来释放资源(关闭文件流、数据库连接)。即使 trycatch 中有 return 语句,finally 也会在 return 之前(准确说是return逻辑组装好准备返回前)执行。

    • 特例:只有 System.exit(0) 会强制杀掉JVM,finally 才不会执行。

2. 声明异常:throws(甩锅) 这是处理异常的“推卸责任”。 如果当前方法不知道怎么处理,或者不想处理,就在方法签名上用 throws 告诉调用者:“我可能会出这个问题,你自己看着办”。

public void readFile() throws IOException { 
    // ... 
}

4. 自定义异常

1. throw 关键字(注意没有s)

if (money < 0) {
    throw new IllegalArgumentException("钱不能是负数"); // 程序在此终止,跳出
}

2. 自定义异常

Java自带的异常不够用时(比如PPT里的“卡无法识别”、“余额不足”),我们需要自己造异常。

案例逻辑(PPT P79 Worker与Car例子):

5. 考点

1. 方法重写(Override)中的异常限制

2. returnfinally 的爱恨情仇

try {
    return 1;
} finally {
    System.out.println("finally");
}

3. 输出预测题

Lesson 11 Java集合框架

1. 集合框架的宏观架构

Java集合框架主要由两个根接口派生出来:

  1. Collection 接口:处理单列数据(一组对象)。

    • 它有两个主要的亲儿子:List(有序、可重复)和 Set(无序、不可重复)。
  2. Map 接口:处理键值对(Key-Value)数据(双列数据)。

    • 它和Collection没有继承关系!这是常考的坑点。

考试重点:


2. Collection接口与其子接口(List & Set)

1. List 接口(有序列表)

List最典型的特征是:有序,指元素的存入顺序和取出顺序一致。

这里有两个绝对的必考点,就是ArrayListLinkedList的区别:

2. Set 接口(唯一集合)

Set注重独一无二


3. Map 接口(键值对)

Map是独立的,专门处理 Key -> Value 的映射。

核心实现类对比(必考):

  1. HashMap

    • 最常用。
    • 线程不安全,效率高。
    • 允许null作为Key或Value。
  2. Hashtable(注意t是小写):

    • 老古董,继承自Dictionary类。
    • 线程安全,效率低。
    • 不允许null作为Key或Value(否则抛异常)。

常用方法:


4. 迭代器(Iterator)——如何遍历?

对于List,你可以用 for(int i=0; ...) 循环,因为List有索引。 但是对于Set和Map,没有索引,怎么遍历?这就需要Iterator

注意:在使用Iterator遍历时,不要直接用集合自己的remove方法删除元素,否则会抛出ConcurrentModificationException异常,要用迭代器自己的remove()方法。


5. Collections 工具类(注意有个s)

PPT考点提炼: 这也是一个经典混淆点:

Collections常用功能


6. 泛型(Generics)

在泛型出现之前,集合里装的都是Object

Lesson 12 GUI设计

(似乎范围没说?)

Lesson 13 IO

维度字节流 (Byte)字符流 (Char)
应用场景图片、视频、二进制数据纯文本 (.txt, .java)
顶层父类InputStream / OutputStreamReader / Writer
节点流
(直接接文件)
FileInputStream
FileOutputStream
FileReader
FileWriter
处理流
(增强功能)
BufferedInputStream
DataInputStream
ObjectInputStream
BufferedReader (有readLine)
PrintWriter (有println)
转换流
(字节变字符)
InputStreamReader
OutputStreamWriter

1. 什么是流

在Java中,流就是数据传输的通道。

Java把流分成了两个维度:

  1. 按流向分(以内存/程序为中心):

    • 输入流 (Input/Reader):外界 -> 内存(读)。
    • 输出流 (Output/Writer):内存 -> 外界(写)。
    • 记忆技巧: 想象你在下载东西,是Input;你在上传东西,是Output。
  2. 按处理单元分(核心考点):

    • 字节流 (Byte Stream):操作8位二进制(byte)。万能流,处理图片、视频、音频、可执行文件必须用它。
    • 字符流 (Character Stream):操作16位Unicode字符(char)。文本专用,处理txt、java、html等文本文件,解决了中文乱码问题。

基类体系:

维度字节流 (Byte)字符流 (Char)
输入InputStream (抽象类)Reader (抽象类)
输出OutputStream (抽象类)Writer (抽象类)

2. File类

在读写文件内容之前,我们得先能找到文件。这就是File类的作用。

File只管理文件/目录的属性(如路径、大小、是否存在、创建时间),不涉及文件的具体内容读写。不要试图用File类去读文件里的文字!

常用API(考试编程题可能用到):

import java.io.File;
import java.io.IOException;

public class FileDemo {
    public static void main(String[] args) {
        // 考点:跨平台路径分隔符使用 File.separator,不要死写 "\\"
        String path = "data" + File.separator + "test.txt"; 
        File f = new File(path);

        // 1. 检查是否存在
        if (!f.exists()) {
            // 获取父目录对象
            File parent = f.getParentFile();
            // 考点:mkdirs() 创建多级目录(推荐),mkdir() 只能创建一级
            if (parent != null && !parent.exists()) {
                parent.mkdirs(); 
            }
            try {
                // 创建空文件
                boolean created = f.createNewFile();
                System.out.println("文件创建成功: " + created);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // 2. 获取属性 (PPT 32-34)
        System.out.println("绝对路径: " + f.getAbsolutePath());
        System.out.println("文件大小(字节): " + f.length());
        System.out.println("是目录吗? " + f.isDirectory());
        
        // 3. 遍历目录
        File dir = new File(".");
        String[] files = dir.list(); // 返回文件名数组
        // File[] files = dir.listFiles(); // 返回File对象数组(更常用)
    }
}

3. 字节流

1. 节点流(直接连在文件上的管子):

2. 读写操作的核心方法(抽象类定义的方法):

import java.io.*;

public class ByteCopy {
    public static void main(String[] args) {
        // 语法糖:try(...) 括号里的资源会在结束后自动调用 close(),
        // 考试写这个能加分(展示你懂JDK7+新特性),如果不会写就用 finally 块关流。
        try (
            FileInputStream fis = new FileInputStream("input.jpg");
            // 考点:第二个参数 true 代表追加模式,不写默认为覆盖模式(false)
            FileOutputStream fos = new FileOutputStream("output.jpg", false) 
        ) {
            // 核心逻辑:建立缓冲区(PPT 62页示例的升级版,一次读一个字节太慢了,要读一组)
            byte[] buffer = new byte[1024]; // 1KB的缓冲区
            int len; // 记录实际读取到的字节数

            // 死背这个while循环!
            // fis.read(buffer) 会把数据填入buffer,并返回读到的个数。
            // 如果读到文件末尾,返回 -1。
            while ((len = fis.read(buffer)) != -1) {
                // 写出数据:从 buffer 的 0 开始,写 len 这么长
                // 严禁写 fos.write(buffer),否则最后一次读取如果没填满,会写入垃圾数据
                fos.write(buffer, 0, len);
            }
            System.out.println("复制完成");

        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

4. 装饰者模式

PPT里提到了“Filter流”、“Data流”、“Buffered流”,这其实运用了装饰者模式(Decorator Pattern)

比喻: 节点流(如FileInputStream)是裸露的水管,虽然能用水,但不方便。处理流就是在裸管外面包了一层层特殊功能的管套。

1. 缓冲流 (BufferedInputStream / BufferedOutputStream)

2. 数据流 (DataInputStream / DataOutputStream)

3. 打印流 (PrintStream)

4. 管道流 (PipedInputStream / PipedOutputStream)

5. 字符流与转换

1. 转换流——字节到字符的桥梁 (InputStreamReader / OutputStreamWriter) —— *难点*

2. 便捷流 (FileReader / FileWriter)

3. 缓冲字符流 (BufferedReader / BufferedWriter) —— *编程题大杀器*

6. 对象序列化

7. 基础输入输出

  1. 命令行参数: public static void main(String[] args) 中的 args 接收命令行输入的字符串数组。
  2. Scanner类: java.util.Scanner

    • next(): 读到空格为止。
    • nextLine(): 读完一整行(包括空格)。
    • nextInt() / nextDouble(): 读取特定类型。

Lesson 14 多线程

1. 概念

进程间资源独立,线程间资源共享。

  • 一个进程可以有多个线程(比如浏览器是一个进程,里面有下载线程、渲染线程)。
  • 同一进程内的线程共享:堆内存(Heap)、方法区(Code/Data)。这意味着它们可以访问同一个全局变量(这也导致了后面的同步问题)。
  • 每个线程独有:程序计数器(PC,记录执行到哪一行了)、虚拟机栈(Stack,记录局部变量和方法调用链)。这一点非常重要,线程切换快就是因为不需要切换内存页表,只要切换栈和寄存器。

2.线程的生命周期

1. 五大状态(状态流转图) 想象一个线程的一生:

  1. 新建 (New)new Thread(),此时它只是个Java对象,还没进操作系统队列。
  2. 就绪 (Runnable):调用了 start()注意:调用start不代表立即运行,而是告诉CPU“我准备好了,随时可以翻我牌子”。
  3. 运行 (Running):CPU真的分配时间片给它了,开始执行 run() 方法。
  4. 阻塞 (Blocked):因为某些原因(等I/O、睡着了sleep、等锁synchronized、等别人join)暂停了。阻塞结束后,不能直接回到Running,必须先回Runnable排队
  5. 终止 (Terminated)run() 执行完了,或者抛异常挂了

2. 调度策略

Java使用的是抢占式调度 (Preemptive),不是分时调度。

3. 控制线程的API

  1. start() vs run() (必考)
  1. sleep() vs yield() (必考,背下这个对比)

这两个都是静态方法 Thread.sleep(), Thread.yield()

特性sleep(long millis)yield()
状态变化Running -> Blocked (阻塞)Running -> Runnable (就绪)
异常抛出 InterruptedException (必须捕获)不抛出异常
后续执行睡醒后去排队放弃当前时间片,重新去排队
优先级不在乎优先级,谁都能运行只让给同级或更高优先级的线程
用途模拟延时、定时等待调试、测试并发性(实际开发很少用)
  1. sleep() vs wait() (深层考点)

PPT稍微提到了 wait(虽然属于同步章节,但这里必须对比)。

  1. join()

4. 创建线程的三种方法

方式一:继承 Thread

逻辑:把类变成线程本身。
缺点:Java是单继承,继承了 Thread 就不能继承别的类了,灵活性差。

// 1. 定义:继承 Thread
class MyThread extends Thread {
    private String name;
    public MyThread(String name) { this.name = name; }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + " 运行: " + i);
        }
    }
}

// 2. 使用
public class Test1 {
    public static void main(String[] args) {
        MyThread t1 = new MyThread("线程A");
        t1.start(); // 千万别写成 t1.run()
    }
}

方式二:实现 Runnable 接口(推荐)

逻辑:把任务(Task)和线程(Thread)分离。解耦,且可以实现资源共享。
代码模式new Thread(Runnable target).start()

// 1. 定义:实现 Runnable
class MyTask implements Runnable {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 正在执行任务");
    }
}

// 2. 使用
public class Test2 {
    public static void main(String[] args) {
        MyTask task = new MyTask();
        // 需要把任务丢给 Thread 对象
        Thread t1 = new Thread(task, "线程A");
        Thread t2 = new Thread(task, "线程B");
        
        t1.start();
        t2.start();
    }
}

方式三:实现 Callable 接口(JDK 5.0+,高级)

逻辑Runnablerun() 返回值是 void 且不能抛出受检异常。如果你需要线程算完告诉你结果,或者想捕获异常,就用 Callable
考点:需要配合 FutureTask 使用。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

// 1. 定义:实现 Callable<返回值类型>
class MyCallTask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 1; i <= 100; i++) sum += i;
        return sum; // 能返回结果!
    }
}

// 2. 使用
public class Test3 {
    public static void main(String[] args) {
        // 创建 Callable 对象
        MyCallTask callTask = new MyCallTask();
        // 包装成 FutureTask (它同时实现了 Runnable 和 Future)
        FutureTask<Integer> futureTask = new FutureTask<>(callTask);
        
        // 启动线程
        new Thread(futureTask).start();
        
        try {
            // 获取结果,get() 方法会阻塞主线程,直到子线程算完
            Integer result = futureTask.get();
            System.out.println("计算结果是:" + result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

三种方式对比总结(考试必写):

  1. Thread:简单,但有单继承限制。
  2. Runnable:逻辑分离,支持多实现,适合多个线程共享同一个资源(Target),最常用
  3. Callable:有返回值,能抛异常,用 Future 拿结果。

Lesson 14.2 多线程-2

核心逻辑: 多线程最大的危险在于共享数据。PPT里举了“北京上海同时操作同一个银行账户”的例子。

1. synchronized锁

1. 锁的本质:
锁的是对象,不是代码!
synchronized 就像是给某个对象加了一把锁,想要执行这段代码的线程,必须先拿到这个对象的钥匙。

2. 三种写法(必考):

它防的是同一个对象被并发访问。如果线程A访问 obj1,线程B访问 obj2,他俩是不冲突的。

2. 线程通信

光有锁还不够,线程之间还得“聊天”。

场景:你要从卡里取钱,但没钱了。你不能死循环一直问“有钱没?有钱没?”,这样CPU会爆炸。

正确做法:没钱你就等待(Wait),等你女朋友存了钱,她通知(Notify)你醒来。

1. 核心方法(背下来):

2. 必考面试题:sleep() 和 wait() 的区别?:

3. 零碎考点

  1. 管道流(PipedStream,46-50页)

    • 作用:线程之间直接传输数据,像水管一样。
    • 考点:输入流和输出流必须connect(连接)起来才能用。
  2. 死锁(Deadlock,51-53页)

    • 定义:你等我,我等你,大家都卡死。
    • 原因:不是电脑坏了,是逻辑设计错了(比如嵌套锁:A锁里拿B锁,B锁里拿A锁)。
  3. 守护线程(Daemon Thread,54-57页)

    • 概念:后台服务的备胎线程,比如垃圾回收(GC)。
    • 特性:如果所有的普通线程(前台线程)都结束了,守护线程会自动陪葬(立即终止)。
    • 用法:thread.setDaemon(true) 必须在 start() 之前设置。
  4. 定时器(Timer,58-61页)

    • 知道有 TimerTimerTask 这两个类,能做定时任务即可。

4. 生产者/消费者

这个“生产者-消费者”模型(Producer-Consumer Pattern)是多线程编程的“圣经”。


第一步:定义“馒头筐”(共享资源)

这是最关键的类。所有的逻辑(加锁、判断满没满、wait、notify)都写在这里面。不要写在生产者或者消费者里,那样会很乱。

核心逻辑:

  1. 必须要有个容器(比如数组或List)装馒头。
  2. 生产方法 push():满了就等待,不满就放,放完喊人来吃。
  3. 消费方法 pop():空了就等待,不空就拿,拿完喊人来做。
// 1. 馒头筐 (共享资源)
class Basket {
    // 假设筐里最多能装 10 个馒头
    private int max = 10;
    // 当前筐里的馒头数量
    private int count = 0;

    // --- 生产馒头的方法 ---
    // 加上 synchronized,因为要保证 count 的安全
    public synchronized void produce() {
        // 1. 判断:如果满了吗?
        // 注意:这里必须用 while,不能用 if!
        // 为什么?因为线程被 notify 唤醒后,需要再次检查条件是否满足。
        while (count == max) {
            try {
                System.out.println("筐满了,生产者 " + Thread.currentThread().getName() + " 开始等待...");
                this.wait(); // 满了就睡,释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 2. 干活:没满,就做一个馒头
        count++;
        System.out.println(Thread.currentThread().getName() + " 生产了一个馒头,当前库存:" + count);

        // 3. 通知:喊醒消费者(可能消费者之前因为没馒头在 wait)
        this.notifyAll(); // 喊醒所有睡着的人
    }

    // --- 消费馒头的方法 ---
    public synchronized void consume() {
        // 1. 判断:如果空了吗?
        while (count == 0) {
            try {
                System.out.println("筐空了,消费者 " + Thread.currentThread().getName() + " 开始等待...");
                this.wait(); // 没得吃就睡,释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        // 2. 干活:没空,就吃一个馒头
        count--;
        System.out.println(Thread.currentThread().getName() + " 吃掉了一个馒头,当前库存:" + count);

        // 3. 通知:喊醒生产者(可能生产者之前因为满了在 wait)
        this.notifyAll();
    }
}

第二步:定义“厨师”(生产者线程)

厨师的任务很简单:死循环一直做馒头。

// 2. 厨师 (生产者线程)
class Producer implements Runnable {
    private Basket basket;

    public Producer(Basket basket) {
        this.basket = basket;
    }

    @Override
    public void run() {
        while (true) {
            basket.produce(); // 调 basket 的方法
            try {
                // 模拟做馒头需要一点时间,不让刷屏太快
                Thread.sleep(500); 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

第三步:定义“吃货”(消费者线程)

吃货的任务也很简单:死循环一直吃馒头。

// 3. 吃货 (消费者线程)
class Consumer implements Runnable {
    private Basket basket;

    public Consumer(Basket basket) {
        this.basket = basket;
    }

    @Override
    public void run() {
        while (true) {
            basket.consume(); // 调 basket 的方法
            try {
                // 模拟消化馒头需要一点时间
                Thread.sleep(800); 
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

第四步:主程序(组装运行)

把大家凑到一起。

public class MainTest {
    public static void main(String[] args) {
        // 1. 只有这一个筐!这是重点!
        Basket basket = new Basket();

        // 2. 招 2 个厨师
        new Thread(new Producer(basket), "厨师大胖").start();
        new Thread(new Producer(basket), "厨师二胖").start();

        // 3. 招 3 个吃货
        new Thread(new Consumer(basket), "吃货小明").start();
        new Thread(new Consumer(basket), "吃货小红").start();
        new Thread(new Consumer(basket), "吃货小刚").start();
    }
}

如果考试让你手写,你要检查自己有没有漏掉这4个关键点

  1. synchronized:写在了 produce()consume() 方法上。没加锁就是0分。
  2. while循环检查while (count == max)。如果写成 if (count == max),虽然大部分时候能跑,但在多消费者情况下会出Bug(虚假唤醒),老师会扣分。
  3. wait():条件不满足(满了或空了)时调用。
  4. notifyAll():干完活(生产完或吃完)后调用。推荐写 notifyAll() 而不是 notify(),防止因为运气不好只唤醒了同伴(比如生产者唤醒了生产者),导致程序卡死。

把这个代码结构在脑子里过一遍:
Basket类管核心逻辑(wait/notify) -> Producer只管调produce -> Consumer只管调consume -> Main里大家共用一个Basket对象。

Lesson 15 网络

1. 网络编程的底层逻辑

PPT的前13页讲的都是计算机网络的基础,Java编程是建立在这些概念之上的。

1. 网络架构模式(考点:区分)

2. IP与端口(核心考点:理解寻址)
网络通信解决两个问题:找到电脑,找到电脑上的程序。

3. 协议:TCP vs UDP(必考!背诵全文)
PPT第9页重点讲了这个,必须死记硬背它们的区别:


2. 基于TCP的网络编程

这是PPT的第18-54页,也是最容易出编程题的地方。Java中通过 SocketServerSocket 两个类来实现。

1. 逻辑模型(类似“114查号台”模型)
PPT第21页那个比喻很关键,请理解这个逻辑:

2. 核心代码流程(背下这个步骤,代码题满分)

服务器端 (Server) 编写步骤:

  1. 建站:创建 ServerSocket,绑定监听端口(比如8888)。
  2. 等待:调用 accept() 方法。注意:这个方法是阻塞的,没人连它就一直卡在这里不动。
  3. 握手:一旦有人连上,accept() 返回一个普通的 Socket 对象,这个对象代表了与那个特定客户端的连接。
  4. IO操作:通过这个 Socket 拿到输入流 getInputStream(听)和输出流 getOutputStream(说)。
  5. 关闭:关流,关Socket。
// Server端伪代码
ServerSocket server = new ServerSocket(8888); // 1. 建站
System.out.println("等待连接...");
Socket socket = server.accept(); // 2. 阻塞等待,直到Client连上,返回一个专线socket
// 3. 拿到流
InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();
// ...读写操作...
socket.close();
server.close();

客户端 (Client) 编写步骤:

  1. 呼叫:创建 Socket,指定服务器的IP和端口。这一步一执行,自动进行TCP“三次握手”建立连接。
  2. IO操作:拿到输入流和输出流。
  3. 关闭
// Client端伪代码
Socket socket = new Socket("127.0.0.1", 8888); // 1. 呼叫服务器
// 2. 拿到流
OutputStream os = socket.getOutputStream(); 
InputStream is = socket.getInputStream();
// ...读写操作...
socket.close();

3. 进阶考点:多线程聊天(PPT 40-50页)
为什么PPT中间花了大量篇幅讲多线程?


3.基于UDP的网络编程

PPT第55-66页。UDP没有客户端和服务器的严格区分,只有“发送端”和“接收端”,或者叫对等节点。

1. 核心类

2. 核心代码流程

发送端:

  1. 建立码头:DatagramSocket
  2. 准备数据:把字符串转成字节数组 byte[]
  3. 打包(关键):创建 DatagramPacket必须指定目标IP和端口
  4. 发送:socket.send(packet)
// 发送端
DatagramSocket ds = new DatagramSocket();
byte[] buf = "Hello".getBytes();
// 封包:数据,长度,目标地址,目标端口
DatagramPacket dp = new DatagramPacket(buf, buf.length, InetAddress.getByName("127.0.0.1"), 9999);
ds.send(dp);
ds.close();

接收端:

  1. 建立码头:DatagramSocket(9999)必须指定监听端口,不然没人找得到你。
  2. 准备空容器:创建一个空的 byte[]
  3. 准备接收包:创建空的 DatagramPacket
  4. 接收:socket.receive(packet)。这是阻塞方法。
  5. 拆包:从 packet 里把数据取出来。
// 接收端
DatagramSocket ds = new DatagramSocket(9999); // 监听9999
byte[] buf = new byte[1024];
DatagramPacket dp = new DatagramPacket(buf, buf.length); // 空包
ds.receive(dp); // 阻塞等待
// 拆包
String msg = new String(dp.getData(), 0, dp.getLength());
ds.close();

4. 总结

  1. 区分Socket类型

    • ServerSocket 只用于TCP服务器端。
    • Socket 用于TCP客户端和服务器端的通信专线。
    • DatagramSocket 用于UDP通信(不分服务端客户端)。
  2. 区分数据传输方式

    • TCP用的是 IO 流 (InputStream, OutputStream),像打电话,线连着。
    • UDP用的是 数据包 (DatagramPacket),像寄信,一个包一个包发。
  3. 常见异常

    • 如果服务器没开,客户端直接连,会报 ConnectException
    • 端口如果被占用了,会报 BindException
  4. 常用方法名

    • TCP Server: accept()
    • UDP: send(), receive()
    • IP类: InetAddress.getByName()
  5. 逻辑题

    • 如果题目让你写一个“能够同时服务多个客户端”的TCP服务器,必须使用多线程。Server每 accept 到一个 Socket,就把它扔给一个新的 Thread 去处理,主线程回去继续 accept。(这是PPT第51页的思考题核心)。