Loading...

文章背景图

Day 007-知识点汇总

2026-02-11
2
-
- 分钟

第一部分:单例设计模式

1. 单例设计模式的概念

Q: 什么是设计模式?单例设计模式解决什么问题?

A: 设计模式是解决特定问题的通用解决方案模板,是从经验中总结出来的最佳实践。

单例设计模式解决的问题:确保一个类在内存中只有一个对象,节省内存。

实现方案

  • 饿汉式:类加载时就创建对象(急切创建)
  • 懒汉式:需要时才创建对象(延迟加载)
// 问题场景:多个对象浪费内存
public class NormalClass {
    // 可以随意创建多个对象
}

// 解决方案:单例模式限制只能有一个对象
public class Singleton {
    // 构造器私有,防止外部new
    private Singleton() {}
  
    // 内部创建唯一对象
    private static Singleton instance = new Singleton();
  
    // 提供公共获取方法
    public static Singleton getInstance() {
        return instance;
    }
}

// 使用对比
public class Demo {
    public static void main(String[] args) {
        // 普通类:可以创建多个对象
        NormalClass n1 = new NormalClass();
        NormalClass n2 = new NormalClass();
        System.out.println(n1 == n2);  // false,不同对象
  
        // 单例类:只能获取同一个对象
        Singleton s1 = Singleton.getInstance();
        Singleton s2 = Singleton.getInstance();
        System.out.println(s1 == s2);  // true,同一对象
    }
}

2. 饿汉式单例模式

Q: 饿汉式单例模式的实现步骤是什么?

A: 饿汉式:类加载时就创建对象,"急切"地创建实例。

实现步骤

  1. 把类的构造器私有(防止外部new)
  2. 定义一个静态变量记住类的一个对象
  3. 定义一个静态方法,返回对象
public class A {
    // 第一步:把类的构造器私有
    private A() {
        System.out.println("A类构造器执行");
    }
  
    // 第二步:定义一个静态变量记住类的一个对象
    // 类加载时就创建对象(饿汉:很急切)
    private static A a = new A();
  
    // 第三步:定义一个静态方法,返回对象
    public static A getInstance() {
        return a;
    }
  
    public void showMessage() {
        System.out.println("Hello, Singleton!");
    }
}

// 测试类
public class Demo011 {
    public static void main(String[] args) {
        // 单例模式对象是无法自己new的
        // A a0 = new A();  // ❌ 报错!构造器私有
  
        // 获取A对象
        A a1 = A.getInstance();
        A a2 = A.getInstance();
  
        System.out.println(a1);  // com.itheima.A@6d06d69c
        System.out.println(a2);  // com.itheima.A@6d06d69c
  
        // 结论:获取的单例对象内存地址一样,只有1个对象
        System.out.println(a1 == a2);  // true
  
        a1.showMessage();  // Hello, Singleton!
    }
}

3. 懒汉式单例模式

Q: 懒汉式单例模式的实现步骤是什么?与饿汉式有什么区别?

A: 懒汉式:类加载时不创建对象,第一次需要时才创建(延迟初始化)。

实现步骤

  1. 把类的构造器私有
  2. 定义一个静态变量用于存储对象(不初始化)
  3. 提供一个静态方法,判断并创建对象返回
public class B {
    // 第一步:把类的构造器私有
    private B() {
        System.out.println("B类构造器执行");
    }
  
    // 第二步:定义一个静态变量用于存储对象,不初始化(null)
    private static B b;  // 默认为null
  
    // 第三步:提供一个静态方法,在方法中判断并创建对象返回
    // ⚠️ 注意:此版本存在线程安全问题,多线程环境需优化
    public static B getInstance() {
        if (b == null) {      // 第一次调用时才创建
            b = new B();      // 创建对象
        }
        return b;             // 后续直接返回已创建的对象
    }
}

// 测试类
public class Demo011 {
    public static void main(String[] args) {
        // 获取B对象
        B b1 = B.getInstance();  // 第一次调用,创建对象:B类构造器执行
        B b2 = B.getInstance();  // 第二次调用,直接返回已有对象
  
        System.out.println(b1);  // com.itheima.B@6d06d69c
        System.out.println(b2);  // com.itheima.B@6d06d69c
        System.out.println(b1 == b2);  // true,同一对象
  
        // 对比饿汉式:懒汉式延迟加载,用的时候才创建,节省资源
    }
}

