Java 比较器

1 使用场景

在 Java 中的对象,一般情况下无法比较对象的大小。但业务中经常会涉及到对象数组的排序问题,也就涉及到了对象之间的比较问题。

Java 中实现对象排序的方式有两种:

1、对象实现java.lang.Comparable接口,重写compareTo(Object obj)方法,通过方法返回值来比较大小;实现java.lang.Comparable接口的对象列表或数组可以通过Collections.sort(List<T> list)Arrays.sort(Object[] arr)方法进行自动排序;

2、使用java.util.Comparator对象,重写compare(Object o1, Object o2)方法,通过方法返回值比较大小;可以将Comparator对象传递给Collections.sort(List<T> list, Comparator c)Arrays.sort(Object[] arr, Comparator c)方法实现排序控制。

2 Java 比较器的两种实现方式

2.1 Comparable 接口

2.1.1 概述

实现Comparable接口的类必须重写compareTo(Object obj)方法,两个对象通过compareTo(Object obj)方法的返回值比较大小:

  • 例如:obj.compareTo(obj2)

如果 obj 大于 obj2,则返回正整数;

如果 obj 小于 obj2,则返回负整数;

如果 obj 等于 obj2,则返回0。

实现Comparable接口的类对象列表或数组可以直接通过Collections.sort(List<T> list)Arrays.sort(Object[] arr)方法进行自动排序,无需指定比较器。这种方式称为自然排序,也可以理解为内部比较器。

2.1.2 代码示例
public class Demo01 {
    public static void main(String[] args) {
        List<People> list = new ArrayList<>();
        People[] arr = new People[4];
        
        // 添加测试数据
        People p1 = new People("小黑", 22);
        list.add(p1);
        arr[0] = p1;
        People p2 = new People("小白", 19);
        list.add(p2);
        arr[1] = p2;
        People p3 = new People("小蓝", 20);
        list.add(p3);
        arr[2] = p3;
        People p4 = new People("小绿", 18);
        list.add(p4);
        arr[3] = p4;

        System.out.println("列表 - 自然排序,排序前:");
        list.forEach(System.out::println);

        // 自然排序
        Collections.sort(list);

        System.out.println("列表 - 自然排序,排序后:");
        list.forEach(System.out::println);

        System.out.println("---------------------------");

        System.out.println("数组 - 自然排序,排序前:");
        System.out.println(Arrays.toString(arr));

        // 自然排序
        Arrays.sort(arr);

        System.out.println("数组 - 自然排序,排序后:");
        System.out.println(Arrays.toString(arr));
    }
}

class People implements Comparable<People> {

    private String name;
    private Integer age;

    public People(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    @Override
    public int compareTo(People people) {
        if (this.age > people.age) {
            return 1;
        } else if (this.age < people.age) {
            return -1;
        } else {
            return 0;
        }
    }

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

运行结果

列表 - 自然排序,排序前:
People{name='小黑', age=22}
People{name='小白', age=19}
People{name='小蓝', age=20}
People{name='小绿', age=18}
列表 - 自然排序,排序后:
People{name='小绿', age=18}
People{name='小白', age=19}
People{name='小蓝', age=20}
People{name='小黑', age=22}
---------------------------
数组 - 自然排序,排序前:
[People{name='小黑', age=22}, People{name='小白', age=19}, People{name='小蓝', age=20}, People{name='小绿', age=18}]
数组 - 自然排序,排序后:
[People{name='小绿', age=18}, People{name='小白', age=19}, People{name='小蓝', age=20}, People{name='小黑', age=22}]

Process finished with exit code 0
2.1.3 附部分源码
2.1.3.1 Comparable 接口源码
public interface Comparable<T> {
    public int compareTo(T o);
}
2.1.3.2 Integer 实现 Comparable 接口的自然排序逻辑源码
public final class Integer extends Number implements Comparable<Integer> {
    public int compareTo(Integer anotherInteger) {
        return compare(this.value, anotherInteger.value);
    }
    
    public static int compare(int x, int y) {
        return (x < y) ? -1 : ((x == y) ? 0 : 1);
    }
}
2.1.3.3 String 实现 Comparable 接口的自然排序逻辑源码
public final class String implements java.io.Serializable, Comparable<String>, CharSequence {
    /**
     * 该方法的比较结果其实就是两个字符串的首个不同的字符之间的 ASCII 编码的差值,如果其中一个字符串完全匹配另一个字符串,则返回两个字符串的长度差
     */
    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) {
                // 当字符不同时,返回结果为两个字符的 ASCII 编码的差值
                // 例如:apple 和 application
                //      从头开始逐个字符匹配,匹配到第5位字符 e 和 i 不同;
                //      ASCII 编码中字符 e 的十进制值为 101,字符 i 的十进制值为 105;
                //      此时返回结果为 101 - 105 = -4
                return c1 - c2;
            }
            k++;
        }
        // 走到这里说明两个字符串中,一个字符串是另一个字符串从头开始完全匹配的子串,此时返回结果为两个字符串的长度差
        // 例如:apple 和 app,返回结果为 5 - 2 = 3
        return len1 - len2;
    }
}

