目录


week8

1. 面向对象核心概念

1.1. OOP(object-oriented-programming)

  • 核心思想: 将现实世界抽象为对象,每个对象具有状态(属性)和行为(方法)

  • 例子:《王者荣耀》安琪拉 - 属性(状态:姓名、血量、角色), 行为(行为:移动、攻击、释放技能)

1.2. JVM 内存模型(JVM Stack & Heap)

内存区域 存储内容 特点
栈 (Stack) 局部变量、基本类型值、引用指针 自动分配释放,速度快
堆 (Heap) 对象、数组 动态分配,需要 GC,速度慢
// 基本类型存储在栈中,基本类型有byte、short、int、long、float、double、char、boolean .

int age = 25;

// 引用类型 - 引用在栈,对象在堆

String name = new String("John");

1.3. 数据类型系统(Primitive vs Reference Types)

基本类型(Primitive Types):

  • 8 种基本类型: byte、short、int、long、float、double、boolean、char
  1. 这八种是直接存储值,没有方法。
  2. 存储在栈中。

引用类型(Reference Types):

  • 除基本类型外的所有类型。
  1. 存储对象的内存地址(指针)。
  2. 对象包含属性和方法。
  3. 存储在堆中。

包装类 (Wrapper Classes):

  • 基本类型的引用类型包装
  1. 提供丰富的方法
  2. 对应关系: int→Integer, char→Character 等
  3. 包装类具有自动装箱与拆箱、数值缓存等特点,还提供了丰富的方法,可用于类型转换、字符串解析等操作,并且能让基本数据类型参与集合操作,支持反射等面向对象的操作。

1.4. String对象(string object)

  • 特点:不可变对象,提供丰富的字符串操作方法。
  • 常用方法: length()、equals()、substring()、toLowerCase()、toUpperCase()

创建方式

// 方式1: 直接赋值(推荐,使用常量池)

String str1 = "Hello";

// 方式2: 使用构造方法(新建对象,使用堆)

String str2 = new String("Hello");

2. 类与对象

2.1 类的定义Class (Template for Objects)

  • 作用: 定义对象的属性和方法,作为对象的模板
  • 结构: 属性(成员变量)、构造方法、成员方法
public class Person {

// 属性(成员变量)

String name;

int age;

String gender;

// 构造方法

public Person(String name, int age, String gender) {

this.name = name;

this.age = age;

this.gender = gender;

}

// 成员方法

public void introduceYourself() {

System.out.println("Hello, I'm " + name);

}

public int getAge() {

return age;

}

}

2.2. 对象的创建与使用(Object (Instance of Class))

  • 创建语法: 类名 对象名 = new 类名(参数);
  • 使用对象: 通过”.” 操作符访问属性和方法
public class TestPerson {​

public static void main(String[] args) {​

// 创建对象​

Person person = new Person("John", 25, "Male");​



// 访问属性​

System.out.println(person.name);​



// 调用方法​

person.introduceYourself();​

int age = person.getAge();​

}​

}

2.3.构造函数(Constructor)

  • 作用: 对象创建时初始化属性
  • 特点: 与类名同名,无返回类型,自动调用
  • this 关键字: 区分实例变量和局部变量

构造函数重载:

public class Person {

String name;

int age;

// 无参构造函数

public Person() {

this.name = "Unknown";

this.age = 0;

}

// 带一个参数的构造函数

public Person(String name) {

this.name = name;

this.age = 0;

}

// 带两个参数的构造函数

public Person(String name, int age) {

this.name = name;

this.age = age;

}

}

3.封装与访问控制

3.1.访问修饰符(Access Modifiers)

修饰符 同一类 同一包 子类 其他包
public
private
protected
default

3.2.封装(Encapsulation (Data Hiding))

  • 核心思想: 将属性设为 private,通过 public 方法访问
  • 实现步骤:
  1. 将属性设为 private
  2. 提供 public 的 getter 方法(获取属性值)
  3. 提供 public 的 setter 方法(设置属性值)
public class BankAccount {​

// 私有属性​

private String accountNumber;​

private double balance;​



// 构造函数​

public BankAccount(String accountNumber) {​

this.accountNumber = accountNumber;​

this.balance = 0.0;​

}​



// Getter方法​

public String getAccountNumber() {​

return accountNumber;​

}​



public double getBalance() {​

return balance;​

}​



// Setter方法(带验证)​

public void deposit(double amount) {​

if (amount > 0) {​

balance += amount;​

}​

}​

}