4. 饿汉式 vs 懒汉式对比

Q: 饿汉式和懒汉式有什么区别?各自适用什么场景?

A:

特性 饿汉式 懒汉式
创建时机 类加载时立即创建 第一次使用时创建
线程安全 天生线程安全 基本版本线程不安全,需额外处理
资源占用 类加载就占用内存 延迟加载,节省资源
执行效率 获取对象速度快 第一次获取有判断开销
适用场景 对象必定使用,或频繁使用 对象可能不使用,或占用资源大
// 饿汉式:类加载就创建,简单线程安全
public class HungrySingleton {
    private static final HungrySingleton INSTANCE = new HungrySingleton();
    private HungrySingleton() {}
    public static HungrySingleton getInstance() {
        return INSTANCE;
    }
}

// 懒汉式:延迟创建,需处理线程安全(双重检查锁定)
public class LazySingleton {
    private static volatile LazySingleton instance;  // volatile防止指令重排序
    private LazySingleton() {}
  
    public static LazySingleton getInstance() {
        if (instance == null) {                    // 第一次检查(性能优化)
            synchronized (LazySingleton.class) {   // 同步块
                if (instance == null) {            // 第二次检查(确保唯一)
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }
}

第二部分:枚举类

5. 枚举类的概念与语法

Q: 什么是枚举类?枚举的本质是什么?

A: 枚举类是一种特殊类,用于表示一组固定的常量,限制输入的有效性,提高代码可读性。

枚举的本质:继承了 java.lang.Enum的final类,每个枚举常量都是该类的静态实例。

// 枚举的定义
public enum Gender {
    // 第一行:罗列枚举常量(必须第一行)
    MAN,           // 等价于:public static final Gender MAN = new Gender();
    WOMAN("女");   // 等价于:public static final Gender WOMAN = new Gender("女");
  
    // 第二行开始:可以定义类的其他成员
    private String value;  // 成员变量
  
    // 构造器(必须是private,写不写都是private)
    private Gender() {}    // 无参构造
  
    Gender(String value) {  // 有参构造
        this.value = value;
    }
  
    // getter/setter
    public String getValue() {
        return value;
    }
  
    public void setValue(String value) {
        this.value = value;
    }
}

// 使用枚举
public class Demo021 {
    public static void main(String[] args) {
        // 枚举类不能new对象(构造器私有)
        // Gender g = new Gender();  // ❌ 编译报错!
  
        // 直接使用枚举常量
        Gender man = Gender.MAN;
        Gender woman = Gender.WOMAN;
  
        System.out.println(man);           // MAN(枚举名称)
        System.out.println(man.getValue()); // null(MAN没有赋值)
  
        System.out.println(woman);           // WOMAN
        System.out.println(woman.getValue()); // 女(WOMAN构造时赋值)
  
        // 解决MAN的value为null的问题
        man.setValue("男");
        System.out.println(man.getValue());  // 男
    }
}

6. 枚举类的注意事项

Q: 使用枚举类有哪些注意事项?编译器会自动添加哪些方法?

A: 注意事项

  1. 枚举类的第一句只能罗列常量名称
  2. 从第二句开始可以定义其他成员(变量、方法、构造器)
  3. 枚举都是final类,不能被继承,默认继承 java.lang.Enum
  4. 构造器都是私有的,对外不能创建对象
  5. 编译器自动添加 values()valueOf(String)静态方法
public enum Gender {
    MAN,           // 常量
    WOMAN("女");   // 带参数的常量
  
    private String value;
  
    private Gender() {}           // 无参构造
    Gender(String value) {        // 有参构造(默认private)
        this.value = value;
    }
  
    public String getValue() { return value; }
    public void setValue(String value) { this.value = value; }
}

public class Demo021 {
    public static void main(String[] args) {
        // 编译器自动添加的方法:
  
        // 1. values():获取所有枚举对象
        Gender[] values = Gender.values();
        System.out.println(Arrays.toString(values));  // [MAN, WOMAN]
  
        // 2. valueOf(String):根据名称获取枚举对象
        Gender man = Gender.valueOf("MAN");
        System.out.println(man);  // MAN
  
        // ❌ 名称不存在会抛异常
        // Gender error = Gender.valueOf("XXX");  // IllegalArgumentException
  
        // 应用场景:限制参数输入
        Student s1 = new Student("播仔", 20, Gender.MAN);
        Student s2 = new Student("播妞", 19, Gender.WOMAN);
  
        // 打印学生信息
        System.out.println("姓名:" + s1.getName() + ",性别:" + 
            (s1.getGender() == Gender.MAN ? "男" : "女"));
    }
}

// 使用枚举的Student类
@Data
@NoArgsConstructor
@AllArgsConstructor
class Student {
    private String name;
    private int age;
    // private String gender;  // ❌ 不好,可以输入任意值
    private Gender gender;     // ✅ 使用枚举限制输入
}

7. 枚举类的应用场景

Q: 枚举类在实际开发中有什么应用场景?与常量相比有什么优势?

A: 应用场景:表示一组有限个数的数据(性别、类型、状态、星期、月份等),作为参数进行传输。

优势:限制输入的有效性,编译期检查,避免非法值。

// ❌ 早期使用常量:无法限制输入
public class OldWay {
    public static final String MAN = "男";
    public static final String WOMAN = "女";
  
    public static void main(String[] args) {
        showGender(MAN);      // 合理
        showGender(WOMAN);    // 合理
        showGender("hello");  // ❌ 不合理,但编译不报错!运行期才出问题
        showGender("男 ");    // ❌ 带空格,拼写错误等难以发现
    }
  
    public static void showGender(String gender) {
        System.out.println("性别:" + gender);
    }
}

// ✅ 使用枚举:编译期限制输入
public class NewWay {
    public enum Gender {
        MAN("男"), WOMAN("女");
        private String value;
        Gender(String value) { this.value = value; }
        public String getValue() { return value; }
    }
  
    public static void main(String[] args) {
        showGender(Gender.MAN);    // ✅ 合理
        showGender(Gender.WOMAN);  // ✅ 合理
        // showGender("hello");    // ❌ 编译报错!类型不匹配
        // showGender(new Gender()); // ❌ 编译报错!无法new枚举
    }
  
    // 参数类型是枚举,只能传入枚举常量
    public static void showGender(Gender gender) {
        System.out.println("性别:" + gender.getValue());
    }
}

第三部分:抽象类

8. 抽象类的概念与语法

Q: 什么是抽象类?什么是抽象方法?有什么特点?

A: 抽象类:用 abstract修饰的类,不能创建对象,作为特殊父类让子类继承实现。

抽象方法:用 abstract修饰的方法,只有方法签名,没有方法体。

特点

  • 有抽象方法的类必须是抽象类
  • 抽象类中不一定要有抽象方法
  • 抽象类可以有普通类的所有成员(变量、方法、构造器)
  • 子类必须重写所有抽象方法,否则子类也必须是抽象类
// 抽象类:Animal
public abstract class Animal {
    // 成员变量
    public String name = "动物";
  
    // 抽象方法:没有方法体,子类必须实现
    public abstract void eat();
  
    // 普通方法:有具体实现
    public void sleep() {
        System.out.println("动物正在睡觉");
    }
  
    // 构造器(虽然不能直接new,但供子类调用)
    public Animal() {}
    public Animal(String name) {
        this.name = name;
    }
  
    // getter/setter
    public void setName(String name) { this.name = name; }
    public String getName() { return name; }
}

// 具体子类:必须实现所有抽象方法
public class Dog extends Animal {
    @Override
    public void eat() {  // 必须重写抽象方法
        System.out.println("狗吃骨头");
    }
  
    // 可以选择性重写普通方法
    @Override
    public void sleep() {
        System.out.println("狗趴着睡觉");
    }
}

// 抽象子类:可以不实现抽象方法
public abstract class Fish extends Animal {
    // 不实现eat(),Fish类也必须声明为abstract
}

// 测试
public class Demo031 {
    public static void main(String[] args) {
        // Animal a = new Animal();  // ❌ 报错!抽象类不能实例化
  
        Animal dog = new Dog();  // ✅ 多态
        dog.eat();      // 狗吃骨头
        dog.sleep();    // 狗趴着睡觉
  
        // Fish fish = new Fish();  // ❌ 报错!抽象类不能实例化
    }
}

9. 抽象类的好处

Q: 使用抽象类有什么好处?为什么说抽象类更好地支持多态?

A: 好处

  1. 强制规范:强制子类必须实现某些方法
  2. 模板作用:提供通用模板,子类按需扩展
  3. 支持多态:父类引用指向子类对象,调用各自实现的方法
// 需求:宠物游戏,猫喵喵叫,狗汪汪叫,动物都会叫但方式不同

// 抽象父类:规定子类必须实现cry()方法
public abstract class Animal {
    private String name;
  
    public abstract void cry();  // 抽象方法:强制子类实现
  
    public String getName() { return name; }
    public void setName(String name) { this.name = name; }
}

// 子类:必须实现cry()
public class Dog extends Animal {
    @Override
    public void cry() {
        System.out.println(getName() + "汪汪汪的叫~~~");
    }
}

public class Cat extends Animal {
    @Override
    public void cry() {
        System.out.println(getName() + "喵喵喵的叫~~~");
    }
}

// 测试:多态的应用
public class Demo {
    public static void main(String[] args) {
        // 多态:父类引用指向不同子类对象
        Animal dog = new Dog();
        dog.setName("旺财");
  
        Animal cat = new Cat();
        cat.setName("咪咪");
  
        // 同一方法,不同表现
        dog.cry();  // 旺财汪汪汪的叫~~~
        cat.cry();  // 咪咪喵喵喵的叫~~~
  
        // 更好的设计:方法参数使用抽象父类
        makeCry(dog);  // 旺财汪汪汪的叫~~~
        makeCry(cat);  // 咪咪喵喵喵的叫~~~
    }
  
    // 参数是抽象父类,可以接收任何子类对象
    public static void makeCry(Animal animal) {
        animal.cry();  // 实际执行子类的实现
    }
}

第四部分:接口

10. 接口的概念与基本语法

Q: 什么是接口?接口与抽象类有什么区别?

A: 接口:用 interface定义,是一组功能的规范(契约),只描述方法名称,没有具体实现。

核心区别

  • 接口不能创建对象,只能被类实现(implements
  • 一个类可以实现多个接口(支持多继承)
  • JDK8之前接口只能有抽象方法,JDK8+可以有默认方法、静态方法、私有方法
// 定义接口:飞行的规范
public interface Flyable {
    // 成员变量:默认是 public static final(常量)
    int MAX_HEIGHT = 10000;  // 等价于:public static final int MAX_HEIGHT = 10000;
  
    // 抽象方法:默认是 public abstract
    void fly();  // 等价于:public abstract void fly();
}

// 实现类:必须实现所有抽象方法
public class Bird implements Flyable {
    @Override
    public void fly() {
        System.out.println("鸟用翅膀飞");
    }
}

public class Airplane implements Flyable {
    @Override
    public void fly() {
        System.out.println("飞机用引擎飞");
    }
}

// 测试
public class Demo {
    public static void main(String[] args) {
        // Flyable f = new Flyable();  // ❌ 报错!接口不能实例化
  
        Flyable bird = new Bird();      // ✅ 多态
        Flyable plane = new Airplane(); // ✅ 多态
  
        bird.fly();   // 鸟用翅膀飞
        plane.fly();  // 飞机用引擎飞
  
        // 访问接口常量
        System.out.println(Flyable.MAX_HEIGHT);  // 10000
    }
}

11. 接口的多实现与多态应用

Q: 接口如何实现多继承的效果?如何利用接口实现多态?

A: Java类不支持多继承,但一个类可以实现多个接口,达到类似多继承的效果。

// 定义多个接口
interface Drivable {
    void drive();  // 驾驶
}

interface Flyable {
    void fly();    // 飞行
}

// 一个类实现多个接口
class Vehicle implements Drivable, Flyable {
    @Override
    public void drive() {
        System.out.println("车辆正在驾驶...");
    }
  
    @Override
    public void fly() {
        System.out.println("车辆正在飞行...");
    }
}

// 多态应用:面向接口编程
public class Main {
    public static void main(String[] args) {
        Vehicle myVehicle = new Vehicle();
  
        // 同一对象,不同接口表现
        myVehicle.drive();  // 车辆正在驾驶...
        myVehicle.fly();    // 车辆正在飞行...
  
        // 面向接口编程:方法参数使用接口类型
        startDriving(myVehicle);  // 车辆正在驾驶...
        startFlying(myVehicle);   // 车辆正在飞行...
    }
  
    // 参数是接口,可以接收任何实现类
    public static void startDriving(Drivable d) {
        d.drive();
    }
  
    public static void startFlying(Flyable f) {
        f.fly();
    }
}

12. JDK8+接口新特性

Q: JDK8开始接口新增了哪些方法?各自有什么特点?

A: JDK8+接口可以包含三种新方法:

方法类型 关键字 特点 调用方式
默认方法 default 有默认实现,实现类可选择重写 对象调用
静态方法 static 属于接口本身,不能重写 接口名调用
私有方法 private 接口内部使用,供默认/静态方法调用 接口内部调用
public interface MyInterface {
    // 抽象方法(传统)
    void abstractMethod();
  
    // 1. 默认方法:有默认实现,实现类可选择重写
    default void defaultMethod() {
        System.out.println("默认方法执行");
        privateHelper();  // 可以调用私有方法
    }
  
    // 2. 静态方法:属于接口,不能重写
    static void staticMethod() {
        System.out.println("静态方法执行");
        privateStaticHelper();  // 可以调用私有静态方法
    }
  
    // 3. 私有方法:接口内部使用(JDK9+)
    private void privateHelper() {
        System.out.println("私有方法:供默认方法调用");
    }
  
    private static void privateStaticHelper() {
        System.out.println("私有静态方法:供静态方法调用");
    }
}

// 实现类
class MyClass implements MyInterface {
    @Override
    public void abstractMethod() {
        System.out.println("抽象方法实现");
    }
  
    // 可以选择重写默认方法
    @Override
    public void defaultMethod() {
        System.out.println("重写后的默认方法");
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        MyInterface obj = new MyClass();
  
        obj.abstractMethod();   // 抽象方法实现
        obj.defaultMethod();    // 重写后的默认方法(对象调用)
  
        MyInterface.staticMethod();  // 静态方法执行(接口名调用)
    }
}

13. 接口的注意事项

Q: 使用接口有哪些重要的注意事项?

A: 注意事项

  1. 接口与接口可以多继承interface C extends A, B { }
  2. 方法签名冲突:多个接口有相同方法签名但返回值不同,不支持多实现
  3. 默认方法冲突:多个接口有同名默认方法,实现类必须重写
  4. 类优先原则:父类和接口有同名默认方法,优先使用父类方法
// 1. 接口多继承
interface A { void show1(); }
interface B { void show2(); }
interface C extends A, B {  // ✅ 接口可以多继承
    void show3();
}

class D implements C {
    @Override public void show1() {}
    @Override public void show2() {}
    @Override public void show3() {}
}

// 2. 方法签名冲突(不支持)
interface A1 { void show(); }
interface B1 { String show(); }  // 返回值不同
// interface C1 extends A1, B1 {}  // ❌ 编译错误!方法签名冲突

// 3. 默认方法冲突(必须重写)
interface A3 {
    default void show() { System.out.println("A3 show"); }
}
interface B3 {
    default void show() { System.out.println("B3 show"); }
}
class Dog2 implements A3, B3 {
    @Override
    public void show() {
        A3.super.show();  // 调用A3的默认方法
        B3.super.show();  // 调用B3的默认方法
        System.out.println("Dog2自己的show");
    }
}

// 4. 类优先原则
interface A2 {
    default void show() { System.out.println("接口A2 show"); }
}
class Animal2 {
    public void show() { System.out.println("父类Animal2 show"); }
}
class Dog3 extends Animal2 implements A2 {
    // 优先使用父类Animal2的show(),而不是接口A2的默认方法
    public void go() {
        show();              // 父类的show()
        super.show();        // 父类的show()
        A2.super.show();     // 接口A2的默认方法(必须显式调用)
    }
}

第五部分:内部类

14. 成员内部类

Q: 什么是成员内部类?有什么特点?如何创建对象?

A: 成员内部类:定义在类中的普通成员,类似成员变量、成员方法。

特点

  • 内部类可以访问外部类的所有成员(包括私有)
  • 外部类不能直接访问内部类的成员
  • 可以定义静态成员(JDK16+)
public class Car {
    // 外部类成员
    public static String model = "宝马";
    public int price = 500000;
  
    // 成员内部类
    public class Engine {
        private int rpm;           // 实例变量
        public static String brand;  // 静态变量(JDK16+)
  
        public Engine(int rpm) {
            this.rpm = rpm;
        }
  
        public void showRpm() {
            System.out.println("发动机转速:" + rpm);
        }
  
        public void showInfo() {
            // 内部类可以访问外部类的所有成员
            System.out.println("型号:" + model);           // 静态成员
            System.out.println("售价:" + price);            // 实例成员
            Car.this.showPrice();  // 显式调用外部类方法
        }
    }
  
    public void showPrice() {
        System.out.println("价格:" + price);
    }
  
    public void showEngineInfo() {
        // 外部类不能直接访问内部类成员
        // showRpm();  // ❌ 报错!
        // Engine.showBrand();  // 只能通过类名访问静态成员
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        // 创建成员内部类对象:必须先有外部类对象
        Car.Engine engine = new Car().new Engine(3000);
  
        // 或者
        Car car = new Car();
        Car.Engine engine2 = car.new Engine(4000);
  
        engine.showRpm();     // 发动机转速:3000
        engine.showInfo();    // 访问外部类成员
    }
}

15. 静态内部类

Q: 什么是静态内部类?与成员内部类有什么区别?

A: 静态内部类:用 static修饰的内部类,不持有外部类实例的引用。

区别

  • 静态内部类可以独立存在,不依赖外部类对象
  • 只能直接访问外部类的静态成员,不能直接访问实例成员
  • 不能使用 外部类.this语法
public class OuterClass {
    private static int outerStaticVar = 10;   // 静态变量
    private int outerInstanceVar = 20;        // 实例变量
  
    // 静态内部类
    public static class StaticInnerClass {
        private int innerVar = 30;
  
        public void display() {
            // ✅ 可以访问外部类静态变量
            System.out.println("外部静态变量:" + outerStaticVar);
      
            // ❌ 不能直接访问外部类实例变量
            // System.out.println(outerInstanceVar);  // 编译错误!
            // System.out.println(OuterClass.this.outerInstanceVar);  // 错误!没有this引用
      
            // 访问自己的变量
            System.out.println("内部变量:" + innerVar);
        }
  
        // 通过参数传入外部类对象,间接访问实例变量
        public void displayWithOuter(OuterClass outer) {
            System.out.println("外部实例变量:" + outer.outerInstanceVar);
        }
    }
}

// 测试
public class Test {
    public static void main(String[] args) {
        // 创建静态内部类对象:不需要外部类对象
        OuterClass.StaticInnerClass inner = new OuterClass.StaticInnerClass();
  
        inner.display();  // 访问静态成员
  
        OuterClass outer = new OuterClass();
        inner.displayWithOuter(outer);  // 通过参数访问实例成员
    }
}

16. 匿名内部类

Q: 什么是匿名内部类?有什么特点?应用场景是什么?

A: 匿名内部类:没有名字的内部类,在创建对象时直接定义,一次性使用。

特点

  • 没有显式类名
  • 即时定义和实例化
  • 本质是父类的子类或接口的实现类
  • 只能继承一个父类或实现一个接口

应用场景:一次性简单使用,作为参数传递(如事件监听、排序比较器等)

// 抽象父类
public abstract class Animal {
    public abstract void eat();
}

// 测试类
public class Demo061 {
    public static void main(String[] args) {
        // 匿名内部类:创建Animal的子类对象,没有类名
        Animal dog = new Animal() {
            @Override
            public void eat() {
                System.out.println("狗吃骨头");
            }
        };  // 注意分号!
  
        dog.eat();  // 狗吃骨头
  
        // 底层:编译器生成类似 Dog$1.class 的类文件
  
        // 应用场景:作为参数传递
        feed(new Animal() {
            @Override
            public void eat() {
                System.out.println("猫吃鱼");
            }
        });
  
        feed(new Animal() {
            @Override
            public void eat() {
                System.out.println("牛吃草");
            }
        });
    }
  
    public static void feed(Animal animal) {
        animal.eat();
    }
}

17. 匿名内部类应用:数组排序

Q: 如何使用匿名内部类实现自定义数组排序?

A: 使用 Arrays.sort()的重载方法,传入 Comparator接口的匿名内部类对象,定义比较规则。

比较规则

  • o1 - o2 > 0:o1大于o2,返回正数(升序:o1放后面)
  • o1 - o2 < 0:o1小于o2,返回负数(升序:o1放前面)
  • 简化:return o1 - o2(升序),return o2 - o1(降序)
import java.util.Arrays;
import java.util.Comparator;

public class Demo062 {
    public static void main(String[] args) {
        Integer[] arr = {10, 88, 52, 21, 76, 92, 28, 55, 39, 82};
  
        System.out.println("排序前:" + Arrays.toString(arr));
  
        // 升序排序
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                // 前面跟后面比:升序
                return o1 - o2;  // 或 return o1.compareTo(o2);
            }
        });
        System.out.println("升序后:" + Arrays.toString(arr));
        // [10, 21, 28, 39, 52, 55, 76, 82, 88, 92]
  
        // 降序排序
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                // 后面跟前面比:降序
                return o2 - o1;  // 或 return o2.compareTo(o1);
            }
        });
        System.out.println("降序后:" + Arrays.toString(arr));
        // [92, 88, 82, 76, 55, 52, 39, 28, 21, 10]
  
        // 复杂排序:先按个位数排序
        Arrays.sort(arr, new Comparator<Integer>() {
            @Override
            public int compare(Integer o1, Integer o2) {
                return (o1 % 10) - (o2 % 10);  // 按个位数升序
            }
        });
        System.out.println("按个位数排序:" + Arrays.toString(arr));
    }
}

