第一部分:单例设计模式
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: 饿汉式:类加载时就创建对象,"急切"地创建实例。
实现步骤:
- 把类的构造器私有(防止外部new)
- 定义一个静态变量记住类的一个对象
- 定义一个静态方法,返回对象
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: 懒汉式:类加载时不创建对象,第一次需要时才创建(延迟初始化)。
实现步骤:
- 把类的构造器私有
- 定义一个静态变量用于存储对象(不初始化)
- 提供一个静态方法,判断并创建对象返回
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: 注意事项:
- 枚举类的第一句只能罗列常量名称
- 从第二句开始可以定义其他成员(变量、方法、构造器)
- 枚举都是final类,不能被继承,默认继承
java.lang.Enum - 构造器都是私有的,对外不能创建对象
- 编译器自动添加
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: 好处:
- 强制规范:强制子类必须实现某些方法
- 模板作用:提供通用模板,子类按需扩展
- 支持多态:父类引用指向子类对象,调用各自实现的方法
// 需求:宠物游戏,猫喵喵叫,狗汪汪叫,动物都会叫但方式不同
// 抽象父类:规定子类必须实现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: 注意事项:
- 接口与接口可以多继承:
interface C extends A, B { } - 方法签名冲突:多个接口有相同方法签名但返回值不同,不支持多实现
- 默认方法冲突:多个接口有同名默认方法,实现类必须重写
- 类优先原则:父类和接口有同名默认方法,优先使用父类方法
// 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);
}
}