4. 静态成员

4.1. 静态变量(Static Variables)

  • 特点: 属于类,所有对象共享,用 static 关键字声明
  • 访问方式: 类名。变量名 或 对象名。变量名

4.2. 静态方法(Static Methods)

  • 特点: 属于类,没有 this 关键字,只能访问静态成员
  • 访问方式: 类名.方法名 ()
    public class Counter {

    // 静态变量:所有对象共享

    private static int count = 0;

    // 实例变量:每个对象独立

    private int id;

    public Counter() {

    count++;

    id = count;

    }

    // 静态方法

    public static int getTotalCount() {

    return count;

    }

    // 实例方法

    public int getId() {

    return id;

    }

    }

    // 使用

    public class TestCounter {

    public static void main(String[] args) {

    Counter c1 = new Counter();

    Counter c2 = new Counter();

    System.out.println(Counter.getTotalCount()); // 2

    System.out.println(c1.getId()); // 1

    System.out.println(c2.getId()); // 2

    }

    }

5. 对象比较与转换

5.1.对象比较(Object Comparison)

== 运算符:

  • 基本类型:比较值是否相等
  • 引用类型:比较引用是否指向同一对象

equals () 方法:

  • 默认比较引用
  • 可重写用于比较对象内容
public class Person {

String name;

int id;

// 重写equals方法

@Override

public boolean equals(Object obj) {

if (this == obj) return true;

if (obj == null || getClass() != obj.getClass()) return false;

Person person = (Person) obj;

return id == person.id && name.equals(person.name);

}

}

5.2.toString () 方法 (toString() Method)

  • 作用: 返回对象的字符串表示
  • 默认实现: 类名 @内存地址
  • 重写意义: 提供有意义的对象描述
public class Person {

String name;
int age;

@Override

public String toString() {
return "Person{name='" + name + "', age=" + age + "}";

}

}

// 使用
Person person = new Person("John", 25);
System.out.println(person); // 自动调用toString()

6. 对象数组(Object Array)

6.1. 创建对象数组

  • 步骤: 先创建数组,再为每个元素创建对象
public class ObjectArrayExample {

public static void main(String[] args) {

// 创建数组

Person[] people = new Person[3];

// 为每个元素创建对象

people[0] = new Person("John", 25);

people[1] = new Person("Jane", 30);

people[2] = new Person("Bob", 35);

// 或者直接初始化

Person[] team = {

new Person("Alice", 28),

new Person("Charlie", 32)

};

}

}

6.2. 遍历对象数组

// 普通for循环

for (int i = 0; i < people.length; i++) {

System.out.println(people\[i]);

}

// 增强for循环

for (Person person : people) {

System.out.println(person);

}

week9


1. 继承(Inheritance)

1.1.定义(Inheritance)

继承是面向对象(OOP)的核心特性,是定义新类(子类)从另一个类(父类/超类)中继承属性和行为。以实现代码复用,但是子类是可以重写父类的方法。

1.2.子类与超类(Subclass and Superclass)

1.2.1.举个例子:

假设大学需要管理三类人:

  • 学生(Student)
  • 教师(Professor)
  • 行政人员(Admin)
    它们都有共同属性(姓名、性别、生日等),但又有各自特有属性。
Person Student
String name String name
String gender String gender
String city String city
String dateOfBirth String dateOfBirth
int idNumber int idNumber
String major
int yearOfStudy

1.2.2. 定义讲解

  • Student 是 Person 的子类
  • Person 是 Student 的超类
  • 在 Java 中,一个类只能继承一个超类(单继承)
  • 但一个超类可以被多个子类继承
public class Student extends Person {
// 子类特有属性
private String major;
private int yearOfStudy;
}

1.3.使用继承

public class Student extends Person {
private final String major;
private final int yearOfStudy;

// 构造器
public Student(String name, String gender, String city, String dateOfBirth, int idNumber,String major, int yearOfStudy) {
super(name, gender, city, dateOfBirth, idNumber); // 调用超类构造器
this.major = major;
this.yearOfStudy = yearOfStudy;
}

// Getter
public String getMajor() {
return major;
}

public int getYearOfStudy() {
return yearOfStudy;
}
}

