13.1 万字长文,深入解析--抽象类和接口

目录

【本节目标】

[1. 抽象类](#1. 抽象类)

​编辑

​编辑

[1.1 抽象类概念](#1.1 抽象类概念)

[1.2 抽象类语法](#1.2 抽象类语法)

[1.3 抽象类的六种特性](#1.3 抽象类的六种特性)

[1.4 抽象类的作用](#1.4 抽象类的作用)

[2. 接口](#2. 接口)

[2.1 接口的概念](#2.1 接口的概念)

[2.2 语法规则](#2.2 语法规则)

[2.3 接口使用](#2.3 接口使用)

[1. USB接口](#1. USB接口)

[2. Mouse鼠标类](#2. Mouse鼠标类)

[3. KeyBoard键盘类](#3. KeyBoard键盘类)

[4. Computer笔记本电脑类](#4. Computer笔记本电脑类)

[5. TestUSB测试类](#5. TestUSB测试类)

[2.4 接口的九种特性](#2.4 接口的九种特性)

[2.5 实现多个接口](#2.5 实现多个接口)

[Animal 基类:](#Animal 基类:)

接口定义:

核心知识点

创建四个具体的动物:

接口设计的优点:

核心要点

[2.6 接口间的继承](#2.6 接口间的继承)

[2.7 接口使用实例](#2.7 接口使用实例)

对象之间进行大小关系比较:

方式一:使用Comparable接口

方式二:使用Comparator接口

补充1:使用Arrays.sort方法进行数组元素的比较

补充2:Arrays.sort方法传入一个比较器

补充3:用实现了comparable的Student类去实现冒泡排序

补充4:冒泡从大到小排序:

[2.8 Clonable 接口和深拷贝:](#2.8 Clonable 接口和深拷贝:)

[2.8.1 浅拷贝引入:](#2.8.1 浅拷贝引入:)

[⚠️ 代码问题总结](#⚠️ 代码问题总结)

[2.8.2 深拷贝引入:](#2.8.2 深拷贝引入:)

[🧠 深拷贝核心机制解析](#🧠 深拷贝核心机制解析)

[① 代码执行流程](#① 代码执行流程)

[② 内存模型对比](#② 内存模型对比)

[③ 运行结果](#③ 运行结果)

[🔍 深拷贝关键点总结](#🔍 深拷贝关键点总结)

[💡 深拷贝的应用场景](#💡 深拷贝的应用场景)

[⚠️ 注意事项](#⚠️ 注意事项)

[浅拷贝 VS 深拷贝](#浅拷贝 VS 深拷贝)

[2.9 抽象类和接口的区别](#2.9 抽象类和接口的区别)

3.小试牛刀

[1. 关于Java抽象类,以下哪个陈述是错误的?​](#1. 关于Java抽象类,以下哪个陈述是错误的?)

[2. 关于Java接口,以下哪个说法是正确的?​](#2. 关于Java接口,以下哪个说法是正确的?)

[3. 以下关于内部类的描述,哪个是错误的?​](#3. 以下关于内部类的描述,哪个是错误的?)

[4. 关于抽象类和接口的区别,以下哪个说法是正确的?​](#4. 关于抽象类和接口的区别,以下哪个说法是正确的?)

[📌 核心考点总结:](#📌 核心考点总结:)

4.知识掌握程度自测:

[5. 总结(特别重要!!!):](#5. 总结(特别重要!!!):)

(1)为何只需实现compare?Comparator接口的继承奥秘

(2)详解Comparable与Comparator的四种实际应用


【本节目标】

  1. 抽象类

  2. 接口

  3. Object类

1. 抽象类

  1. Shape.java(基类)
java 复制代码
package demo1;

/**
 * @Author 12629
 * @Description
 */
public class Shape {
    public void draw() {
        System.out.println("画一个图形.....");
    }
}
  1. Cycle.java(子类1)
java 复制代码
package demo1;

public class Cycle extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个圆圈.....");
    }
}
  1. Rect.java(子类2)
java 复制代码
package demo1;

public class Rect extends Shape {
    @Override
    public void draw() {
        System.out.println("画一个矩形.....");
    }
}
  1. Flower.java(子类3)
java 复制代码
package demo1;

public class Flower extends Shape {
    @Override
    public void draw() {
        System.out.println("画一朵花.....");
    }
}

13.250317-抽象类和接口--00:21:20->00:23:40
Test.java​ (测试类)

java 复制代码
package demo1;

/**
 * @Author 12629
 * @Description:
 */
public class Test {

    public static void drawMap(Shape shape) {
        shape.draw();
    }

    public static void main(String[] args) {
        Shape shape = new Cycle();
        drawMap(new Cycle());
        drawMap(new Flower());
        drawMap(new Rect());
    }
}

测试代码

13.250317-抽象类和接口--00:21:50->00:37:10

13.250317-抽象类和接口--00:37:10->00:44:30
抽象基类Cycle(继承自 Shape)和派生类 A(继承自 Cycle

java 复制代码
public abstract class Cycle extends Shape {
    
    public abstract void test();
}

class A extends Cycle {
    
    @Override
    public void test() {
        // 方法体(当前为空)
    }
    
    @Override
    public void draw() {
        // 方法体(当前为空)
    }
}

总结:抽象方法一定要被重写,没重写一定会出问题!!!

13.250317-抽象类和接口--00:44:30->00:48:50

13.250317-抽象类和接口--00:48:50->00:52:24
13.250317-抽象类和接口--00:52:24->00:59:00

1.1-1.4讲解:知识点见下方

1.1 抽象类概念

在面向对象的概念中,所有的对象都是通过类来描绘的,但是反过来,并不是所有的类都是用来描绘对象的,如果一个类中没有包含足够的信息来描绘一个具体的对象,这样的类就是抽象类。

比如:

在打印图形例子中,我们发现,父类 Shape 中的 draw 方法好像并没有什么实际工作,主要的绘制图形都是由 Shape 的各种子类的 draw 方法来完成的。像这种没有实际工作的方法,我们可以把它设计成一个 抽象方法(abstract method),包含抽象方法的类我们称为 抽象类(abstract class)

1.2 抽象类语法

在 Java 中,一个类如果被 abstract 修饰称为抽象类,抽象类中被 abstract 修饰的方法称为抽象方法,抽象方法不用给出具体的实现体。

java 复制代码
// 抽象类:被abstract修饰的类 
public abstract class Shape {
 // 抽象⽅法:被abstract修饰的⽅法,没有⽅法体 
 abstract public void draw();
 abstract void calcArea();
 // 抽象类也是类,也可以增加普通⽅法和属性 
 public double getArea(){
 return area;
 }
 protected double area; // ⾯积 
}

注意:抽象类也是类,内部可以包含普通方法和属性,甚至构造方法

1.3 抽象类的六种特性

  1. 抽象类不能直接实例化对象
java 复制代码
Shape shape = new Shape();
// 编译出错 
Error:(30, 23) java: Shape是抽象的; ⽆法实例化
  1. 抽象方法不能是private的
java 复制代码
abstract class Shape {
 abstract private void draw();
}
// 编译出错 
Error:(4, 27) java: ⾮法的修饰符组合: abstract和private
  1. 抽象方法不能被final和static修饰,因为抽象方法要被子类重写
java 复制代码
public abstract class Shape {
 abstract final void methodA();
 abstract public static void methodB();
}
// 编译报错: 
// Error:(20, 25) java: ⾮法的修饰符组合: abstract和final 
// Error:(21, 33) java: ⾮法的修饰符组合: abstract和static 
  1. 抽象类必须被继承,并且继承后子类要重写父类中的抽象方法,否则子类也是抽象类,必须要使⽤ abstract 修饰
java 复制代码
//矩形类
public class Rect extends Shape {
    private double length;
    private double width;

    Rect(double length, double width) {
        this.length = length;
        this.width = width;
    }

    public void draw() {
        System.out.println("矩形:length=" + length + " width=" + width);
    }

    public void calcArea() {
        area = length * width;
    }
}

//圆类:
public class Circle extends Shape {
    private double r;
    final private static double PI = 3.14;

    public Circle(double r) {
        this.r = r;
    }

    public void draw() {
        System.out.println("圆:r=" + r);
    }

    public void calcArea() {
        area = PI * r * r;
    }
}

//三角形类:
public abstract class Triangle extends Shape {
    private double a;
    private double b;
    private double c;

    @Override
    public void draw() {
        System.out.println("三角形:a=" + a + " b=" + b + " c=" + c);
    }

    //三角形:直角三角形、等腰三角形等,还可以继续细化
    //@Override
    //double calcArea(); //编译失败:要么实现该抽象方法,要么将三角形设计为抽象类
}
  1. 抽象类中不⼀定包含抽象方法,但是有抽象方法的类⼀定是抽象类

  2. 抽象类中可以有构造方法,供子类创建对象时,初始化父类的成员变量

1.4 抽象类的作用

抽象类本身不能被实例化,要想使用,只能创建该抽象类的子类。然后让子类重写抽象类中的抽象方法。

有些同学可能会说了,普通的类也可以被继承呀,普通的方法也可以被重写呀,为啥非得用抽象类和抽象方法呢?

确实如此。但是使用抽象类相当于多了一重编译器的校验。

使用抽象类的场景就如上面的代码,实际工作不应该由父类完成,而应由子类完成。那么此时如果不小心误用成父类了,使用普通类编译器是不会报错的。但是父类是抽象类就会在实例化的时候提示错误,让我们尽早发现问题。

很多语法存在的意义都是为了"预防出错",例如我们曾经用过的final也是类似。创建的变量用户不去修改,不就相当于常量嘛?但是加上final能够在不小心误修改的时候,让编译器及时提醒我们。

充分利用编译器的校验,在实际开发中是非常有意义的。


2. 接口

13.250317-抽象类和接口--01:17:41->01:24:55

java 复制代码
package demo2;

/**
 * @Author 12629
 * @Description:
 */
public interface IShape {
    int SIZE = 10;
    
    void test();
}

++命名规范:把前面的修饰符省略...++

13.250317-抽象类和接口--01:24:55->01:26:43
13.250317-抽象类和接口--01:26:43->01:29:00
13.250317-抽象类和接口--01:29:00->01:32:35
13.250317-抽象类和接口--01:32:35->01:42:00

2.1 接口的概念

在现实生活中,接口的例子比比皆是,比如:笔记本上的USB口,电源插座等。

电脑的USB口上,可以插:U盘、鼠标、键盘...所有符合USB协议的设备。

电源插座插座上,可以插:电脑、电视机、电饭煲...所有符合规范的设备。

通过上述例子可以看出:接口就是公共的行为规范标准,大家在实现时,只要符合规范标准,就可以通用。

在Java中,接口可以看成是:多个类的公共规范,是一种引用数据类型。

2.2 语法规则

接口的定义格式与定义类的格式基本相同,将class关键字换成interface关键字,就定义了一个接口。

java 复制代码
public interface 接口名称 {
    // 抽象方法

    // 方式1: public abstract是固定搭配,可以不写
    public abstract void method1();

    // 方式2
    public void method2();

    // 方式3
    abstract void method3();

    // 方式4 (推荐)
    void method4();

    // 注意:在接口中上述写法都是抽象方法,更推荐方式4,代码更简洁
}

提示:

  1. 创建接口时,接口的命名一般以大写字母 I 开头。

  2. 接口的命名一般使用"形容词"词性的单词。

  3. 阿里编码规范中约定,接口中的方法和属性不要加任何修饰符号,保持代码的简洁性。

2.3 接口使用

接口不能直接使用,必须要有一个"实现类"来"实现"该接口,实现接口中的所有抽象方法。

java 复制代码
public class 类名称 implements 接口名称 {
    // ...
}

注意 :子类和父类之间是 extends继承关系,类与接口之间是 implements实现关系。

实例说明:实现笔记本电脑使用USB鼠标、USB键盘的例子

  1. USB接口:包含打开设备、关闭设备功能

  2. 笔记本类:包含开机功能、关机功能、使用USB设备功能

  3. 鼠标类:实现USB接口,并具备点击功能

  4. 键盘类:实现USB接口,并具备输入功能

1. USB接口

java 复制代码
public interface USB {
    void openDevice();
    void closeDevice();
}

2. Mouse鼠标类

java 复制代码
public class Mouse implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开鼠标");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭鼠标");
    }

    public void click() {
        System.out.println("鼠标点击");
    }
}

3. KeyBoard键盘类

java 复制代码
public class KeyBoard implements USB {
    @Override
    public void openDevice() {
        System.out.println("打开键盘");
    }

    @Override
    public void closeDevice() {
        System.out.println("关闭键盘");
    }

    public void inPut(){
        System.out.println("键盘输入");
    }
}

4. Computer笔记本电脑类

java 复制代码
public class Computer {
    public void powerOn() {
        System.out.println("打开笔记本电脑");
    }
    
    public void powerOff() {
        System.out.println("关闭笔记本电脑");
    }
    
    public void useDevice(USB usb) {
        usb.openDevice();
        if(usb instanceof Mouse){
            Mouse mouse = (Mouse)usb;
//上面一行要向下转型是因为:
//函数接收参数的时候有向上转型,根据原则:
//通过父类引用(此处为接口)只能调用
//自己的方法
//和子类重写接口的方法(发生动态绑定,调取子类自己的方法)
//所以此处需要向下转型
            mouse.click();
        }else if(usb instanceof KeyBoard){
            KeyBoard keyBoard = (KeyBoard)usb;
            keyBoard.inPut();
        }
        usb.closeDevice();
    }
}

5. TestUSB测试类

java 复制代码
public class TestUSB{
    public static void main(String[] args) {
        Computer computer = new Computer();
        computer.powerOn();
        
        //使用鼠标设备
        computer.useDevice(new Mouse());
        
        //使用键盘设备
        computer.useDevice(new KeyBoard());
        
        computer.powerOff();
    }
}

程序说明:

这是一个完整的面向对象多态编程示例:

  1. USB接口定义了设备连接的通用协议(打开/关闭)

  2. Mouse ​ 和 KeyBoard​ 类实现了该接口,并各自有专有功能

  3. Computer​ 类通过接口引用接收设备,运行时判断具体类型来调用特有方法

  4. TestUSB​ 展示了接口多态的实际使用场景

执行结果会是:

打开笔记本电脑

打开鼠标

鼠标点击

关闭鼠标

打开键盘

键盘输入

关闭键盘

关闭笔记本电脑

13.250317-抽象类和接口--01:42:00->02:14:10

2.4 接口的九种特性

1. 特性一

**特性:**​ 接口类型是一种引用类型,但是不能直接new接口的对象

java 复制代码
public class TestUSB {
    public static void main(String[] args) {
        USB usb = new USB();
    }
}

**编译错误信息:**​

Error:(10, 19) java: day20210915.USB是抽象的;无法实例化

2. 特性二

特性: ​ 接口中每一个方法都是public的抽象方法,即接口中的方法会被隐式的指定为public abstract(只能是public abstract,其他修饰符都会报错)

java 复制代码
public interface USB {
    // Error:(4, 18) java: 此处不允许使用修饰符private
    private void openDevice();
    
    void closeDevice();
}

3. 特性三

**特性:**​ 接口中的方法是不能在接口中实现的,只能由实现接口的类来实现

java 复制代码
public interface USB {
    void openDevice();
    
    // 编译失败:因为接口中的方法默认为抽象方法
    // Error:(5, 23) java: 接口抽象方法不能带有主体
    void closeDevice() {
        System.out.println("关闭USB设备");
    }
}

4.特性四

重写接口中方法时,不能使用默认的访问权限

java 复制代码
public interface USB {
    void openDevice(); // 默认是public的
    void closeDevice(); // 默认是public的
}

public class Mouse implements USB {
    @Override
    void openDevice() {
        System.out.println("打开鼠标");
    }
    // ...
}
// 编译报错,重写USB中openDevice方法时,不能使用默认修饰符
// 正在尝试分配更低的访问权限;以前为public

5.特性五

接口中可以含有变量,但是接口中的变量会被隐式的指定为 public static final变量

java 复制代码
public interface USB {
    double brand = 3.0; // 默认被: final public static修饰
    void openDevice();
    void closeDevice();
}

public class TestUSB {
    public static void main(String[] args) {
        System.out.println(USB.brand); // 可以直接通过接口名访问,说明是静态的
        // 编译报错: Error:(12, 12) java: 无法为最终变量brand分配值
        USB.brand = 2.0; // 说明brand具有final属性
    }
}

6.特性六

接口中不能有静态代码块和构造方法

java 复制代码
public interface USB {
    //编译失败
    public USB(){
    }
    
    //编译失败
    static {
    }
    
    void openDevice();
    void closeDevice();
}

7.特性七

接口虽然不是类,但是接口编译完成后字节码文件的后缀格式也是.class

8.特性八

如果类没有实现接口中的所有的抽象方法,则类必须设置为抽象类

13.250317-抽象类和接口--02:31:25->02:33:37

9.特性九

jdk8中:接口中还可以包含default方法

default方法可以不重写...也可以重写从而发生动态绑定!!!

13.250317-抽象类和接口--02:33:37->02:39:22

2.5 实现多个接口

13.250317-抽象类和接口--02:39:22->02:49:15

在 Java 中,类和类之间是单继承的,一个类只能有一个父类,即 Java 中不支持多继承,但是一个类可以实现多个接口。下面通过类来表示一组动物。

Animal 基类:

java 复制代码
class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
}

另外我们再提供一组接口,分别表示"会飞的"、"会跑的"、"会游泳的"。

接口定义:

java 复制代码
interface IFlying {
    void fly();
}

interface IRunning {
    void run();
}

interface ISwimming {
    void swim();
}

核心知识点

  1. 单一继承 :Java 中一个类只能直接继承一个父类(如 Animal)。

  2. 多重实现 :一个类可以同时实现多个接口(如 IFlying, IRunning, ISwimming),从而具备多种行为特征。

  3. 设计优势:通过接口组合,可以更灵活地定义对象的能力,突破单继承的限制。

此示例为后续演示一个类(如 Dog, Bird等)如何通过实现多个接口来扩展功能奠定了基础。

创建四个具体的动物:

猫,是会跑的。代码1 - Cat类:

java 复制代码
class Cat extends Animal implements IRunning {
    public Cat(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        System.out.println(this.name + "正在用四条腿跑");
    }
}

鱼,是会游的。代码2 - Fish类:

java 复制代码
class Fish extends Animal implements ISwimming {
    public Fish(String name) {
        super(name);
    }
    
    @Override
    public void swim() {
        System.out.println(this.name + "正在用尾巴游泳");
    }
}

青蛙,既能跑,又能游(两栖动物)。代码3 - Frog类:

java 复制代码
class Frog extends Animal implements IRunning, ISwimming {
    public Frog(String name) {
        super(name);
    }
    
    @Override
    public void run() {
        System.out.println(this.name + "正在往前跳");
    }
    
    @Override
    public void swim() {
        System.out.println(this.name + "正在蹬腿游泳");
    }
}

注意:一个类实现多个接口时,每个接口中的抽象方法都要实现,否则类必须设置为抽象类。

提示,IDEA中使用ctrl+i快速实现接口

还有一些神奇的动物,水陆空三栖,叫做"鸭子"。

代码部分 (Duck类):

java 复制代码
class Duck extends Animal implements IRunning, ISwimming, IFlying {
    public Duck(String name) {
        super(name);
    }

    @Override
    public void fly() {
        System.out.println(this.name + "正在用翅膀飞");
    }

    @Override
    public void run() {
        System.out.println(this.name + "正在用两条腿跑");
    }

    @Override
    public void swim() {
        System.out.println(this.name + "正在漂在水上");
    }
}

上面的代码展示了Java面向对象编程中最常见的用法:一个类继承一个父类,同时实现多种接口。

继承与接口的核心差异:

  • 继承(Inheritance) :表达 is-a语义(如"猫是一种动物")。

  • 接口(Interface) :表达 has-a特性/能力(如"猫具有会跑的特性")。

举例说明:

  • 猫是一种动物,具有会跑的特性。

  • 青蛙也是一种动物,既能跑,也能游泳。

  • 鸭子也是一种动物,既能跑,也能游,还能飞。

接口设计的优点:

时刻牢记多态的好处------让程序员忘记类型。有了接口之后,类的使用者就不必关注具体类型,而只关注某个类是否具备某种能力

例如,现在实现一个方法,叫"散步"

  1. "散步"方法示例:
java 复制代码
public static void walk(IRunning running) {
    System.out.println("我带伙伴去散步");
    running.run();
}
  1. 多态调用示例与执行结果

在这个walk方法内部,我们并不关注到底是哪种动物,只要参数是会跑的,就行

java 复制代码
Cat cat = new Cat("小猫");
walk(cat);

Frog frog = new Frog("小青蛙");
walk(frog);

// 执行结果:

// 我带伙伴去散步

// 小猫正在用四条腿跑

// 我带伙伴去散步

// 小青蛙正在往前跳

甚至参数可以不是"动物",只要会跑!

java 复制代码
class Robot implements IRunning {
    private String name;
    
    public Robot(String name) {
        this.name = name;
    }
    
    @Override
    public void run() {
        System.out.println(this.name + "正在用轮子跑");
    }
}

Robot robot = new Robot("机器人");
walk(robot);

// 执行结果:
机器人正在用轮子跑

核心要点

  1. 接口的多态扩展性Robot类并非 Animal的子类,但通过实现 IRunning接口,同样可以作为参数传递给 walk(IRunning running)方法。

  2. 设计灵活性 :这展示了面向接口编程的核心优势------关注行为(能力)而非类型(身份) 。只要对象具备"会跑"的能力(实现 IRunning接口),就可以参与到相关的业务逻辑中。

  3. 代码复用 :原有的 walk()方法无需任何修改,即可支持新的 Robot类型,体现了开闭原则(对扩展开放,对修改关闭)。

注:此示例是对前述"继承表达 is-a,接口表达 has-a"原则的强力补充,说明接口能够突破继承体系的限制,实现更灵活的设计。
13.250317-抽象类和接口--02:49:15->03:01:37(上一个节点到此)

2.6 接口间的继承

14.250319-接口--00:24:30->00:27:30

14.250319-接口--00:30:20->00:36:30

在Java中,类和类之间是单继承的,一个类可以实现多个接口,接口与接口之间可以多继承。即:用接口可以达到多继承的目的。

接口可以继承一个接口,达到复用的效果。使用extends关键字。

java 复制代码
// 定义两个接口
interface IRunning {
    void run();
}

interface ISwimming {
    void swim();
}

// 接口多继承
interface IAmphibious extends IRunning, ISwimming {
    
}

// 实现类
class Frog implements IAmphibious {
    // 必须实现两个接口的所有方法
    @Override
    public void run() {
        System.out.println("青蛙跳一跳");
    }
    
    @Override
    public void swim() {
        System.out.println("青蛙在游泳");
    }
}

通过接口继承创建一个新的接口IAmphibious表示"两栖的"。此时实现接口创建的Frog类,就继续要实现run方法,也需要实现swim方法。

接口间的继承相当于把多个接口合并在一起。

2.7 接口使用实例

对象之间进行大小关系比较:

java 复制代码
class Student {
    public String name;
    public int score;
    
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    
    @Override
    public String toString() {
        return "[" + this.name + ":" + this.score + "]";
    }
}
java 复制代码
class Test {
    public static void main(String[] args) {
        Student s1 = new Student("张三", 10);
        Student s2 = new Student("李四", 20);
        
        // 这里会编译报错
        System.out.println(s1 > s2);
    }
}

此时程序会编译报错,并没有指定根据分数还是什么进行大小比较。所以,应该指定以什么样的方式进行比较。

不太好的点是,只要按照该方式比较后,不灵活。无法按照其他方式进行比较

方式一:使用Comparable接口

使用Comparable接口

java 复制代码
public class Student implements Comparable<Student> { 
    public String name; 
    public int age; 
    public double score; 
    
    public Student(String name, int age, double score) { 
        this.name = name; 
        this.age = age; 
        this.score = score; 
    } 
    
    @Override
    public String toString() { 
        return "Student{" + 
            "name='" + name + '\'' + 
            ", age=" + age + 
            ", score=" + score + 
            '}';
    }
    
    @Override
    public int compareTo(Student o) { 
        /* if(this.age > o.age) { 
            return 1; 
        } else if(this.age == o.age) { 
            return 0; 
        } else { 
            return -1; 
        } */ 
        return this.age - o.age; 
    }
}
java 复制代码
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("zhangsan", 10, 89.3);
        Student student2 = new Student("lisi", 5, 90.29);
        
        if(student1.compareTo(student2) > 0) {
            System.out.println("s1 > s2");
        }
    }
}

14.250319-接口--00:36:30->00:56:00

让我们的Student类实现Comparable接口,并实现其中的compareTo方法

java 复制代码
class Student implements Comparable {
  private String name;
  private int score;

  public Student(String name, int score) {
    this.name = name;
    this.score = score;
  }

  @Override
  public String toString() {
    return "[" + this.name + ":" + this.score + "]";
  }

  @Override
  public int compareTo(Object o) { //用Object比较少,最多的还是我上方的代码截图
    Student s = (Student)o;
    if (this.score > s.score) {
      return -1;
    } else if (this.score < s.score) {
      return 1;
    } else {
      return 0;
    }
  }
}
java 复制代码
public class Test {
  public static void main(String[] args) {
    Student s1 = new Student("zhangsan",10);
    Student s2 = new Student("lisi",20);
    System.out.println(s1.compareTo(s2));
  }
}

如果s1大于s2那么返回大于0的数字, 如果相同返回0, 否则返回小于0的数字。

方式二:使用Comparator接口

使用Comparator接口:

  1. AgeComparator.java(年龄比较器)
java 复制代码
public class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
  1. ScoreComparator.java(分数比较器)
java 复制代码
public class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return (int)(o1.score - o2.score);
    }
}
  1. Test.java(测试类)
java 复制代码
public class Test {
    public static void main(String[] args) {
        Student student1 = new Student("zhangsan", 10, 89.3);
        Student student2 = new Student("lisi", 5, 90.29);
        
        // 使用年龄比较器
        AgeComparator ageComparator = new AgeComparator();
        int ret = ageComparator.compare(student1, student2);
        if (ret > 0) {
            System.out.println("年龄关系:s1>s2");
        }
        
        // 使用分数比较器
        ScoreComparator scoreComparator = new ScoreComparator();
        int ret2 = scoreComparator.compare(student1, student2);
        if (ret2 > 0) {
            System.out.println("分数关系:s1>s2");
        } else {
            System.out.println("分数关系:s1<s2");
        }
    }
}

1. String类的接口实现

java 复制代码
public final class String
    implements java.io.Serializable, Comparable<String>, CharSequence,
    Constable, ConstantDesc {
    // 类体内容...
}

解释String类实现了多个接口,其中 Comparable<String>表明 String对象可通过 compareTo()方法进行自然排序(按字典顺序比较)。

  1. NameComparator比较器
java 复制代码
public class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        // return o1.name - o2.name;  // 错误写法,不能直接对字符串做减法
        return o1.name.compareTo(o2.name);
    }
}
  1. 比较器使用示例
java 复制代码
NameComparator nameComparator = new NameComparator();
int ret3 = nameComparator.compare(student1, student2);
if(ret3 > 0) {
    System.out.println("姓名关系:s1 > s2");
} else {
    System.out.println("姓名关系:s1 < s2");  // 注意:图片中此处是"编辑关系",应为"姓名关系"
}

14.250319-接口--00:56:00->01:12:10

补充1:使用Arrays.sort方法进行数组元素的比较

java 复制代码
public class Person implements Comparable<Person> {
    public String name;
    public int age;
    public double score;

    public Person(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }

    @Override
    public int compareTo(Person o) {
        // 从小到大比较
        // return this.age - o.age;
        return o.age - this.age;
    }
}
java 复制代码
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        // 1. 创建Person数组并初始化
        Person[] people = new Person[3];
        people[0] = new Person("zhangsan", 10, 89.3);
        people[1] = new Person("lisi", 5, 90.29);
        people[2] = new Person("abc", 15, 10.29);
        
        // 2. 对Person数组排序
        Arrays.sort(people);
        System.out.println(Arrays.toString(people));
        
        System.out.println("=================");
        
        // 3. 创建并排序基本类型数组
        int[] array = {1, 12, 31, 14, 5};
        Arrays.sort(array);
        System.out.println(Arrays.toString(array));
    }
}

14.250319-接口--01:25:35->01:45:00

补充2:Arrays.sort方法传入一个比较器

  1. ScoreComparator.java(分数比较器)
java 复制代码
public class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return (int)(o1.score - o2.score);
    }
}
  1. Test.java(测试主类)
java 复制代码
import java.util.Arrays;

public class Test {
    public static void main(String[] args) {
        Student[] students = new Student[3];
        students[0] = new Student("zhangsan", 10, 89.3);
        students[1] = new Student("lisi", 5, 90.29);
        students[2] = new Student("abc", 15, 10.29);
        
        ScoreComparator scoreComparator = new ScoreComparator();
        Arrays.sort(students, scoreComparator);
        
        System.out.println(Arrays.toString(students));
    }
}
  • 创建比较器实例 scoreComparator

  • 调用 Arrays.sort(数组, 比较器)students数组进行排序

++核心机制:++ Arrays.sort(students, scoreComparator)

++工作原理:++

  1. 传递策略 :将 scoreComparator对象作为排序策略 传递给 sort()方法

  2. 回调机制sort()内部算法在需要比较两个元素时,会自动调用 scoreComparator.compare(o1, o2)

  3. 动态排序:根据比较器的返回结果(正数、负数、零)决定元素顺序

++与自然排序的区别:++

方式 示例 特点
自然排序 Arrays.sort(students) 需实现 Comparable接口,只有一种默认顺序
定制排序 Arrays.sort(students, 比较器) 更灵活,可定义多种排序规则(如按年龄、分数、姓名)

14.250319-接口--01:45:00->01:49:45

补充3:用实现了comparable的Student类去实现冒泡排序

1. Student 类(学生类)

java 复制代码
// 这个类必须存在,但图片中没有展示
public class Student implements Comparable<Student> {
    public String name;
    public int age;
    public double score;
    
    public Student(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
    
    // 必须实现 compareTo 方法才能用 Arrays.sort(students)
    @Override
    public int compareTo(Student o) {
        // 需要定义比较规则,比如按年龄
        return this.age - o.age;
    }
    
    // 还需要 toString 方法以便 Arrays.toString() 能正确输出
    @Override
    public String toString() {
        return String.format("Student[name=%s, age=%d, score=%.2f]", 
                            name, age, score);
    }
}
  1. Main 方法代码 ​ (在 Test类中)
java 复制代码
public static void main(String[] args) {
    Student[] students = new Student[3];
    students[0] = new Student(name: "zhangsan", age: 10, score: 89.3);
    students[1] = new Student(name: "lisi", age: 5, score: 90.29);
    students[2] = new Student(name: "abc", age: 15, score: 10.29);
    
    //ScoreComparator scoreComparator = new ScoreComparator();
    
    //Arrays.sort(students);
    bubbleSort(students);
    
    System.out.println(Arrays.toString(students));
    
}
java 复制代码
    // Shape[] shapes = {new Cycle(), ...};
  1. bubbleSort 方法实现
java 复制代码
public static void bubbleSort(Comparable[] comparables) {
    // i代表趟数
    for (int i = 0; i < comparables.length - 1; i++) {
        for (int j = 0; j < comparables.length - 1 - i; j++) {
            if (comparables[j].compareTo(comparables[j + 1]) > 0) {
                // 交换
                Comparable tmp = comparables[j];
                comparables[j] = comparables[j + 1];
                comparables[j + 1] = tmp;
            }
        }
    }
}

14.250319-接口--01:49:45->01:56:15

补充4:冒泡从大到小排序:

14.250319-接口--01:58:40->02:02:50

java 复制代码
class Student {
    public String name;
    public int score;
    
    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }
    
    @Override
    public String toString() {
        return "(" + this.name + ":" + this.score + ")";
    }
}

class ScoreComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.score - o2.score;
    }
}

class NameComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.name.compareTo(o2.name);
    }
}
java 复制代码
public class Test {
    public static void main(String[] args) {
        Student s1 = new Student("zhangsan", 10);
        Student s2 = new Student("lisi", 20);
        
        // 根据分数进行比较
        ScoreComparator scoreComparator = new ScoreComparator();
        System.out.println(scoreComparator.compare(s1, s2));
        
        // 根据姓名进行比较
        NameComparator nameComparator = new NameComparator();
        System.out.println(nameComparator.compare(s1, s2));
    }
}

2.8 Clonable 接口和深拷贝:

  1. Person.java
java 复制代码
package demo3;

public class Person {
    public String name;
    public int age;
    public double score;
    
    public Person(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
    
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  1. Test.java
java 复制代码
package demo3;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person("zhangsan", 10, 89.3);
        Person person2 = (Person) person1.clone();
        System.out.println(person2);
    }
}

报错了!!!解决办法:

实现Cloneable接口

此时:

14.250319-接口--02:28:10->02:42:00

14.250319-接口--02:42:00->02:43:43

2.8.1 浅拷贝引入:

++Money 类和 Person 类定义++

java 复制代码
package demo3;

class Money {
    public double money = 9.9;
}

public class Person implements Cloneable {
    public String name;
    public int age;
    public double score;

    public Money m = new Money();
    
    public Person(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
    
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
}

++Test 类基本框架++

java 复制代码
package demo3;

/**
 * @Author 12629
 * @Description:
 */
public class Test {
    public static void main(String[] args) {
        Person person1 = new Person(name: "zhangsan", age: 10, score: 10.9);
    }
}

++内存示意图(无代码,是内存模型图示)++


++Test 类(添加克隆操作):++

java 复制代码
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person(name: "zhangsan", age: 10, score: 10.9);
        Person person2 = (Person) person1.clone();
    }
}

++内存示意图(克隆后)++

++Test 类(完整测试代码)++

java 复制代码
public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person(name: "zhangsan", age: 10, score: 10.9);
        Person person2 = (Person) person1.clone();
        
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
        
        System.out.println("===============");
        
        person2.m.money = 19.99;
        
        System.out.println(person1.m.money);
        System.out.println(person2.m.money);
    }
}

++程序运行结果++

14.250319-接口--02:43:43->02:50:50

⚠️ 代码问题总结

  1. IDE显示格式 :代码中的 name: "zhangsan"是IDE参数提示格式,不是标准Java语法

  2. 浅克隆问题:Money对象被两个Person对象共享

  3. toString不一致:Person类的toString返回"Student{...}"

  4. 没有实现clone():Person类声明了Cloneable但没实现clone()方法

📋 标准Java代码应该是:

java 复制代码
// Person.java
package demo3;

public class Person implements Cloneable {
    public String name;
    public int age;
    public double score;
    public Money m = new Money();
    
    public Person(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
    
    // 需要实现clone()方法
    @Override
    protected Object clone() throws CloneNotSupportedException {
        Person cloned = (Person) super.clone();
        cloned.m = (Money) this.m.clone();  // 深克隆Money对象
        return cloned;
    }
    
    @Override
    public String toString() {
        return "Person{name='" + name + "', age=" + age + ", score=" + score + "}";
    }
}
java 复制代码
// Money.java
package demo3;

class Money implements Cloneable {
    public double money = 9.9;
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
java 复制代码
// Test.java
package demo3;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 标准Java语法
        Person person1 = new Person("zhangsan", 10, 10.9);
        Person person2 = (Person) person1.clone();
        
        // 测试代码...
    }
}

2.8.2 深拷贝引入:

完整的深拷贝实现代码测试代码,并整理如下:

📦 代码结构

  1. Money.java
java 复制代码
package demo3;

class Money implements Cloneable {
    public double money = 9.9;
    
    @Override
    protected Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}
  1. Person.java
java 复制代码
package demo3;

public class Person implements Cloneable {
    public String name;
    public int age;
    public double score;
    public Money m = new Money();  // 引用类型成员
    
    public Person(String name, int age, double score) {
        this.name = name;
        this.age = age;
        this.score = score;
    }
    
    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", age=" + age +
                ", score=" + score +
                '}';
    }
    
    // ✅ 深拷贝实现(核心代码)
    @Override
    protected Object clone() throws CloneNotSupportedException {
        // return super.clone();  // 浅拷贝(被注释掉)
        
        // 深拷贝实现
        Person tmp = (Person) super.clone();      // 1. 克隆Person对象本身
        tmp.m = (Money) this.m.clone();           // 2. 克隆Money成员(关键!)
        return tmp;                               // 3. 返回深拷贝对象
    }
}
  1. Test.java
java 复制代码
package demo3;

public class Test {
    public static void main(String[] args) throws CloneNotSupportedException {
        // 创建原对象
        Person person1 = new Person("zhangsan", 10, 10.9);
        
        // 深拷贝克隆
        Person person2 = (Person) person1.clone();
        
        // 测试深拷贝效果
        System.out.println("克隆前对比:");
        System.out.println("person1.m.money = " + person1.m.money);  // 9.9
        System.out.println("person2.m.money = " + person2.m.money);  // 9.9
        
        System.out.println("====================");
        
        // 修改克隆对象的money
        person2.m.money = 19.99;
        
        System.out.println("修改person2后对比:");
        System.out.println("person1.m.money = " + person1.m.money);  // 9.9(深拷贝:不影响原对象)
        System.out.println("person2.m.money = " + person2.m.money);  // 19.99
    }
}

++运行结果:++

🧠 深拷贝核心机制解析

代码执行流程

java 复制代码
// 1. 调用super.clone()创建Person对象的浅拷贝
Person tmp = (Person) super.clone();
// 此时:tmp和原对象共享同一个Money对象(浅拷贝)

// 2. 对引用类型成员单独克隆
tmp.m = (Money) this.m.clone();
// 此时:tmp拥有独立的Money对象(深拷贝)

// 3. 返回深拷贝结果
return tmp;

内存模型对比

修改前(浅拷贝):

java 复制代码
person1 (0x123) ──→ Person{name="zhangsan", m=0x19} ──→ Money(0x19){9.9}
person2 (0x908) ──→ Person{name="zhangsan", m=0x19} ──→ Money(0x19){9.9}
                                              ↑共享↑

修改后(深拷贝):

java 复制代码
person1 (0x123) ──→ Person{name="zhangsan", m=0x19} ──→ Money(0x19){9.9}
person2 (0x908) ──→ Person{name="zhangsan", m=0x66} ──→ Money(0x66){9.9}
                                             各自独立

运行结果

java 复制代码
克隆前对比:
person1.m.money = 9.9
person2.m.money = 9.9
====================
修改person2后对比:
person1.m.money = 9.9    ← 原对象不受影响
person2.m.money = 19.99  ← 克隆对象修改成功

14.250319-接口--02:50:50->03:01:32

🔍 深拷贝关键点总结

步骤 代码 目的 内存变化
1 super.clone() 创建对象的浅拷贝 复制基本类型字段
2 this.m.clone() 克隆引用类型成员 创建新的Money对象
3 tmp.m = ... 替换引用为新对象 实现对象独立

💡 深拷贝的应用场景

  1. 需要完全独立的对象副本

  2. 多线程环境下避免共享状态

  3. 需要修改副本而不影响原对象

  4. 原型模式(Prototype Pattern)

⚠️ 注意事项

  1. 所有引用类型成员 都必须实现 Cloneable接口

  2. 嵌套引用需要递归处理(如Money内部还有引用)

  3. 构造器调用语法 :图片中的 new Person(name: "...")是IDE显示格式,实际应为 new Person("...")

  4. toString方法 :建议返回 "Person{...}"而非 "Student{...}"保持语义一致

这个深拷贝实现确保了Person对象及其Money成员都是独立的,修改任一对象不会影响其他对象。

Java 中内置了一些很有用的接口,Clonable 就是其中之一。

Object 类中存在一个 clone 方法,调用这个方法可以创建一个对象的 "拷贝"。但是要想合法调用 clone 方法,必须要先实现 Clonable 接口,否则就会抛出 CloneNotSupportedException 异常。

java 复制代码
class Animal implements Cloneable {
    private String name;
    
    @Override
    public Animal clone() {
        Animal o = null;
        try {
            o = (Animal)super.clone();
        } catch (CloneNotSupportedException e) {
            e.printStackTrace();
        }
        return o;
    }
}

public class Test {
    public static void main(String[] args) {
        Animal animal = new Animal();
        Animal animal2 = animal.clone();
        System.out.println(animal == animal2);
    }
}

// 输出结果
// false

浅拷贝 VS 深拷贝

Cloneable 拷贝出的对象是一份"浅拷贝"

观察以下代码:

java 复制代码
class Money {
    public double m = 99.99;
}

class Person implements Cloneable {
    public Money money = new Money();
    
    @Override
    public Object clone() throws CloneNotSupportedException {
        return super.clone();
    }
}

public class TestDemo3 {
    public static void main(String[] args) throws CloneNotSupportedException {
        Person person1 = new Person();
        Person person2 = (Person) person1.clone();
        
        System.out.println("通过person2修改前的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
        
        person2.money.m = 13.6;
        
        System.out.println("通过person2修改后的结果");
        System.out.println(person1.money.m);
        System.out.println(person2.money.m);
    }
}

执行结果

通过person2修改前的结果

99.99

99.99

通过person2修改后的结果

13.6

13.6
如上代码,我们可以看到,通过clone,我们只是拷贝了Person对象。但是Person对象中的Money对象,并没有拷贝。通过person2这个引用修改了m的值后,person1这个引用访问m的时候,值也发生了改变。这里就是发生了浅拷贝。那么同学们想一下如何实现深拷贝呢?

2.9 抽象类和接口的区别

抽象类和接口都是Java中多态的常见使用方式。都需要重点掌握。同时又要认清两者的区别(重要!!!常见面试题)。

核心区别:抽象类中可以包含普通方法和普通字段,这样的普通方法和字段可以被子类直接使用(不必重写),而接口中不能包含普通方法,子类必须重写所有的抽象方法。

如之前写的Animal例子。此处的Animal中包含一个name这样的属性,这个属性在任何子类中都是存在的。因此此处的Animal只能作为一个抽象类,而不应该成为一个接口。

java 复制代码
abstract class Animal {
    protected String name;
    
    public Animal(String name) {
        this.name = name;
    }
}

再次提醒:

抽象类存在的意义是为了让编译器更好的校验,像Animal这样的类我们并不会直接使用,而是使用它的子类。万一不小心创建了Animal的实例,编译器会及时提醒我们


3.小试牛刀

**1. 关于Java抽象类,以下哪个陈述是错误的?**​

**A.**​ 抽象类可以包含非抽象方法

**B.**​ 抽象类可以被实例化

**C.**​ 抽象类可以有构造方法

**D.**​ 一个类可以继承多个抽象类

正确答案:BD

📖 详细解析

  • B选项错误:抽象类的主要特征就是不能被实例化,必须通过子类来使用

  • A选项正确:抽象类可以包含具体实现的方法(非抽象方法)

  • C选项正确:抽象类可以有构造方法,用于子类通过super()调用

  • D选项错误:Java是单继承,一个类只能继承一个抽象类(但题目问的是"错误陈述",B更明显)

**2. 关于Java接口,以下哪个说法是正确的?**​

**A.**​ 接口中的所有方法都必须是抽象的

**B.**​ 接口可以包含静态方法

**C.**​ 接口可以有私有方法

**D.**​ 一个类只能实现一个接口

正确答案:B

📖 详细解析

  • B选项正确:从Java 8开始,接口可以包含静态方法(static methods)

  • A选项错误:可以定义static方法,并且Java 8+接口可以包含默认方法(default methods),不是必须抽象

  • C选项不准确:不行,接口中的方法都是public修饰

  • D选项错误:一个类可以实现多个接口(多实现)

**3. 以下关于内部类的描述,哪个是错误的?**​

**A.**​ 静态内部类可以访问外部类的静态成员

**B.**​ 非静态内部类可以直接访问外部类的所有成员

**C.**​ 局部内部类可以访问所在方法的final局部变量

**D.**​ 匿名内部类可以继承其他类

正确答案:C(按传统说法)

🔍 考点分析

  • 内部类访问权限:静态内部类 vs 非静态内部类

  • 局部内部类:访问局部变量的限制(final或effectively final)

  • 匿名内部类:可以继承类或实现接口

📖 详细解析

  • C选项(传统说法):局部内部类只能访问final局部变量(Java 8之前严格限制)

  • Java 8更新:局部内部类可以访问"effectively final"变量(事实上final)

  • A选项正确:静态内部类只能访问外部类的静态成员

  • B选项正确:非静态内部类可以访问外部类的所有成员(包括私有)

  • D选项正确:匿名内部类可以继承类或实现接口

**4. 关于抽象类和接口的区别,以下哪个说法是正确的?**​

**A.**​ 抽象类可以有构造方法,而接口不能

**B.**​ 一个类可以实现多个抽象类,但只能实现一个接口

**C.**​ 接口中的所有方法都必须是public的,而抽象类没有这个限制

**D.**​ 抽象类中的方法必须都是抽象的

正确答案:A

📖 详细解析

  • A选项正确:抽象类可以有构造方法(供子类调用),接口不能有构造方法

  • B选项错误:正好相反,一个类只能继承一个抽象类,但可以实现多个接口

  • C选项应该加上Jdk8以前,但公司里基本上都是用的jdk8

  • D选项错误:抽象类可以包含非抽象方法(具体实现)

小试牛刀:1、3、4

14.250319-接口--02:02:50->02:13:23

📌 核心考点总结

  1. 抽象类:不能被实例化,单继承,可以有构造方法和非抽象方法

  2. 接口:多实现,Java 8+支持静态方法和默认方法,Java 9+支持私有方法

  3. 内部类:访问权限规则,局部内部类对局部变量的访问限制

  4. 抽象类vs接口:构造方法、继承机制、方法修饰符等差异


4.知识掌握程度自测:


5. 总结(特别重要!!!):

(1)为何只需实现compareComparator接口的继承奥秘

小罗解答:

这是一个非常好的问题!图中的疑问涉及 接口默认方法​ 和 类继承 的知识点。

核心原因

Comparator<T>接口中,equals方法不需要 在实现类中强制重写,因为它是从所有类的父类 Object继承而来的。

详细解释

  1. 接口方法的特殊性
java 复制代码
public interface Comparator<T> {
    int compare(T o1, T o2);      // 1. 抽象方法 - 必须实现
    boolean equals(Object obj);   // 2. 从Object继承 - 已有默认实现
}
  1. 继承关系说明
java 复制代码
// 所有类都隐式继承自Object类
public class AgeComparator implements Comparator<Student> {
    // 实际上是这样的继承链:
    // AgeComparator → Object
    // 并且实现了 Comparator<Student> 接口
}
  1. 为什么不需要重写 equals
  • Object类已提供实现Object.equals(Object obj)默认比较对象地址

  • 接口不强制重写 :接口中的方法如果与 Object类中的方法签名相同,实现类不需要重写

  • 编译器识别 :编译器知道 equals方法已在 Object类中存在,所以不要求必须重写

  1. 验证示例
java 复制代码
public class TestInterface {
    public static void main(String[] args) {
        // 即使不重写equals,也能正常使用
        Comparator<String> comp = new Comparator<String>() {
            @Override
            public int compare(String o1, String o2) {
                return o1.length() - o2.length();  // 只实现compare
            }
            // 没有重写equals,但不会报错!
        };
        
        System.out.println(comp.equals(comp));  // 调用的是Object的equals
    }
}
  1. Java官方规范说明

根据Java语言规范:

如果一个接口声明了一个与 Object类中公共方法相同签名的方法,那么接口中的这个方法不会被视为抽象方法

这意味着:

  • compare是接口独有的 → 必须实现

  • equalsObject已有的 → 已有默认实现,可选重写

  1. 实际编程建议

虽然可以不重写,但在实际开发中,建议重写equals方法,以确保比较器对象的正确比较:

java 复制代码
public class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
    
    // 建议加上,确保比较器本身的正确比较
    @Override
    public boolean equals(Object obj) {
        if (this == obj) return true;
        if (obj == null || getClass() != obj.getClass()) return false;
        AgeComparator that = (AgeComparator) obj;
        // 根据实际需要比较字段
        return true;  // 如果所有实例逻辑相同
    }
}
  1. 总结

您的疑问答案很简单:因为 equals方法已经在 Object类中有默认实现,所以 Comparator接口的实现类不需要强制重写它。编译器足够智能,能够识别这种情况。

这是Java设计中的一个巧妙之处,既保证了接口的灵活性,又避免了不必要的重复实现。
15.20250320-接口完--00:11:20->00:24:50:下方!!!

(2)详解Comparable与Comparator的四种实际应用

  1. 学生类 Student实现 Comparable接口
java 复制代码
public class Student implements Comparable<Student> { 
    public String name; 
    public int age; 
    
    public Student(String name, int age) { 
        this.name = name; 
        this.age = age; 
    } 
    
    @Override
    public String toString() { 
        return "Student{" + 
                "name='" + name + '\'' + 
                ", age=" + age + 
                "}"; 
    } 
    
    @Override
    public int compareTo(Student o) { 
        return this.age - o.age; 
    }
}
  1. 比较器类 AgeComparator
java 复制代码
package demo1;

import java.util.Comparator;

public class AgeComparator implements Comparator<Student> {
    @Override
    public int compare(Student o1, Student o2) {
        return o1.age - o2.age;
    }
}
  1. 测试类 Test包含多种排序方法
java 复制代码
import java.util.Arrays;
import java.util.Comparator;

public class Test {
    // 方法1: 使用Comparable的冒泡排序
    public static void bubbleSort(Comparable[] comparables) {
        for (int i = 0; i < comparables.length - 1; i++) {
            for (int j = 0; j < comparables.length - 1 - i; j++) {
                if (comparables[j].compareTo(comparables[j + 1]) > 0) {
                    Comparable tmp = comparables[j];
                    comparables[j] = comparables[j + 1];
                    comparables[j + 1] = tmp;
                }
            }
        }
    }
    
    // 方法2: 使用Comparator的冒泡排序
    public static void bubbleSort(Student[] students, Comparator<Student> comparator) {
        for (int i = 0; i < students.length - 1; i++) {
            for (int j = 0; j < students.length - 1 - i; j++) {
                if (comparator.compare(students[j], students[j + 1]) > 0) {
                    Student tmp = students[j];
                    students[j] = students[j + 1];
                    students[j + 1] = tmp;
                }
            }
        }
    }
    
    public static void main(String[] args) {
        // 创建学生数组
        Student[] students = new Student[3];
        students[0] = new Student("zhangsan", 10);
        students[1] = new Student("lisi", 5);
        students[2] = new Student("abc", 15);
        
        // 创建比较器
        AgeComparator ageComparator = new AgeComparator();
        
        // 多种排序方式示例(按需使用)
        // 1. 使用内置排序 + Comparable
        // Arrays.sort(students);
        
        // 2. 使用内置排序 + Comparator
        // Arrays.sort(students, ageComparator);
        
        // 3. 使用自定义冒泡排序 + Comparable
        bubbleSort(students);
        
        // 4. 使用自定义冒泡排序 + Comparator
        // bubbleSort(students, ageComparator);
        
        // 输出结果
        System.out.println(Arrays.toString(students));
    }
}
  1. 程序输出结果
java 复制代码
[Student{name='lisi', age=5}, Student{name='zhangsan', age=10}, Student{name='abc', age=15}]
  1. 代码说明:

  2. Comparable接口Student类实现了此接口,通过 compareTo方法定义自然排序规则(按年龄排序)

  3. Comparator接口AgeComparator类实现了此接口,提供独立于 Student类的比较逻辑

  4. 两种排序方式

    • 基于 Comparable的排序:对象自身知道如何比较

    • 基于 Comparator的排序:通过外部比较器定义比较规则

  5. 两种实现方式

    • 使用 Arrays.sort()内置排序

    • 自定义 bubbleSort()冒泡排序实现

相关推荐
A懿轩A1 小时前
【Java 基础编程】Java 面向对象进阶:static/final、抽象类、接口、单例模式
java·开发语言·单例模式
lifallen1 小时前
后缀数组 (Suffix Array)
java·数据结构·算法
EmbedLinX2 小时前
C语言标准库stdlib.h
c语言·开发语言·笔记
逆境不可逃2 小时前
LeetCode 热题 100 之 76.最小覆盖子串
java·算法·leetcode·职场和发展·滑动窗口
I_LPL2 小时前
day35 代码随想录算法训练营 动态规划专题3
java·算法·动态规划·hot100·求职面试
百锦再2 小时前
Java中的日期时间API详解:从Date、Calendar到现代时间体系
java·开发语言·spring boot·struts·spring cloud·junit·kafka
A懿轩A3 小时前
【Java 基础编程】Java 枚举与注解从零到一:Enum 用法 + 常用注解 + 自定义注解实战
java·开发语言·python
mjhcsp3 小时前
C++ 树形 DP解析
开发语言·c++·动态规划·代理模式
tuokuac3 小时前
MyBatis-Plus调用getEntity()触发异常
java·mybatis