第六部分:综合案例

18. 抽象类与接口综合案例

Q: 如何综合运用抽象类和接口设计一个"人"的体系,包含吃饭、开车、打麻将行为?

A: 设计思路

  • 抽象类 Person:定义共同特征(吃饭),所有人都会吃饭但方式不同
  • 接口 IDrive:定义开车能力,不是所有人都会开车
  • 接口 IMaJiang:定义打麻将能力,不是所有人都会打麻将
  • 具体类:根据实际能力继承和实现
// 抽象类:定义吃饭行为(所有人都会,但方式不同)
public abstract class Person {
    public abstract void eat();
}

// 接口:开车能力
public interface IDrive {
    void playDriving();
}

// 接口:打麻将能力
public interface IMaJiang {
    void playMaJiang();
}

// 学生:会吃饭、会开车、会打麻将
public class Student extends Person implements IMaJiang, IDrive {
    @Override
    public void eat() {
        System.out.println("学生在吃饭...");
    }
  
    @Override
    public void playDriving() {
        System.out.println("学生在开车...");
    }
  
    @Override
    public void playMaJiang() {
        System.out.println("学生玩麻将...");
    }
}

// 老师:只会吃饭
public class Teacher extends Person {
    @Override
    public void eat() {
        System.out.println("老师在吃饭...");
    }
}