只需要在子类后面加入extends 父类名就可以,无需复制父类或者超类的代码。

1.4.相关关键性质

1. Final关键字

Fanal 可以用于方法、变量和类中。

  • 用于变量:一旦赋值不可更改
  • 用于方法:禁止子类重写
  • 用于类:禁止被继承
public class Student extends Person {
private final String major; // 专业一旦设定不能改
private final int yearOfStudy;

public Student(String name, String gender, String city, String dateOfBirth, int idNumber,String major, int yearOfStudy) {
super(name, gender, city, dateOfBirth, idNumber);
this.major = major;
this.yearOfStudy = yearOfStudy;
}
}

2. Constractor(构造器)

  • 子类构造器必须调用超类构造器
  • 使用 super(...) 调用超类构造器
    注意:super()中必须要带有父类相应构造器中的全部参数
public Student(String name, String gender, String city, String dateOfBirth, int idNumber,
String major, int yearOfStudy) {
super(name, gender, city, dateOfBirth, idNumber); // 调用父类构造器
this.major = major;
this.yearOfStudy = yearOfStudy;
}

3. Encapsulation(封装)

  • 为子类的特有属性添加 getter 方法

4. Overriding Methods(重写方法)

  • 对父类提供的方法进行重写覆盖。
  • 最好使用 @Override 注解
@Override
public String toString() {
return "Student: name=" + this.getName() +
", gender=" + this.getGender() +
", city=" + this.getCity() +
", date of birth=" + this.getDateOfBirth() +
", idnumber=" + this.getIdNumber() +
", major: " + major +
", year of study=" + yearOfStudy;
}
特性 重载(Overloading) 重写(Overriding)
位置 同一个类内 子类中
方法名 相同 相同
参数列表 必须不同 必须相同
返回类型 可不同(但通常一致) 必须兼容(协变返回类型)
目的 提供多种调用方式 定制子类行为

1.5.子类与超类之间的转换

1. 向上转型(Upcasting)——自动完成

Person p2 = new Student("A Student", "M", "China", "1954-04-07", 193, , "Digital Media");

System.out.println(p2.getName()); // ✅ 有效

// System.out.println(p2.getMajor()); // ❌ 编译错误!p2 是 Person 类型,没有 getMajor()

解释StudentPerson 的子类,所以 Student 对象可以赋值给 Person 引用。

此时 p2 被编译器视作 person 类,不过当 p2 调用 studentperson 类共有的方法时,会使用 student 类中重写过,此时 p2 不能调用 student 中特有的方法,不过可以调用向下转型解决。

2. 向下转型(Downcasting)——需显式

Person p2 = new Student("A Student", "M", "China", "1954-04-07", 193, 3, "Digital Media");

Student s1 = (Student) p2; // 显式强制转换

System.out.println(s1.getMajor()); // ✅ 有效

注意: 如果 p2 不是 Student 类型,会抛出 ClassCastException


2. 多态(Polymorphism)

2.1. 定义

多态指的就是同一个方法在不同子类中有不同的实现方式,也可以说:父类的引用可以指向子类对象。当你调用重写方法时,执行的是子类的方法。

Person[] people = {
new Student("Alice", "F", "Beijing", "2000-01-01", 1001, "Computer Science", 2),
new Professor("Dr. Smith", "M", "New York", "1970-05-15", 2001, "CS", new Course[]{...}, 85000)
};

for (Person p : people) {
System.out.println(p.toString()); // 自动调用各自重写的 toString()
}

2.2. 类型转换(object type casting)

  1. 向上转型
  2. 向下转型

示例:

Person p1 = new Person("James Bond", "M", "London", "1921-11-11", 007);
Student s1 = new Student("A Student", "M", "China", "1954-04-07", 193, 3, "Digital Media");

System.out.println(s1.toString()); // 调用 Student 的 toString
System.out.println(p1.toString()); // 调用 Person 的 toString

当调用 s1.toString() 时,由于 s1Student 类型,会调用 StudenttoString() 方法。

3. 接口(Interface)

3.1. 定义

接口(Interface)是Java中的一种引用类型,它定义了一组方法的规范,但不提供方法的具体实现。接口可以看作是一个”契约”或”协议”,规定了实现该接口的类必须提供的方法。

语法:

