一、简介

本文主要讲解Comparable接口以及Comparator接口的比较原理以及在实际场景的一些使用方法,以及两者的区别。

二、Comparable接口

Comparable是一个排序接口,如果一个类实现了Comparable接口,并且实现了compareTo()方法,那么这个类就支持排序。Comparable接口是一个内比较器,这些类可以与自身进行比较。实现了Comparable接口的类如果加入到List等容器中后,会进行自动排序。这主要是由Comparable接口中compareTo(T o)方法决定的,定义如下:

public int compareTo(T o);

compareTo()方法主要有三种返回值:

【a】比较象大于被比较者,返回正整数;

【b】比较者等于被比较者,返回0;

【c】比较者小于被比较者,返回负整数;

其实JDK提供了一些类也实现了Comparable接口,如String、Date、Integer、Character等。下面我们对一些常见的内置引用类型实现了Comparable接口的类的比较原理做一些讲解:

【1】String类:根据字符Unicode进行比较,依次比较各个字符,返回第一个不相同的字符的Unicode码之差。如果字符都相同则返回字符串长度之差。

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) {
        //根据字符Unicode进行比较
                char c1 = v1[k];
                char c2 = v2[k];
                if (c1 != c2) {
        //返回第一个不相等的Unicode码大小之差
                    return c1 - c2;
                }
                k++;
            }
        //返回两个字符串的长度之差
            return len1 - len2;
        }

【2】Integer类:根据基本数据类型数值大小进行比较

public static int compare(int x, int y) {
            return (x < y) ? -1 : ((x == y) ? 0 : 1);
        }

【3】Character类:根据Unicode编码大小进行比较

public static int compare(char x, char y) {
            return x - y;
        }

【4】Date类:获取相应的毫秒数,再进行大小比较

public int compareTo(Date anotherDate) {
        //获取相应的毫秒数,再进行大小比较
            long thisTime = getMillisOf(this);
            long anotherTime = getMillisOf(anotherDate);
            return (thisTime<anotherTime ? -1 : (thisTime==anotherTime ? 0 : 1));
        }

下面对内置引用类型做一个简单的测试:

/**
 * @Description:c 内置引用数据类型比较原理分析
 * @Author: weishihuai
 * @Date: 2018/10/18 22:17
 */
public class Test {

    public static void main(String[] args) {
        //根据基本数据类型数值大小
        Integer a = 120;
        Integer b = 100;
        System.out.println(a.compareTo(b) == -1 ? "a < b" : (a.compareTo(b) == 0) ? "a = b" : "a > b");

        //根据Unicode编码大小
        Character c1 = 'a';
        Character c2 = 'c';
        System.out.println(c1.compareTo(c2) < 0 ? "c1 < c2" : (c1.compareTo(c2) == 0) ? "c1 = c2" : "c1 > c2");

        //根据字符Unicode进行比较,依次比较各个字符,返回第一个不相同的字符的Unicode码之差。如果字符都相同则返回字符串长度之差。
        String s1 = "ad";
        String s2 = "adbf";
        System.out.println(s1.compareTo(s2) < 0 ? "s1 < s2" : (s1.compareTo(s2) == 0 ? "s1 = s2" : "s1 > s2"));

        //获取相应的毫秒数,再进行大小比较
        Date d1 = new Date();
        Date d2 = new Date(System.currentTimeMillis() + 60 * 1000 * 1000);
        System.out.println(d1.compareTo(d2) == -1 ? "d1 < d2" : (d1.compareTo(d2) == 0 ? "d1 = d2" : "d1 > d2"));
    }

}

测试结果:

compara排序 java java排序comparator原理_Comparator

以上是JDK提供的一些已经实现了Comparable接口的引用类型类的讲解,那如果我们想使用自定义的类进行比较,同理,只需要让该类实现Comparable接口即可,重写compareTo()方法。下面我们通过一个示例说明:按发布日期降序、浏览量升序、标题降序排序新闻。

【a】首先,创建一个News新闻类,该类实现Comparable接口,重写compareTo方法:泛型指定News类,当然也可以指定其他类。

/**
 * @Description: 新闻类
 * @Author: weishihuai
 * @Date: 2018/10/17 21:53
 * <p>
 * Comparable的使用方法: 实现compareTo方法
 */
