Effective Java学习笔记--重写compareTo方法

目录

Comparable接口简介

如何重写compareTo方法

需要遵循的重写原则

compareTo方法的实现方式

通过Comparator比较器进行比较

比较器构建与应用步骤

1. 构建比较器类


Comparable接口简介

与前面Object的方法不同,compareTo方法并不是Object类的内置方法,而是Comparable接口唯一的可重写方法,该方法赋予特定类等同比较与顺序比较的功能。Comparable接口是一个泛型接口。

当一个类实现了 Comparable 接口,它就拥有了自然顺序,它就告诉 Java 运行时环境(JRE)如何比较该类的实例。这对于集合框架特别重要,因为它允许 JRE 根据元素的自然顺序自动对集合进行排序或维护元素的唯一性。如果未实现Comparable接口,类就默认没有自然顺序,就需要单独实现comparator接口来进行排序。

如何重写compareTo方法

compareTo的比较方法如下:

  • 如果调用对象小于参数对象,则返回负整数。
  • 如果调用对象等于参数对象,则返回零。
  • 如果调用对象大于参数对象,则返回正整数。

需要遵循的重写原则

  • 对称性:sgn(x.compareTo(y)) = -sgn(y.compareTo(x))必须成立;
  • 传递性:x.compareTo(y)>0&&y.compareTo(z)>0,那么x.compareTo(z)>0必须成立;
  • 同一性:如果x.compareTo(z)=0, 那么sgn(y.compareTo(x))=sgn(y.compareTo(z))必须成立。

除了以上三个原则,作者强烈建议将compareTo的实现与equals保持一致,即(x.compareTo(y)==0)==(x.equals(y)),要实现这个原则的关键就是要保持compareTo和equals方法中对比的值组件保持一致。

public class Book implements Comparable<Book> {
    private String title;
    private String author;

    public Book(String title, String author) {
        this.title = title;
        this.author = author;
    }

    @Override
    public int compareTo(Book other) {
        int titleComparison = this.title.compareTo(other.title);
        if (titleComparison != 0) {
            return titleComparison;
        } else {
            return this.author.compareTo(other.author);
        }
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null || getClass() != obj.getClass()) {
            return false;
        }
        Book other = (Book) obj;
        return title.equals(other.title) && author.equals(other.author);
    }

比如上述这个案例,equals方法和compareTo方法都实现了对于title和author两个值组件的比较。但是作者后续又补充这个原则并不是必须的,换句话说compareTo在进行等同性比较的时候可以与equals不一致,这主要是因为compareTo和equals从作用上来说并不一样,compareTo返回的整数值是用于做排序的,而equals是用来做相等性判断的,换句话说,用作排序的值组件并不一定要和判断相等的一致,所以有时候从compareTo方法的性能优化和特定的设计场景(比如软件包的排序通过版本号判断,但是相等性通过标识符判断)上考虑,会有意让两者不一致。

但是要注意的是,两者的不一致会导致一系列的问题。尤其是在与一些顺序集合的交互过程中,比如在TreeSet中应用相关元素会导致基于compareTo的单一检测功能和单独元素的equals判断发生冲突。又比如在多线程环境中,不一致的比较可能导致竞态条件或死锁。所以,程序员应尽可能保证两个方法的一致性。

compareTo方法的实现方式

编写compareTo方法与编写equals方法非常相似,最底层都可以基于包装类静态的compare方法,但存在几个不同:

  • comparable接口是泛型接口,因此compareTo方法只接受同类型的比较,而equals方法以Object类的实例为输入,具体操作时需要进行类型转换。
  • 与equals方法不一样,comparable接口并不是所有类都实现了的,因此会存在对于某对象引用值组件进行比较的时候,对应类没有实现comparable接口的情况,这是就需要单独构建一个静态comparator构造器来实现比较。

通过Comparator比较器进行比较

可以通过comparator接口配置类的外接比较规则。它允许你在运行时动态地定义对象的排序规则,而不必修改类的实现。