interface 接口名 { 
// 常量(public static final)
int MAX_SIZE = 100;

// 抽象方法(public abstract)
void methodName1();
int methodName2(String param);
}

3.2. 接口的特点与抽象类区别

  1. 接口中的方法默认是public和abstract
  2. 接口中的变量默认是public static final
  3. 接口不能被实例化
  4. 接口需要被类实现(implement)

接口与抽象类的区别

特征 接口 抽象类
关键字 interface abstract class
实现/继承 implements extends
方法 默认public abstract 可以有具体实现
变量 默认public static final 普通成员变量
继承 多实现 单继承
构造器 不能有 可以有

3.3. 示例:

1. 定义接口

interface Animal { 
// 接口中的方法没有方法体
void makeSound();
void move();
// 接口中的常量
int MAX_AGE = 100;
}

2. 实现接口

class Dog implements Animal {
@Override
public void makeSound() {
System.out.println("汪汪汪");
}
@Override
public void move() {
System.out.println("狗在跑");
}
}
class Cat implements Animal {
@Override
public void makeSound() {
System.out.println("喵喵喵");
}
@Override
public void move() {
System.out.println("猫在走");
}
}

3. 使用实现类

public class Test {
public static void main(String[] args) {
Dog dog = new Dog();
Cat cat = new Cat();

dog.makeSound(); // 输出:汪汪汪
cat.move(); // 输出:猫在走

// 多态性
Animal animal = new Dog();
animal.makeSound(); // 输出:汪汪汪
}
}

4. 多接口实现

interface Flyable {
void fly();
}

interface Swimmable {
void swim();
}

class Duck implements Flyable, Swimmable {
@Override
public void fly() {
System.out.println("鸭子在飞");
}

@Override
public void swim() {
System.out.println("鸭子在游泳");
}
}

5. 接口中的默认方法(Java 8+)

interface Vehicle {
void start();

// 默认方法,可以有具体实现
default void stop() {
System.out.println("车辆停止");
}

// 静态方法
static void info() {
System.out.println("这是车辆接口");
}
}

class Car implements Vehicle {
@Override
public void start() {
System.out.println("汽车启动");
}
// stop()方法自动继承,无需重写
}

4.抽象类

1. 定义

抽象类(Abstract class)是面向对象中的一种特殊类,他只能作为其他类的父类存在,不能被实例化,用与定义一组有共性但是未完全实现的方法和属性,要求其子类必须提供具体实现,不然会报错

关键点:抽象类代表的是”抽象概念”,而不是具体的实体。例如,”形状”是一个抽象概念,而”圆形”、”三角形”是具体形状。

2. 核心特征

1. 不能被实例化

2. 包含抽象方法

  • 抽象方法没有方法体,只有方法声明,必须在子类中实现:
public abstract class Shape {

// 抽象方法:没有实现
public abstract double getArea();

// 具体方法:有实现
public double getPerimeter() {
return 4 * 10; // 默认实现
}
}

3. 可以包含具体方法

  • 抽象类可以包含已经实现的方法,子类可以直接继承使用:
public abstract class Shape {
// 具体方法
public void printInfo() {
System.out.println("This is a shape.");
}

// 抽象方法
public abstract double getArea();

}

4. 不能被密封(sealed)

抽象类不能使用sealed关键字,因为sealed表示不能被继承,而抽象类必须被继承。

5. 抽象类 vs 具体类

特性 抽象类 具体类
能否实例化 ❌ 不能 ✅ 可以
是否必须包含抽象方法 ✅ 必须至少有一个抽象方法 ❌ 不需要
是否能包含具体方法 ✅ 可以 ✅ 可以
用途 作为基类,提供模板 作为具体实现

6. 抽象类 vs 接口

特性 抽象类 接口
是否能包含字段 ✅ 可以 ❌ 不能(Java 8+ 可以有默认方法,但不能有字段)
是否能包含构造方法 ✅ 可以 ❌ 不能
是否能包含非抽象方法 ✅ 可以 ✅ Java 8+ 可以(默认方法)
是否能包含静态方法 ✅ 可以 ✅ Java 8+ 可以
继承方式 单继承(一个类只能继承一个抽象类) 多重实现(一个类可以实现多个接口)
用途 提供通用实现和模板 定义契约(行为规范)

附录


各种面向对象对比