// 工人:会吃饭、会打麻将
public class Worker extends Person implements IMaJiang {
    @Override
    public void eat() {
        System.out.println("工人在吃饭...");
    }
  
    @Override
    public void playMaJiang() {
        System.out.println("工人打麻将...");
    }
}

// 司机:会吃饭、会开车
public class Driver extends Person implements IDrive {
    @Override
    public void eat() {
        System.out.println("司机在吃饭...");
    }
  
    @Override
    public void playDriving() {
        System.out.println("司机在开车...");
    }
}

// 测试:多态的强大应用
public class Demo051 {
    public static void main(String[] args) {
        Student s = new Student();
        Teacher t = new Teacher();
        Worker w = new Worker();
        Driver d = new Driver();
  
        // 请所有人吃饭(参数是抽象父类)
        pleaseEat(s);  // 学生在吃饭...
        pleaseEat(t);  // 老师在吃饭...
        pleaseEat(w);  // 工人在吃饭...
        pleaseEat(d);  // 司机在吃饭...
  
        // 请会开车的人开车(参数是接口)
        playDriving(s);  // 学生在开车...
        playDriving(d);  // 司机在开车...
        // playDriving(t);  // ❌ 编译错误!Teacher没有实现IDrive
  
        // 请会打麻将的人打麻将(参数是接口)
        playMaJiang(s);  // 学生玩麻将...
        playMaJiang(w);  // 工人打麻将...
    }
  