2.2 Comparator 接口

2.2.1 概述

当对象类没有实现Comparable接口,或者实现了Comparable接口但自然排序规则不适用于当前操作时,可以使用Comparator接口,重写compare(Object o1, Object o2)方法进行比较:

  • 例如:compare(o1, o2)

如果返回正整数,则表示 o1 大于 o2;

如果返回负整数,则表示 o1 小于 o2;

如果返回0,则表示 o1 等于 o2。

Comparator对象传递给Collections.sort(List<T> list, Comparator c)Arrays.sort(Object[] arr, Comparator c)方法即可实现排序控制。这种方式称为定制排序,也可以理解为外部比较器。

2.2.2 代码示例
public class Demo02 {
    public static void main(String[] args) {
        List<PeopleNew> list = new ArrayList<>();
        PeopleNew[] arr = new PeopleNew[4];
        PeopleNew p1 = new PeopleNew("小黑", 22);
        list.add(p1);
        arr[0] = p1;
        PeopleNew p2 = new PeopleNew("小白", 19);
        list.add(p2);
        arr[1] = p2;
        PeopleNew p3 = new PeopleNew("小蓝", 20);
        list.add(p3);
        arr[2] = p3;
        PeopleNew p4 = new PeopleNew("小绿", 18);
        list.add(p4);
        arr[3] = p4;

        // 外部比较器定制排序
        Comparator<PeopleNew> comparator = new Comparator<PeopleNew>() {
            @Override
            public int compare(PeopleNew o1, PeopleNew o2) {
                if (o1.getAge() > o2.getAge()) {
                    return 1;
                } else if (o1.getAge() < o2.getAge()) {
                    return -1;
                } else {
                    return 0;
                }
            }
        };
        // lambda 写法
//        Comparator<PeopleNew> comparator = (o1, o2) -> {
//            if (o1.getAge() > o2.getAge()) {
//                return 1;
//            } else if (o1.getAge() < o2.getAge()) {
//                return -1;
//            } else {
//                return 0;
//            }
//        };

        System.out.println("列表 - 定制排序,排序前:");
        list.forEach(System.out::println);

        // 定制排序
        Collections.sort(list, comparator);
        // 或者直接使用 List 的 sort(java.util.Comparator<? super E> c) 方法
//        list.sort(comparator);
        // 使用 内部类 + lambda 表达式 一条语句搞定
//        list.sort((o1, o2) -> {
//            if (o1.getAge() > o2.getAge()) {
//                return 1;
//            } else if (o1.getAge() < o2.getAge()) {
//                return -1;
//            } else {
//                return 0;
//            }
//        });

        System.out.println("列表 - 定制排序,排序后:");
        list.forEach(System.out::println);

        list.sort(comparator.reversed());
        System.out.println("列表 - 定制排序,倒序排序:");
        list.forEach(System.out::println);

        System.out.println("---------------------------");

        System.out.println("数组 - 定制排序,排序前:");
        System.out.println(Arrays.toString(arr));

        // 定制排序
        Arrays.sort(arr, comparator);
        
        System.out.println("数组 - 定制排序,排序后:");
        System.out.println(Arrays.toString(arr));

        Arrays.sort(arr, comparator.reversed());
        System.out.println("数组 - 定制排序,倒序排序:");
        System.out.println(Arrays.toString(arr));
    }
}

class PeopleNew {

    private String name;
    private Integer age;

    public PeopleNew(String name, Integer age) {
        this.name = name;
        this.age = age;
    }