1. 内存模型对比

特性 栈 (Stack) 堆 (Heap)
存储内容 局部变量、基本类型值、引用指针 对象、数组
分配方式 自动分配释放 动态分配
管理方式 系统自动管理 GC 回收
访问速度

2. 数据类型对比

类型 存储方式 方法 内存位置
基本类型 直接存储值
引用类型 存储地址 对象在堆,引用在栈

3. 访问修饰符对比

修饰符 同一类 同一包 子类 其他包
public
private
protected
default

4. 方法重载 vs 方法重写对比

特性 方法重载 方法重写
位置 同一类 子类
参数 必须不同 必须相同
返回值 可不同 必须相同
修饰符 可不同 不能更严格
目的 多种实现 改变行为

5. 类型转换对比

类型 方向 语法 安全性
向上转型 子类→父类 隐式 安全
向下转型 父类→子类 (子类) 对象 不安全,需 instanceof 检查

OOP 核心知识点速记卡

1. 类与对象(Class & Object)

类别 内容
英文备注 Class (template) & Object (instance)
核心语法 java// 类定义class ClassName { dataType variable; returnType method() {}}// 对象创建ClassName obj = new ClassName();
易错点 - 混淆类和对象的概念- 忘记使用 new 关键字创建对象- 直接访问 private 成员变量

2. 构造函数(Constructor)

