专栏首页孟君的编程札记使用Comparable和Comparator对Java集合对象进行排序

使用Comparable和Comparator对Java集合对象进行排序

在现实生活中,我们可能会遇到需要对集合内的对象进行排序的场景,比如,有一个游戏得分排行榜,如先按照分数的高低由高到低排序,在分数相同的情况下按照记录创建的时间由早到新的顺序排序

在Java语言中,要实现集合内对象的排序,咱们可以采用如下两种方式来完成:

  1. 使用Comparable来实现
  2. 使用Comparator来实现

接下来,我们先使用Comparable和Comparator、结合示例来完成集合内对象排序的功能,然后,对这两种方式进行比较;最后,结合多属性排序的话,给出相对较好的实践方法。

一、使用Comparable实现

编写游戏记录类GameRecord,然后实现Comparable接口#compareTo方法。

 public int compareTo(T o);

具体代码如下:

import java.util.Date;

import org.apache.http.client.utils.DateUtils;

/**
 * @author wangmengjun
 *
 */
public class GameRecord implements Comparable<GameRecord> {

    /**玩家名称*/
    private String name;

    /**记录创建时间*/
    private Date createTime;

    /**得分*/
    private int score;

    /**
     * @param name
     * @param createTime
     * @param score
     */
    public GameRecord(String name, Date createTime, int score) {
        this.name = name;
        this.createTime = createTime;
        this.score = score;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the createTime
     */
    public Date getCreateTime() {
        return createTime;
    }

    /**
     * @param createTime the createTime to set
     */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    /**
     * @return the score
     */
    public int getScore() {
        return score;
    }

    /**
     * @param score the score to set
     */
    public void setScore(int score) {
        this.score = score;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "GameRecord [name=" + name + ", createTime="
                + DateUtils.formatDate(createTime, "yyyy-MM-dd HH:mm:ss") + ", score=" + score
                + "]";
    }

    /* (non-Javadoc)
     * @see java.lang.Comparable#compareTo(java.lang.Object)
     */
    @Override
    public int compareTo(GameRecord other) {
        int scoreComapre = Integer.compare(other.getScore(), score);
        /**
         * 先按照分数从高到低排列,
         * 如果分数一样,按照记录创建的时间排序
         */
        if (scoreComapre == 0) {
            return createTime.compareTo(other.getCreateTime());
        }
        return scoreComapre;
    }

}

当GameRecord类实现Comparable接口之后,该类对象就具有比较的功能了,然后我们要做的就是对GameRecord对象的集合类进行排序即可,集合的排序可以采用java.util.Collections类的sort方法完成。

代码示例如下:

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.http.client.utils.DateUtils;

/**
 * @author wangmengjun
 *
 */
public class Main {

    /**
     * 可用于转换的日期格式集合
     */
    private static final Collection<String> AVAILABLE_DATE_FORMATTERS = Arrays
            .asList("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws DateParseException {

        Date d1 = DateUtil.parseDate("2015-12-31 00:22:30", AVAILABLE_DATE_FORMATTERS);
        Date d2 = DateUtil.parseDate("2016-05-11 12:56:01", AVAILABLE_DATE_FORMATTERS);
        Date d3 = DateUtil.parseDate("2016-05-11 12:56:01", AVAILABLE_DATE_FORMATTERS);

        GameRecord r1 = new GameRecord("Wang", d1, 300);
        GameRecord r2 = new GameRecord("Meng", d2, 100);
        GameRecord r3 = new GameRecord("Jun", d3, 300);

        List<GameRecord> records = Arrays.asList(r3, r2, r1);
        System.out.println("排序前的结果==>");
        //System.out.println(records);
        printGameRecordInfo(records);

        Collections.sort(records);
        System.out.println("排序后的结果==>");
        printGameRecordInfo(records);

    }