    // 方法参数使用抽象父类:可以接收所有Person子类
    public static void pleaseEat(Person person) {
        person.eat();
    }
  
    // 方法参数使用接口:只能接收实现了该接口的类
    public static void playMaJiang(IMaJiang maJiang) {
        maJiang.playMaJiang();
    }
  
    public static void playDriving(IDrive drive) {
        drive.playDriving();
    }
}

19. 接口实现方案切换案例

Q: 如何使用接口实现灵活的方案切换?以班级学生信息管理为例。

A: 需求:打印学生信息和平均成绩,支持多套方案灵活切换。

设计

  • 定义接口 StudentService:声明功能方法
  • 多套方案实现接口:方案1(基础版)、方案2(增强版)
  • 使用时通过多态切换实现类
// 学生实体类
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Student {
    private String name;
    private String gender;
    private double score;
}

// 接口:定义功能规范
public interface StudentService {
    void printStudents(Student[] arr);      // 功能1:打印学生信息
    void printAverageScore(Student[] arr); // 功能2:打印平均成绩
}

// 方案1:基础版
public class ServiceImpl1 implements StudentService {
    @Override
    public void printStudents(Student[] arr) {
        for (Student s : arr) {
            System.out.println(s);
        }
    }
  
    @Override
    public void printAverageScore(Student[] arr) {
        double sum = 0;
        for (Student s : arr) {
            sum += s.getScore();
        }
        System.out.println("平均分: " + sum / arr.length);
    }
}