类别 内容
英文备注 Constructor (initializes objects)
核心语法 javapublic class ClassName { // 无参构造 public ClassName() {} // 带参构造 public ClassName(parameters) { this.variable = parameter; }}
易错点 - 构造函数名与类名不一致- 忘记使用 this 关键字- 自定义构造后缺少默认构造函数

3. 访问修饰符(Access Modifiers)

类别 内容
英文备注 public, private, protected, default
核心语法 javapublic class ClassName { private dataType variable; public returnType method() {}}
易错点 - 错误的访问权限设置- 直接访问 private 成员- 过度使用 public 修饰符

4. 封装(Encapsulation)

类别 内容
英文备注 Encapsulation (data hiding)
核心语法 javapublic class ClassName { private dataType variable; public dataType getVariable() { return variable; } public void setVariable(dataType value) { this.variable = value; }}
易错点 - 忘记设置访问器方法- 在 setter 中缺少数据验证- 直接暴露成员变量

5. 静态成员(Static Members)

类别 内容
英文备注 Static variables and methods
核心语法 javapublic class ClassName { private static dataType staticVar; public static returnType staticMethod() { return staticVar; }}// 使用ClassName.staticMethod();
易错点 - 在静态方法中访问实例成员- 过度使用静态成员- 忘记静态变量的共享特性

6. 对象比较(Object Comparison)

类别 内容
英文备注 == operator vs equals() method
核心语法 ```java// == 比较引用 if (obj1 == obj2) {}//equals () 比较内容 @Overridepublic boolean equals (Object obj) { if (this == obj) return true; if (obj == null
易错点 - 使用 == 比较对象内容- 忘记重写 equals () 方法- 重写 equals () 时违反契约

7. 对象数组(Object Array)

类别 内容
英文备注 Object array
核心语法 java// 创建数组ClassName[] array = new ClassName[size];// 初始化元素array[0] = new ClassName();// 遍历for (ClassName obj : array) { System.out.println(obj);}
易错点 - 空指针异常(未初始化元素)- 数组越界访问- 直接打印数组引用

8. JVM 内存区域(JVM Memory Areas)

类别 内容
英文备注 JVM Stack & Heap
核心语法 java// 栈:存基本类型值、局部变量、引用指针int a = 5; // a和5在栈String s = new String("Hi"); // s(指针)在栈// 堆:存对象、数组String s2 = new String("Hi"); // "Hi"对象在堆int[] nums = {1,2}; // 数组对象在堆
易错点 - 误以为引用类型的对象在栈中- 数组是引用类型,存储在堆(栈中仅存指针)

9. 基本类型与包装类(Primitive & Wrapper)

类别 内容
英文备注 Primitive & Wrapper Class
核心语法 java// 基本类型无方法,用包装类静态方法char c = 'a';// Character:char的包装类System.out.println(Character.isLowerCase(c)); // trueint num = 10;// Integer:int的包装类System.out.println(Integer.toString(num)); // "10"
易错点 - 直接调用基本类型的方法(如5.toString()编译错误)- 混淆包装类与基本类型(Integer num = 5是引用类型,存堆)

10. String 对象(String Object)

类别 内容
英文备注 String Object
核心语法 java// 创建方式1:直接赋值(常量池)String s1 = "Hello";// 创建方式2:构造方法(堆)String s2 = new String("Hello");// 常用方法System.out.println(s1.length()); // 5System.out.println(s1.substring(1)); // "ello"System.out.println(s1.equals(s2)); // true(比较内容)
易错点 - 用==比较 String 内容(s1 == s2 → false,应改equals())- substring()索引从 0 开始,而非 1

11. 变量传递(Variable Passing)

类别 内容
英文备注 Value Passing & Reference Passing
核心语法
java// 1. 值传递(基本类型)public static void changeInt(int a) { a = 100; }int num = 10;changeInt(num);System.out.println(num); // 10(未变)// 2. 引用传递(引用类型)public static void changeSB(StringBuilder sb) { sb.append("World"); }StringBuilder sb = new StringBuilder("Hello");changeSB(sb);System.out.println(sb); // HelloWorld(已变)
易错点 - 误以为引用传递是 “传递对象本身”(实际传递指针副本)- 忽略 String 不可变(修改时创建新对象,原对象不变)

12. toString () 方法(toString () Method)

类别 内容
英文备注 toString() Method
核心语法 javaclass ClassName { private String name; // 重写toString() @Override public String toString() { return "ClassName{name='" + name + "'}"; }}// 调用ClassName obj = new ClassName("James");System.out.println(obj); // ClassName{name='James'}
易错点 未重写toString(),直接打印对象(输出ClassName@15db9742,无意义)

13. 构造函数重载(Constructor Overloading)

类别 内容
英文备注 Constructor Overloading
核心语法 javapublic class ClassName { // 多个同名但参数不同的构造函数 public ClassName() {} public ClassName(parameter1) {} public ClassName(parameter1, parameter2) {} // 在构造函数中调用其他构造函数 public ClassName(parameters) { this(parameter1, parameter2); }}
易错点 - 参数列表必须不同(数量、类型或顺序)- this()必须是构造函数的第一条语句- 避免构造函数之间的循环调用

14. 继承(Inheritance)

类别 内容
英文备注 Inheritance
核心语法 java// 父类public class SuperClass { // 属性和方法}// 子类继承父类public class SubClass extends SuperClass { // 新增属性和方法 // 重写父类方法 @Override public returnType methodName() { super.methodName(); // 调用父类方法 // 子类实现 }}
易错点 - Java 只支持单继承- 子类构造函数必须调用父类构造函数- 子类不能访问父类的 private 成员- 重写方法的签名必须与父类完全相同

15. 方法重写(Method Overriding)

类别 内容
英文备注 Method Overriding
核心语法 javapublic class Parent { public void method() { // 父类实现 }}public class Child extends Parent { @Override public void method() { // 子类实现 super.method(); // 可选:调用父类方法 }}
易错点 - 必须使用 @Override 注解- 方法签名必须完全相同- 返回类型必须相同或为协变类型- 访问修饰符不能比父类更严格- 不能抛出比父类更多的 checked 异常

16. 类型转换(Type Casting)

类别 内容
英文备注 Upcasting & Downcasting
核心语法 java// 向上转型(隐式)Parent parent = new Child();// 向下转型(显式,需要强制转换)if (parent instanceof Child) { Child child = (Child) parent;}
易错点 - 向上转型会丢失子类特有的方法- 向下转型前必须使用 instanceof 检查- 错误的向下转型会抛出 ClassCastException- 只有当父类引用实际指向子类对象时才能安全向下转型

17. Comparable 接口

类别 内容
英文备注 Comparable Interface
核心语法 javapublic class ClassName implements Comparable<ClassName> { @Override public int compareTo(ClassName other) { // 比较逻辑 if (this.field < other.field) return -1; if (this.field > other.field) return 1; return 0; }}// 使用排序Arrays.sort(array);
易错点 - compareTo () 方法必须返回 int 值- 实现必须是一致的(x.compareTo (y) == 0 暗示 x.equals (y))- 排序是稳定的,相等的元素保持原有顺序- 可以使用包装类的 compare 方法简化实现