    public Integer getAge() {
        return age;
    }

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

运行结果

列表 - 定制排序,排序前:
PeopleNew{name='小黑', age=22}
PeopleNew{name='小白', age=19}
PeopleNew{name='小蓝', age=20}
PeopleNew{name='小绿', age=18}
列表 - 定制排序,排序后:
PeopleNew{name='小绿', age=18}
PeopleNew{name='小白', age=19}
PeopleNew{name='小蓝', age=20}
PeopleNew{name='小黑', age=22}
列表 - 定制排序,倒序排序:
PeopleNew{name='小黑', age=22}
PeopleNew{name='小蓝', age=20}
PeopleNew{name='小白', age=19}
PeopleNew{name='小绿', age=18}
---------------------------
数组 - 定制排序,排序前:
[PeopleNew{name='小黑', age=22}, PeopleNew{name='小白', age=19}, PeopleNew{name='小蓝', age=20}, PeopleNew{name='小绿', age=18}]
数组 - 定制排序,排序后:
[PeopleNew{name='小绿', age=18}, PeopleNew{name='小白', age=19}, PeopleNew{name='小蓝', age=20}, PeopleNew{name='小黑', age=22}]
数组 - 定制排序,倒序排序:
[PeopleNew{name='小黑', age=22}, PeopleNew{name='小蓝', age=20}, PeopleNew{name='小白', age=19}, PeopleNew{name='小绿', age=18}]

Process finished with exit code 0
2.2.3 附部分源码

Comparator 接口定制排序的方法源码

@FunctionalInterface
public interface Comparator<T> {
    int compare(T o1, T o2);
    
    /**
     * 倒序排序
     */
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }
}

注:Comparator 接口的方法有很多,此处只讨论排序相关的方法,故不赘述其他方法源码。如需追溯倒序排序底层实现,源码见下方Collections 接口部分源码

Collections 接口部分源码

public class Collections {
    public static <T> Comparator<T> reverseOrder() {
        return (Comparator<T>) ReverseComparator.REVERSE_ORDER;
    }
    
    public static <T> Comparator<T> reverseOrder(Comparator<T> cmp) {
        if (cmp == null)
            return reverseOrder();

        if (cmp instanceof ReverseComparator2)
            return ((ReverseComparator2<T>)cmp).cmp;

        return new ReverseComparator2<>(cmp);
    }
    
    private static class ReverseComparator implements Comparator<Comparable<Object>>, Serializable {

        private static final long serialVersionUID = 7207038068494060240L;

        static final ReverseComparator REVERSE_ORDER = new ReverseComparator();

        public int compare(Comparable<Object> c1, Comparable<Object> c2) {
            return c2.compareTo(c1);
        }

        private Object readResolve() { return Collections.reverseOrder(); }

        @Override
        public Comparator<Comparable<Object>> reversed() {
            return Comparator.naturalOrder();
        }
    }
    
    private static class ReverseComparator2<T> implements Comparator<T>, Serializable {
        private static final long serialVersionUID = 4374092139857L;

        /**
         * The comparator specified in the static factory.  This will never
         * be null, as the static factory returns a ReverseComparator
         * instance if its argument is null.
         *
         * @serial
         */
        final Comparator<T> cmp;

        ReverseComparator2(Comparator<T> cmp) {
            assert cmp != null;
            this.cmp = cmp;
        }

        public int compare(T t1, T t2) {
            // 倒序
            return cmp.compare(t2, t1);
        }

        public boolean equals(Object o) {
            return (o == this) ||
                (o instanceof ReverseComparator2 &&
                 cmp.equals(((ReverseComparator2)o).cmp));
        }

        public int hashCode() {
            return cmp.hashCode() ^ Integer.MIN_VALUE;
        }

        @Override
        public Comparator<T> reversed() {
            return cmp;
        }
    }
}

3 Java 比较器两种实现方式的区别

1、接口所在包不同:Comparable接口位于java.lang包下;而Comparator接口位于java.util包下;

2、定义比较逻辑位置不同:Comparable接口是类继承接口后在类的内部定义比较逻辑;而Comparator接口是在类的外部定义比较逻辑;

3、接口方法及参数不同:Comparable接口只有一个compareTo(Object obj)方法,使用时拿当前对象与形参对象进行比较;而Comparator接口包含多个方法,其中提供compare(Object o1, Object o2)比较方法,传入两个形参对象进行比较。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

_秋牧

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

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

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

打赏作者

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

抵扣说明:

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

余额充值