    private static void printGameRecordInfo(Collection<GameRecord> records) {
        for (GameRecord record : records) {
            System.out.println(String.format("%s\t%d\t%s",
                    DateUtils.formatDate(record.getCreateTime(), "yyyy-MM-dd HH:mm:ss"),
                    record.getScore(), record.getName()));
        }
    }

}

运行结果如下:

排序前的结果==>
2016-05-11 12:56:01  300  Jun
2016-05-11 12:56:01  100  Meng
2015-12-31 00:22:30  300  Wang
排序后的结果==>
2015-12-31 00:22:30  300  Wang
2016-05-11 12:56:01  300  Jun
2016-05-11 12:56:01  100  Meng

二、使用Comparator来实现

如果采用Comparator来实现,那么实体类GameRecord类无需实现Comparable接口无需改变实体类的内容

我们要做的就是建立一个外部的Comparator即可。实体类GameRecord和外部Compartor代码如下:

import java.util.Date;

import org.apache.http.client.utils.DateUtils;

/**
 * @author wangmengjun
 *
 */
public class GameRecord {

    /**玩家名称*/
    private String name;

    /**记录创建时间*/
    private Date createTime;

    /**得分*/
    private int score;

    /**
     * @param name
     * @param createTime
     * @param score
     */
    public GameRecord(String name, Date createTime, int score) {
        this.name = name;
        this.createTime = createTime;
        this.score = score;
    }

    /**
     * @return the name
     */
    public String getName() {
        return name;
    }

    /**
     * @param name the name to set
     */
    public void setName(String name) {
        this.name = name;
    }

    /**
     * @return the createTime
     */
    public Date getCreateTime() {
        return createTime;
    }

    /**
     * @param createTime the createTime to set
     */
    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    /**
     * @return the score
     */
    public int getScore() {
        return score;
    }

    /**
     * @param score the score to set
     */
    public void setScore(int score) {
        this.score = score;
    }

    /* (non-Javadoc)
     * @see java.lang.Object#toString()
     */
    @Override
    public String toString() {
        return "GameRecord [name=" + name + ", createTime="
                + DateUtils.formatDate(createTime, "yyyy-MM-dd HH:mm:ss") + ", score=" + score
                + "]";
    }

}

比较器GameRecordComparator实现Comparator接口,然后实现Comparatorcompare方法:

比较分数(从高到低),如果分数一致,那么比较记录创建时间 (从早到新)

public int compare(GameRecord r1, GameRecord r2) {
        int scoreCompare =  Integer.compare(r2.getScore(), r1.getScore());
        return scoreCompare == 0 ? r1.getCreateTime().compareTo(r2.getCreateTime()) : scoreCompare;
    }

GameRecordComparator类的详细代码如下:

import java.util.Comparator;

/**
 * @author wangmengjun
 *
 */
public class GameRecordComparator implements Comparator<GameRecord> {

    /* (non-Javadoc)
     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
     */
    @Override
    public int compare(GameRecord r1, GameRecord r2) {
        int scoreCompare =  Integer.compare(r2.getScore(), r1.getScore());
        return scoreCompare == 0 ? r1.getCreateTime().compareTo(r2.getCreateTime()) : scoreCompare;
    }

}

然后,我们就可以直接调用java.util.Collections类的sort方法完成排序,java.util.Collections类的sort方法源码如下:

 public static <T> void sort(List<T> list, Comparator<? super T> c) {
        Object[] a = list.toArray();
        Arrays.sort(a, (Comparator)c);
        ListIterator i = list.listIterator();
        for (int j=0; j<a.length; j++) {
            i.next();
            i.set(a[j]);
        }
    }

测试代码如下:

import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;

import org.apache.commons.httpclient.util.DateParseException;
import org.apache.commons.httpclient.util.DateUtil;
import org.apache.http.client.utils.DateUtils;

/**
 * @author wangmengjun
 *
 */
public class Main {