比较器构建与应用步骤

1. 构建比较器类

首先需要创建一个实现了Comparator接口的类,由于Comparator接口也是泛型的,所以还需要指定接口类型。

import java.util.Comparator;

public class PersonComparator implements Comparator<Person> {
    
}

如果比较器后续不需要重复使用,可以在执行类的内部通过匿名内部类、Lambda表达式和方法引用等方式直接创建Comparator实例:

//匿名内部类
Comparator<Person> ageComparator = new Comparator<Person>() {
    @Override
    public int compare(Person p1, Person p2) {
        return Integer.compare(p1.getAge(), p2.getAge());
    }
};

//Lambda表达式
Comparator<Person> ageComparator = (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge());


//方法引用
Comparator<Person> ageComparator = Comparator.comparingInt((Person pn)->pn.getAge());

作者重点介绍了静态方法引用的方式,comparingInt接受一个函数式接口(ToIntFunction)并返回一个 int 类型的值。后续比较可以继续调用thenComparingInt等Comparator上的实例方法。

comparator还实现了基于对象引用的静态方法comparing,它有两个重载,一个仅输入函数式接口,另一个还可以额外输入对象引用的其他比较器:

comparing(Function<T, U> keyExtractor)
comparing(Function<T, U> keyExtractor, Comparator<U> keyComparator)

这两个方法都要求函数式接口返回一个实现了comparable接口的类型(U extends Comparable<? super U>),所以可以用于比较类的实例。

Comparator<Person> complexComparator = Comparator.comparingInt(Person::getAge)
                                                 .thenComparing(Person::toString);

这里就实现了先比较基础数据类型int,再去比较类实例String。如果对于String的比较有独立的构造器就可以使用第二种重载:

static Comparator<String> OtherOrder = new Comparator<String>(){

    public int compare(String o1, String o2){
        ....
}
    
}

Comparator<Person> complexComparator = Comparator.comparingInt(Person::getAge)
                                                 .thenComparing(Person::toString,OtherOrder);

  • 5
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很好的问题!让我来解答。 首先,让我们来了解一下 `Comparable` 接口。这个接口是 Java 中的一个接口,它只有一个方法 `compareTo()`。这个方法的作用是定义类的自然排序顺序。 如果我们要对一个对象集合进行排序,只需要让这个类实现 `Comparable` 接口,并且重写 `compareTo()` 方法。这个方法返回一个整型值,表示该对象与另一个对象的大小关系。如果该对象小于另一个对象,则返回负整数;如果两个对象相等,则返回零;如果该对象大于另一个对象,则返回正整数。 下面是一个简单的例子,展示如何使用 `Comparable` 接口进行排序。 ```java public class Student implements Comparable<Student> { private String name; private int age; public Student(String name, int age) { this.name = name; this.age = age; } public String getName() { return name; } public int getAge() { return age; } // 重写 compareTo 方法 @Override public int compareTo(Student o) { // 按照年龄从小到大排序 return this.age - o.getAge(); } } ``` 在这个例子中,我们定义了一个 `Student` 类,并且让它实现了 `Comparable` 接口。我们重写了 `compareTo()` 方法,按照年龄从小到大排序。 现在,我们可以使用 `Collections.sort()` 方法对 `Student` 对象进行排序了: ```java List<Student> students = new ArrayList<>(); students.add(new Student("Tom", 20)); students.add(new Student("Jerry", 18)); students.add(new Student("Alice", 22)); Collections.sort(students); for (Student student : students) { System.out.println(student.getName() + " " + student.getAge()); } ``` 输出结果为: ``` Jerry 18 Tom 20 Alice 22 ``` 可以看到,我们成功地按照年龄从小到大对 `Student` 对象进行了排序。 希望这个例子能够帮助你理解如何使用 `Comparable` 接口进行排序。如果你还有其他问题,请随时提出!
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值