// 方案2:增强版(统计男女人数,去掉最高最低分)
public class ServiceImpl2 implements StudentService {
    @Override
    public void printStudents(Student[] arr) {
        int manCount = 0, womanCount = 0;
        for (Student s : arr) {
            System.out.println(s);
            if ("男".equals(s.getGender())) manCount++;
            else womanCount++;
        }
        System.out.println("男生:" + manCount + ",女生:" + womanCount);
    }
  
    @Override
    public void printAverageScore(Student[] arr) {
        double sum = arr[0].getScore();
        double max = arr[0].getScore();
        double min = arr[0].getScore();
  
        for (int i = 1; i < arr.length; i++) {
            double score = arr[i].getScore();
            sum += score;
            if (score > max) max = score;
            if (score < min) min = score;
        }
  
        double avg = (sum - max - min) / (arr.length - 2);
        System.out.println("去掉最高最低后的平均分: " + avg);
    }
}

// 测试:灵活切换方案
public class Demo {
    public static void main(String[] args) {
        Student[] arr = {
            new Student("小王", "男", 90),
            new Student("小李", "男", 80),
            new Student("小张", "女", 95),
            new Student("小赵", "女", 85)
        };
  
        // 使用方案1
        StudentService service = new ServiceImpl1();
        System.out.println("=== 方案1 ===");
        service.printStudents(arr);
        service.printAverageScore(arr);
  
        // 切换为方案2:只需改这一行!
        service = new ServiceImpl2();
        System.out.println("=== 方案2 ===");
        service.printStudents(arr);
        service.printAverageScore(arr);
    }
}
评论交流

文章目录