    /**
     * 可用于转换的日期格式集合
     */
    private static final Collection<String> AVAILABLE_DATE_FORMATTERS = Arrays
            .asList("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) throws DateParseException {

        Date d1 = DateUtil.parseDate("2015-12-31 00:22:30", AVAILABLE_DATE_FORMATTERS);
        Date d2 = DateUtil.parseDate("2016-05-11 12:56:01", AVAILABLE_DATE_FORMATTERS);
        Date d3 = DateUtil.parseDate("2016-05-11 12:56:01", AVAILABLE_DATE_FORMATTERS);

        GameRecord r1 = new GameRecord("Wang", d1, 300);
        GameRecord r2 = new GameRecord("Meng", d2, 100);
        GameRecord r3 = new GameRecord("Jun", d3, 300);

        List<GameRecord> records = Arrays.asList(r3, r2, r1);
        System.out.println("排序前的结果==>");
        //System.out.println(records);
        printGameRecordInfo(records);

        Collections.sort(records, new GameRecordComparator());
        System.out.println("排序后的结果==>");
        printGameRecordInfo(records);

    }

    private static void printGameRecordInfo(Collection<GameRecord> records) {
        for (GameRecord record : records) {
            System.out.println(String.format("%s\t%d\t%s",
                    DateUtils.formatDate(record.getCreateTime(), "yyyy-MM-dd HH:mm:ss"),
                    record.getScore(), record.getName()));
        }
    }

}

运行结果如下:

排序前的结果==>
2016-05-11 12:56:01  300  Jun
2016-05-11 12:56:01  100  Meng
2015-12-31 00:22:30  300  Wang
排序后的结果==>
2015-12-31 00:22:30  300  Wang
2016-05-11 12:56:01  300  Jun
2016-05-11 12:56:01  100  Meng

通过上面的代码,我们完成了使用Comparable以及Comparator实现对象集合排序的示例,接下来,我们来简单分析一下Comparable和Comparator的区别

三、Comparable和Comparator区别

  • 采用Comparable的方法,该方法从类的内部实现对象的比较。采用Comparator的方法,是一种类外部的实现,不需要对需要排序的类(如GameRecord)进行改变,保持原有状态即可。
  • 采用Comparable的方法,因为是类内部实现的,其排序的方式只有一种方式。采用Comparator的方法,因为是外部编写比较器实现的,所以会更加灵活。我们可以编写多种比较器完成不一样的排序

比如:

3.1 游戏分数比较器

import java.util.Comparator;

/**
 * @author wangmengjun
 *
 */
public class ScoreComparator implements Comparator<GameRecord> {

    /* (non-Javadoc)
     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
     */
    @Override
    public int compare(GameRecord r1, GameRecord r2) {
        return Integer.compare(r2.getScore(), r1.getScore());
    }

}
Collections.sort(records, new ScoreComparator());
排序前的结果==>
2016-05-11 12:56:01  300  Jun
2016-05-11 12:56:01  100  Meng
2015-12-31 00:22:30  300  Wang
排序后的结果==>
2016-05-11 12:56:01  300  Jun
2015-12-31 00:22:30  300  Wang
2016-05-11 12:56:01  100  Meng

3.2 游戏记录创建时间比较器

import java.util.Comparator;

/**
 * @author wangmengjun
 *
 */
public class CreateTimeComparator implements Comparator<GameRecord> {

    /* (non-Javadoc)
     * @see java.util.Comparator#compare(java.lang.Object, java.lang.Object)
     */
    @Override
    public int compare(GameRecord r1, GameRecord r2) {
        return r1.getCreateTime().compareTo(r2.getCreateTime());
    }

}
Collections.sort(records, new CreateTimeComparator());
排序前的结果==>
2016-05-11 12:56:01  300  Jun
2016-05-11 12:56:01  100  Meng
2015-12-31 00:22:30  300  Wang
排序后的结果==>
2015-12-31 00:22:30  300  Wang
2016-05-11 12:56:01  300  Jun
2016-05-11 12:56:01  100  Meng

3.3 Comparator适用场景

如果不想改变原来类的状态,添加排序功能,可以使用Comparator来做。如果有多种排序策略,可以采用Comparator来做。

3.4 多属性排序方法

在上述示例中,我们采用先按照分数排序(降序),然后如果分数相等,按照记录的创建日期排序(升序)。以GameRecordComparator的compare方法为例:

