zoukankan      html  css  js  c++  java
  • Java中对集合排序的实现演变:从Comparable、Comparator到lambda,从啰嗦到简洁

    今年初学Java,是个新人。若文中有错误纰漏,希望能指出,见谅。

    目标:对 User 对象集合进行排序,要求使用简单并且代码可读性强。

    User 类定义如下:

    public class User {
    
        /**
         * id
         */
        private String id;
        /**
         * 姓名
         */
        private String Name;
        /**
         * 年龄
         */
        private int age;
        /**
         * 身高
         */
        private Integer height;
        /**
         * 体重
         */
        private Double weight;
        /**
         * 性别
         */
        private boolean sex;
        /**
         * 出生年月
         */
        private Date birthday;
    
        public String getId() {
            return id;
        }
    
        public void setId(String id) {
            this.id = id;
        }
    
        public String getName() {
            return Name;
        }
    
        public void setName(String name) {
            Name = name;
        }
    
        public int getAge() {
            return age;
        }
    
        public void setAge(int age) {
            this.age = age;
        }
    
        public Integer getHeight() {
            return height;
        }
    
        public void setHeight(Integer height) {
            this.height = height;
        }
    
        public Double getWeight() {
            return weight;
        }
    
        public void setWeight(Double weight) {
            this.weight = weight;
        }
    
        public boolean isSex() {
            return sex;
        }
    
        public void setSex(boolean sex) {
            this.sex = sex;
        }
    
        public Date getBirthday() {
            return birthday;
        }
    
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
    }
    User类

    User 对象集合定义如下:

    private List<User> getUsers() throws Exception{
        SimpleDateFormat format = new SimpleDateFormat("yyyy-MM-dd");
    
        User u1 = new User();
        u1.setId(UUID.randomUUID().toString());
        u1.setName("隔壁老王");
        u1.setSex(false);
        u1.setAge(33);
        u1.setHeight(168);
        u1.setWeight(72.5);
        u1.setBirthday(format.parse("1970-05-13"));
    
        User u2 = new User();
        u2.setId(UUID.randomUUID().toString());
        u2.setName("小头爸爸");
        u2.setSex(false);
        u2.setAge(30);
        u2.setHeight(172);
        u2.setWeight(59.8);
        u2.setBirthday(format.parse("1980-10-08"));
    
        User u3 = new User();
        u3.setId(UUID.randomUUID().toString());
        u3.setName("大头儿子的妈妈");
        u3.setSex(true);
        u3.setAge(27);
        u3.setHeight(165);
        u3.setWeight(47.3);
        u3.setBirthday(format.parse("1983-11-15"));
    
        User u4 = new User();
        u4.setId(UUID.randomUUID().toString());
        u4.setName("大头儿子");
        u4.setSex(false);
        u4.setAge(9);
        u4.setHeight(108);
        u4.setWeight(37D);
        u4.setBirthday(format.parse("2001-04-01"));
    
        List<User> data = new ArrayList<>();
        data.add(u1);
        data.add(u2);
        data.add(u3);
        data.add(u4);
    
        return data;
    }
    User集合定义

     
    User定义中,age(年龄)的数据类型为 int,height(身高)的数据类型为 Integer,以基本类型、包类型为比较,分别对它们实现排序。

    先实现对 height(身高)的排序。

    实现按 height(身高)排序的方式有多种,按照“jdk版本从低到高”、“写法从复杂到简单”的次序逐一演示。

    方法1:定义比较器类

    List<User> data = this.getUsers();
    System.out.println("原始数据:");
    this.prints(data);
    
    class HeightComparator implements Comparator {
        public int compare(Object object1, Object object2) {
                User p1 = (User) object1;
                User p2 = (User) object2;
                return p1.getHeight().compareTo(p2.getHeight());
            }
        }
    
    Collections.sort(data, new HeightComparator());
    System.out.println("按身高排序后:");
    this.prints(data);

    方法2:定义比较器对象

    List<User> data = this.getUsers();
    System.out.println("原始数据:");
    this.prints(data);
    
        Comparator<User> c1 = new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getHeight() - o2.getHeight();
            }
        };
    
    Collections.sort(data, c1);
    System.out.println("按身高排序后:");
    this.prints(data);

    方法3:以lambda方式定义比较器对象

    List<User> data = this.getUsers();
    System.out.println("原始数据:");
    this.prints(data);
    
    Function<User, Integer> f1 = u -> u.getHeight();
    Comparator<User> c1 = Comparator.comparing(f1);
    //上述2句代码,也可以简化成一句:
    //Comparator<User> c1 = Comparator.comparing(u -> u.getHeight());
    
    Collections.sort(data, c1);
    System.out.println("按身高排序后:");
    this.prints(data);

     

    jrk1.8之后实现了lambda表达式,于是有了Comsumer、Function、Predicate这些基于lambda的实现(在java里不知道概念叫什么),相应的Comparator也实现了对Function的支持,因此方法3需要jdk1.8的支持。从上述3种方式来看,代码量越来越少,也越来越优雅。
    方法3已经能够一句代码实现按 height(身高)排序的功能:

    Collections.sort(data, Comparator.comparing(u -> u.getHeight()));

    但这仍旧不足,因为排序操作应该只需要关心“要排序的集合”和“按什么排序”,上面这句代码还多了“比较器”。不能忍,必须消灭它。

    实现对List的工具类方法sort和sortDescending,分别表示顺序排序和倒序排序,实现如下:

    /**
         * 根据指定属性对集合顺序排序
         * @param data 集合对象
         * @param func 委托
         * @param <T> 数据类型
         * @param <R> 要排序的属性的数据类型
         */
        public static <T, R extends Comparable<? super R>> void sort(List<T> data, Function<T, R> func){
            Comparator<T> comparator = Comparator.comparing(func);
            data.sort(comparator);
        }
    
        /**
         * 根据指定属性对集合倒序排序
         * @param data 集合对象
         * @param func 委托
         * @param <T> 数据类型
         * @param <R> 要排序的属性的数据类型
         */
        public static <T, R extends Comparable<? super R>> void sortDescending(List<T> data, Function<T, R> func){
            Comparator<T> comparator = Comparator.comparing(func).reversed();
            data.sort(comparator);
        }

    由于jdk中只支持对List的排序(List对象内置sort方法,Collections.sort方法虽然封装在Collections中,但其实也只支持List),因此可以将上述工具方法封装在ListUtil中,有了这2个方法,就可以像下面那样“快速”地写出可读性很高的排序了:

    List<User> data = this.getUsers();
    System.out.println("原始数据:");
    this.prints(data);
    
    ListUtil.sort(data, a -> a.getHeight());
    System.out.println("按身高顺序排序后:");
    this.prints(data);
    
    ListUtil.sortDescending(data, a -> a.getHeight());
    System.out.println("按身高倒序排序后:");
    this.prints(data);

    若想根据其他属性排序,动几下(真的是几下)手指头,就可以实现:

    ListUtil.sort(data, a -> a.getAge());
    
    ListUtil.sortDescending(data, a -> a.getWeight());

    那么实现对 age(年龄)的排序,和上面是不是一样?
    并不完全一样,存在较大的差异。
    像上面的方法1是无法实现对 age(年龄)的排序的,因为该属性数据类型为 int。
    我们来看一下方法1中一句关键代码:

    return p1.getHeight().compareTo(p2.getHeight());

    Heigth是Integer类型,而这种包装类,都是继承了Comparable接口并实现了compareTo方法的,因此上述代码中可以直接使用compareTo方法。但基本类型int并没有该方法的实现,因此无法以方法1的方式实现排序。

    方法2可以实现对 age(年龄)的排序,但当排序属性非Number类型时,这个compare写起来就微微麻烦了,因为compare的返回值的含义是“1表示大于,0表示等于,-1表示小于”。当排序属性是boolean、string等类型时,得这样写:

    public int compare(User o1, User o2) {
            //性别属性,boolean类型
            int i1 = o1.isSex() ? 1 : 0;
            int i2 = o2.isSex() ? 1 : 0;
            return i1 - i2;
    }
    
    
    public int compare(UT o1, UT o2) {
            //姓名属性,String类型
            int i1 = (o1.getName() == null ? 0 : (o1.getName().charAt(0)));
            int i2 = (o2.getName() == null ? 0 : (o2.getName().charAt(0)));
            return i1 - i2;
    }

    方法3可以实现对 age(年龄)的排序,并且也支持其他各种数据类型的属性,Comparaor中会有对各种数据类型(包含非Number类型)的默认排序规则,比如boolean类型按照false->true排序。

    List<User> data = this.getUsers();
    System.out.println("原始数据:");
    this.prints(data);
    
    Function<User, Integer> f1 = u -> u.getAge();
    Comparator<User> c1 = Comparator.comparing(f1);
    //上述2句代码,也可以简化成一句:
    //Comparator<User> c1 = Comparator.comparing(u -> u.getHeight());
    
    Collections.sort(data, c1);
    System.out.println("按年龄排序后:");
    this.prints(data);

    需要说明的是,虽然 age(年龄)的数据类型是int,但Function中的返回类型不能写成int,因为Function的泛型定义并不支持基本类型。 
    所以,如果要对 sex(性别,数据类型是 boolean)排序,这句Function的定义得这样写:

    Function<UT, Boolean> f1 = u -> u.isSex();


    综上所述,无论是对 height(身高)的排序还是对 age(年龄)的排序,方法3都支持得非常棒。而前面贴出的扩展工具方法—— sort 和 sortDescending,都是基于方法3的,所以该扩展方法,适用于各种排序。


    结尾。
    拥抱变化吧,虽然目前java实现的lambda由于受擦除式泛型的限制,还不是非常灵活,但目前这些新写法,无论是代码数量、简洁程度、优雅性、可读性,在我看来都优于常规的for写法。虽然jdk1.8本身暴露的lambda接口寥寥无几,但我们能够去扩展去完善这些api,让集合操作更优雅简洁。

  • 相关阅读:
    [转] 面向对象软件开发和过程(六)针对契约设计
    [转] 面向对象软件开发和过程(三)案例实战(下)
    条形码字体下载
    [转] 面向对象软件开发和过程(五)优化代码的组织
    JQuery动画效果
    实时监听文本框状态的方法
    JQuery操作元素的属性与样式及位置
    AJAX XMLHttpRequest对象
    JQuery常用方法技巧
    CSS弹出二级菜单
  • 原文地址:https://www.cnblogs.com/matrixkey/p/5893360.html
Copyright © 2011-2022 走看看