Java 对象排序(Object Ordering)

目录

1、Comparable自定义可比较的类型

2、Comparator 自定义比较规则


我们知道,集合列表 list 可以按如下顺序进行排序

Collections.sort(list);

如果 List 包含的是 String 类型元素,那么它将按字母的顺序进行排序。如果包含的是 Date 类型的元素,则按时间顺序排序。这些又是怎么发生的呢? 原因就是 String 和 Date 类都实现了Comparable接口,并实现了 compareTo() 方法,String 类的实现如下所示:

// string 对 compareTo 的实现
public int compareTo(String anotherString) {
        int len1 = value.length; // 先获取长度
        int len2 = anotherString.value.length;
        int lim = Math.min(len1, len2); // 获取最小长度的字符串
        char v1[] = value; // 获取值
        char v2[] = anotherString.value;

        int k = 0;
        while (k < lim) { 
            char c1 = v1[k];
            char c2 = v2[k];
            if (c1 != c2) {  // 一个一个字符进行比较
                return c1 - c2; // 返回不等处的比较值
            }
            k++;
        }
        return len1 - len2; // 如果最小部分都相等,计算长度差值
    }

实现 Comparable 接口,为实现类提供了一种自然的排序方式,它允许对该类的对象进行自动排序。下表总结了 Java 中实现了 Comparable 接口的一些比较重要的类:

Classes Implementing Comparable
ClassNatural Ordering
ByteSigned numerical
CharacterUnsigned numerical
LongSigned numerical
IntegerSigned numerical
ShortSigned numerical
DoubleSigned numerical
FloatSigned numerical
BigIntegerSigned numerical
BigDecimalSigned numerical
BooleanBoolean.FALSE < Boolean.TRUE
FileSystem-dependent lexicographic on path name
StringLexicographic
DateChronological
CollationKeyLocale-specific lexicographic

如果列表中的元素没有实现 Comparable 接口,将不能使用 Collections.sort(list) 进行比较,否则会抛出 ClassCastException 异常。同样,如果使用比较器 Comparator 试图对一个不能相互比较的元素列表进行排序,Collections.sort(list, comparator)也会抛出ClassCastException 异常。

1、Comparable自定义可比较的类型

Comparable接口由以下方法组成

public interface Comparable<T> {
    public int compareTo(T o);
}

compareTo 方法将接收对象与指定对象进行比较,并根据接收对象是小于、等于还是大于指定对象返回负整数、0或正整数

示例代码,如 Name.class 实现了Comparable 接口,定义了自己类的 compareTo() 逻辑

import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Objects;

public class Name implements Comparable<Name> {

    /**
     * 名称对象是不可变的。
     * 在一些情况下,需要类型是不可变的,比如作为set集合中的元素,或者在map中用作键的对象。
     */
    private final String firstName, lastName;

    public Name(String firstName, String lastName) {
        // 在构造函数中检查参数是否为空。确保使用Name对象的方法不会抛出NullPointerException。
        if (firstName == null || lastName == null) {
            throw new NullPointerException();
        }
        this.firstName = firstName;
        this.lastName  = lastName;
    }

    public String firstName() {
        return firstName;
    }

    public String lastName() {
        return lastName;
    }

    public boolean equals(Object o) {
        // 如果指定的对象为空或类型不合适,equals方法返回false。
        if (Objects.isNull(o) || !(o instanceof Name)) {
            return false;
        }
        Name n = (Name) o;
        return n.firstName.equals(firstName) && n.lastName.equals(lastName);
    }

    /**
     * hashCode方法被重新定义
     */
    public int hashCode() {
        return 31 * firstName.hashCode() + lastName.hashCode();
    }

    /**
     * toString方法重新定义,以可读的形式打印Name。
     */
    public String toString() {
        return firstName + " " + lastName;
    }

    /**
     * 实现了Comparable排序接口,姓优先于名进行比较。
     */
    public int compareTo(Name n) {
        int lastCmp = lastName.compareTo(n.lastName);
        return (lastCmp != 0 ? lastCmp : firstName.compareTo(n.firstName));
    }
    
    public static void main(String[] args) {
        Name nameArray[] = {
            new Name("John", "Smith"),
            new Name("Karl", "Ng"),
            new Name("Jeff", "Smith"),
            new Name("Tom", "Rich")
        };
        List<Name> names = Arrays.asList(nameArray);
        Collections.sort(names);
        System.out.println(names);
    }
}