    public int compare(GameRecord r1, GameRecord r2) {
        int scoreCompare =  Integer.compare(r2.getScore(), r1.getScore());
        return scoreCompare == 0 ? r1.getCreateTime().compareTo(r2.getCreateTime()) : scoreCompare;
    }

如果属性比较多,假设在分数记录创建时间之外还需要对名称等字段进行比较,那么compare方法中,我们需要一个个地对各个属性字段逐个比较,这样写的越多,我们的if语句或者三元运算符逻辑就会增多。

有没有更加简便的方式来完成多属性排序呢?答案是肯定的

下面我们就采用CompareToBuilder以及ComparisonChain来完成。

3.5 使用org.apache.commons.lang.builder.CompareToBuilder完成多属性排序

    public int compare(GameRecord r1, GameRecord r2) {

        return new CompareToBuilder().append(r2.getScore(), r1.getScore())
                .append(r1.getCreateTime(), r2.getCreateTime())
                .toComparison();

    }

3.6 使用com.google.common.collect.ComparisonChain完成多属性排序

    public int compare(GameRecord r1, GameRecord r2) {
        return ComparisonChain.start().compare(r2.getScore(), r1.getScore())
                .compare(r1.getCreateTime(), r2.getCreateTime()).result();
    }

本文分享自微信公众号 - 孟君的编程札记(gh_0f0f5e0ae1de),作者:孟君

原文出处及转载信息见文内详细说明,如有侵权,请联系 yunjia_community@tencent.com 删除。

原始发表时间:2019-09-06

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • 享元模式浅析

    面向对象技术可以很好地解决一些灵活性或可以扩展性问题,但是很多情况下需要在系统中增加类和对象的个数。当对象数量太多时,将导致对象创建以及垃圾回收的代价过高,造成...

    孟君
  • 装饰者模式浅析

    装饰角色,持有一个Component对象的实例,并定义一个与Componnet接口一致的接口。

    孟君
  • 24点解法

    24点游戏是小时候很喜欢玩的游戏,给出4个数,通过加、减、乘、除完成运算,最终得到结果为24。比如数字1、3、5和9,可以通过3 * 5 + 9 * 1 得到结...

    孟君
  • 一个循环启动APP并保持WiFi常开的多线程类

    本人在使用monkey进行测试的时候,发现monkey参数里面--pct-appswitch参数并不好用,随机性比较大,所以想自己来控制启动APP的次数和间隔。...

    FunTester
  • 西安与腾讯签署战略合作,共同打造智慧教育新典范 | 智慧教育

    ? 1月16日下午,西安市教育局与腾讯公司签署战略合作协议,腾讯将为西安提供基于腾讯教育云的一站式解决方案,助力打造大西安智慧教育新典范。 西安市政府副市长徐...

    腾讯智慧教育
  • 教育公平和质量提升成政府工作报告重点 腾讯教育科技助力探索新路径

    ? 5月22日,第十三届全国人民代表大会第三次会议在人民大会堂举行开幕会。在国务院总理李克强作政府工作报告中,特别指出推动教育公平发展和质量提升,要优化投入结...

    鹅老师
  • Tomcat 9最新版安装与使用手册,tomcat更改端口号,tomcat控制台乱码问题解决方法

    先为大家介绍 tomcat 的环境搭建,后面还有 tomcat 控制台中文乱码的解决办法。

    小蓝枣
  • 开源日志框架的原理与分析

    日志用于记录系统中硬件,软件,系统,进程和应用运行时的信息,同时可以监控系统中发生的各种事件,我们可以用它检查发生错误的原因,找到攻击者留下的攻...

    疯狂的KK
  • 【深度学习研究系列】之漫谈RNN(二)

    ? 推送第二日,量化投资与机器学习公众号将为大家带来一个系列的 Deep Learning 原创研究。本次深度学习系列的撰稿人为 张泽旺 ,DM-Master...

    量化投资与机器学习微信公众号
  • Excel公式技巧19: 在方形区域内填充不重复的随机整数

    本文分享一个基于公式生成n×n随机整数的解决方案,并且每个整数都是唯一的。例如,下图1显示了生成10行10列的不重复随机整数。

    fanjy

扫码关注云+社区

领取腾讯云代金券