public class News implements Comparable<News> {
    /**
     * 浏览量
     */
    private int hits;
    /**
     * 发布时间
     */
    private Date publishDate;
    /**
     * 标题
     */
    private String title;

    public News() {
    }

    public News(int hits, Date publishDate, String title) {
        this.hits = hits;
        this.publishDate = publishDate;
        this.title = title;
    }

    @Override
    public int compareTo(News o) {
        int result;
        //发布日期降序
        result = -this.publishDate.compareTo(o.publishDate);
        if (result == 0) {
            //浏览量升序
            result = this.hits - o.hits;
            if (result == 0) {
                //标题降序
                result = -this.title.compareTo(o.title);
            }
        }
        return result;
    }

    public int getHits() {
        return hits;
    }

    public void setHits(int hits) {
        this.hits = hits;
    }

    public Date getPublishDate() {
        return publishDate;
    }

    public void setPublishDate(Date publishDate) {
        this.publishDate = publishDate;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    @Override
    public String toString() {
        return "News{" +
                "hits=" + hits +
                ", publishDate=" + publishDate +
                ", title='" + title + '\'' +
                '}' + "\n";
    }
}

【b】测试News是否能够按指定的规则排序:

public class TestNewsComparable {

    public static void main(String[] args) {
        List<News> newsList = new ArrayList<>();
        newsList.add(new News(100, new Date(), "hello1"));
        newsList.add(new News(70, new Date(System.currentTimeMillis() - 1000 * 60 * 60), "hello2"));
        newsList.add(new News(110, new Date(System.currentTimeMillis() - 1000 * 60 * 60), "hello4"));
        newsList.add(new News(110, new Date(System.currentTimeMillis() - 1000 * 60 * 60), "hello5"));
        newsList.add(new News(80, new Date(System.currentTimeMillis() + 1000 * 60 * 60), "hello3"));
        System.out.println("排序前: " + Arrays.toString(newsList.toArray()));
        Collections.sort(newsList);
        System.out.println("排序后: " + Arrays.toString(newsList.toArray()));
    }

}

测试结果:

compara排序 java java排序comparator原理_Comparator_02

由上图可以看到,News按照我们指定的规则进行了排序,首先按发布日期降序、发布日期相同则按浏览量升序、浏览量相同则按标题降序。

三、Comparator接口

Comparator接口是一个外比较器,如果一个自定义的类想进行比较,而且又没有实现Compable接口,那么我们可以自定义Comparator比较器让这个类也可以进行比较。Comparator接口适用于不想跟自身进行比较的场景,主要实现方法为compare()方法、

int compare(T o1, T o2);

方法有两个参数,分别表示两个不同的对象。返回值同样有三种情况:

【1】o1大于o2,返回正整数

【2】o1等于o2,返回0

【3】o1小于o3,返回负整数

我们都知道JDK提供的String比较是根据字符的Unicode码进行比较,那么我们可以定义一个Comparator比较器使String比较长度来进行排序。

【a】自定义StringComparator,实现compare()方法

/**
 * @Description: 内置类String比较器
 * @Author: weishihuai
 * @Date: 2018/10/18 22:46
 */
public class StringComparator implements Comparator<String> {
    @Override
    public int compare(String string1, String string2) {
        //根据长度比较
        return string1.length() - string2.length();
    }
}

【b】测试:

public class TestStringComparator {

    public static void main(String[] args) {
        List<String> stringList = new ArrayList<>();
        stringList.add("zhangsan");
        stringList.add("lisi");
        stringList.add("wangwu");
        Collections.sort(stringList, new StringComparator());
        System.out.println(Arrays.toString(stringList.toArray()));
    }

}

【c】测试结果

compara排序 java java排序comparator原理_比较器_03

可以看到,字符串已经使用长度比较进行了排序。

下面我们同样使用一个示例来说明Comparator接口的使用:

【a】首先定义Goods类

/**
 * @Description: 商品类
 * @Author: weishihuai
 * @Date: 2018/10/17 22:09
 */
public class Goods {
    /**
     * 商品名称
     */
    private String name;
    /**
     * 浏览量
     */
    private int hits;
    /**
     * 商品价格
     */
    private double price;