在示例代码中,使用 Collections.sort() 对集合进行排序,执行 main 函数的结果如下:

[Karl Ng, Tom Rich, Jeff Smith, John Smith] // 排序结果,先比较 lastName

Comparable 使用的注意事项:

  1. compareTo方法内必须做非空判断(规范问题),基本类型除外。
  2. 子类需要实现Comparable<T>接口
  3. Collection.sort(list) 不会自动调用 compareTo,如果不显示调用,集合不会自动进行排序
  4. 如果是数组则用专用的 Arrays.sort(a) 方法

2、Comparator 自定义比较规则

如果希望以不同于其自然顺序的顺序对某些对象进行排序,又有什么办法呢?或者,想对一些没有实现 Comparable 接口的对象进行排序呢?

要实现上边这两点,需要借助 Comparator (比较器:一个封装排序的对象)。与Comparable接口类似,Comparator接口也由单个方法组成。

public interface Comparator<T> {
    int compare(T o1, T o2);
}

compare方法比较它的两个参数,根据对比第一个参数小于、等于或大于第二个参数,返回一个负整数、0或正整数。如果其中一个参数的类型不适合进行比较,compare 就会抛出ClassCastException 异常。

编写 compare() 与编写 compareTo() 类似,不同的是 compare() 获取两个传入的参数对象。而且,Comparator 也必须对所比较的对象给出一个总的顺序。// 相等情况下的顺序再比较

测试案例

Employee 对象的定义如下,如果要求按照雇佣日期对 Employee 对象进行排序,应该如何做?

import java.time.LocalDate;
import lombok.Data;

@Data
public class Employee {

    private Name      name;
    private int       EmployeeNo;
    private LocalDate hireDate;

    public Employee(Name name, int employeeNo, LocalDate hireDate) {
        this.name     = name;
        EmployeeNo    = employeeNo;
        this.hireDate = hireDate;
    }
}

因为 Employee 对象并没有实现Comparable接口,所以需要定义一个比较器,在 EmpSort 类中命名为 SENIORITY_ORDER。然后实现 compare() 方法,首先根据雇佣日期 HireDate 进行排序,如果雇佣日期相同,那么就必须要再进行相等情况下的顺序比较。因为,如果把集合中的数据放入Set类等不允许重复元素的集合中时,雇佣日期相等的 Employee 将会被视为重复对象,从而造成数据丢失,这明显不符合现实中的应用场景。

因此,如果 Employee 对象的雇佣日期相同,还需要再比较 employeeNo,一般来说 employeeNo 是唯一的,能够始终定义出两个Employee 对象的顺序。

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class EmpSort {

    static final Comparator<Employee> SENIORITY_ORDER =
        new Comparator<Employee>() {
            public int compare(Employee e1, Employee e2) {
                int dateCmp = e2.getHireDate().compareTo(e1.getHireDate());
                if (dateCmp != 0) {
                    return dateCmp; // 调整正负,实现正序或倒序排序
                }
                // 必须处理相等的情况以应对 treeSet 等非重复读元素的集合
                // return e1.number() - e2.number();
                // 注意:如果i是一个大的正整数而j是一个大的负整数,i - j将溢出并返回一个负整数
                return (e1.getEmployeeNo() < e2.getEmployeeNo() ? -1 :
                    (e1.getEmployeeNo() == e2.getEmployeeNo() ? 0 : 1));
            }
        };

    // 测试:Employee database 
    // static final Collection<Employee> employees = null;
    public static List<Employee> employees() {
        List<Employee> employees = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Employee employee = new Employee(new Name("ZHOU" + i, "XIAO" + i), 1001 + i,
                LocalDate.parse("2021-01-01", DateTimeFormatter.ofPattern("yyyy-MM-dd")).plusDays(i));
            employees.add(employee);
        }
        return employees;
    }

    public static void main(String[] args) {
        List<Employee> employees = employees();
        // 排序 -> 传入排序集合和比较器
        Collections.sort(employees, SENIORITY_ORDER);
        System.out.println(employees);
    }
}

参考文章:https://docs.oracle.com/javase/tutorial/collections/interfaces/order.html

Comparator (Java Platform SE 8 )

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值