    public Goods() {
    }

    public Goods(String name, int hits, double price) {
        this.name = name;
        this.hits = hits;
        this.price = price;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getHits() {
        return hits;
    }

    public void setHits(int hits) {
        this.hits = hits;
    }

    public double getPrice() {
        return price;
    }

    public void setPrice(double price) {
        this.price = price;
    }

    @Override
    public String toString() {
        return "Goods{" +
                "name='" + name + '\'' +
                ", hits=" + hits +
                ", price=" + price +
                '}' + "\n";
    }

}

【b】根据具体的业务定义相应的业务比较器

这里我们定义两个业务相关的比较器:

(1) 按商品点击量比较Comparator (升序)

/**
 * @Description: 商品点击量比较Comparator (升序)
 * @Author: weishihuai
 * @Date: 2018/10/17 22:12
 */
public class GoodsFitsComparator implements Comparator<Goods> {

    @Override
    public int compare(Goods o1, Goods o2) {
        return o1.getHits() - o2.getHits() > 0 ? 1 : (o1.getHits() - o2.getHits() == 0 ? 0 : -1);
    }
}

(2) 按商品价格比较Comparator (降序)

/**
 * @Description: 商品价格比较Comparator (降序)
 * @Author: weishihuai
 * @Date: 2018/10/17 22:12
 */
public class GoodsPriceComparator implements Comparator<Goods> {

    @Override
    public int compare(Goods o1, Goods o2) {
        return -(o1.getPrice() - o2.getPrice() > 0 ? 1 : (o1.getPrice() - o2.getPrice()) == 0 ? 0 : -1);
    }
}

【c】测试

/**
 * @Description: 测试Comparator接口的使用方法
 * @Author: weishihuai
 * @Date: 2018/10/17 22:02
 */
public class TestGoodsFitsComparator {

    public static void main(String[] args) {
        List<Goods> goodsList = new ArrayList<>();
        goodsList.add(new Goods("商品1", 100, 2000));
        goodsList.add(new Goods("商品2", 110, 1000));
        goodsList.add(new Goods("商品3", 10, 3000));

        //按价格降序排序
        System.out.println("排序前: " + Arrays.toString(goodsList.toArray()));
        Collections.sort(goodsList, new GoodsFitsComparator());
        System.out.println("排序后: " + Arrays.toString(goodsList.toArray()));
    }

}

测试结果:

compara排序 java java排序comparator原理_compara排序 java_04

可见,已经按商品价格降序排序。

/**
 * @Description: 测试Comparator接口的使用方法
 * @Author: weishihuai
 * @Date: 2018/10/17 22:02
 */
public class TestGoodsFitsComparator {

    public static void main(String[] args) {
        List<Goods> goodsList = new ArrayList<>();
        goodsList.add(new Goods("商品1", 100, 2000));
        goodsList.add(new Goods("商品2", 110, 1000));
        goodsList.add(new Goods("商品3", 10, 3000));

        //按价格降序排序
        System.out.println("排序前: " + Arrays.toString(goodsList.toArray()));
        Collections.sort(goodsList, new GoodsFitsComparator());
        System.out.println("排序后: " + Arrays.toString(goodsList.toArray()));
    }

}

测试结果:

compara排序 java java排序comparator原理_Comparable_05

可以看见,商品已经实现了按点击量升序排序。

四、Comparable与Comparator的比较

1. 实现方法不同

Comparable: compareTo()方法

Comparator:compare()方法

2. Comparator耦合度相对较低,Comparable耦合度相对较高,假如需要增加新的比较规则,对于Comparable接口需要修改实体类的比较规则,而Comparator接口则不需要修改实体类的任何代码,只需增加相应的业务比较器接口即可。

3. Comparable可以跟自身进行比较,Comparator则不适用于跟自身进行比较的场景。

在实际项目中,根据具体的场景选择合适的比较接口即可,并没有说一定要使用哪一个接口好一点。

五、总结

本文是笔者对Comparable接口以及Comparator接口的一些见解以及实践方法,希望对大家有所帮助,仅供大家学习参考,一起学习,一起进步!