Comparable和Comparator的区别&Collections.sort的两种用法

  在Java集合的学习中,我们明白了:

    看到tree,可以按顺序进行排列,就要想到两个接口。Comparable(集合中元素实现这个接口,元素自身具备可比性),Comparator(比较器,传入容器构造方法中,容器具备可比性)。

那么Comparable和Comparator有什么区别呢?

1.  Comparable---接口(集合中元素实现此接口,元素具有可比性)

  Comparable可以认为是一个内比较器,实现了Comparable接口的类有一个特点,就是这些类是可以和自己比较的,至于具体和另一个实现了Comparable接口的类如何比较,则依赖compareTo方法的实现,compareTo方法也被称为自然比较方法。如果开发者add进入一个Collection的对象想要Collections的sort方法帮你自动进行排序的话,那么这个对象必须实现Comparable接口。compareTo(Object o)方法的返回值是int,且此方法只有一个参数,返回值有三种情况:

1、返回正整数

2、返回0

3、返回负整数

  可以这么理解:返回1表示当前元素排在与之对比的元素后面,返回-1表示当前元素排在与之对比的元素前面,返回0表示不排序(按其原顺序排列)。(其实并不是1,-1,0;只要是正数负数和0就可以进行区分)。

  元素自身可以理解为基准,而参数上的obj可以理解为与之对比的元素。

 

1.比如我们想比较人的时候按年龄倒序排列

  思路:实现上面接口,如果与之对比的元素年龄比他大,排在他前面(返回负数),否则排在他后面(返回正数)。

例如:

public class Person implements Comparable<Person> {

    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public int compareTo(Person comparedUser) {
        // 返回负数表示this对象排在comparedUser前面
        if (this.age > comparedUser.getAge()) {
            return -1;
        }

        // 返回正数表示this对象排在comparedUser后面
        if (this.age < comparedUser.getAge()) {
            return 1;
        }

        return 0;
    }

    public Person(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [age=" + age + ", name=" + name + "]";
    }

}

 

测试:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class Test1 {
    public static void main(String[] args) {
        List<Person> list = new ArrayList<>();
        list.add(new Person(20, "张三"));
        list.add(new Person(22, "李四"));
        list.add(new Person(19, "王五"));
        list.add(new Person(19, "张是"));
        list.add(new Person(17, "开发"));
        list.add(new Person(29, "看"));
        
        Collections.sort(list);
        for(Person p : list){
            System.out.println(p);
        }
    }
}

结果:

Person [age=29, name=看]
Person [age=22, name=李四]
Person [age=20, name=张三]
Person [age=19, name=王五]
Person [age=19, name=张是]
Person [age=17, name=开发]

 

2.按照上面的思路我们写一个按年龄正序排列

修改上面的compareTo方法:

    @Override
    public int compareTo(Person comparedUser) {
        // 返回负数表示this对象排在comparedUser前面
        if (this.age < comparedUser.getAge()) {
            return -1;
        }

        // 返回正数表示this对象排在comparedUser后面
        if (this.age > comparedUser.getAge()) {
            return 1;
        }

        return 0;
    } 

测试代码还是上面代码,查看结果:

Person [age=17, name=开发]
Person [age=19, name=王五]
Person [age=19, name=张是]
Person [age=20, name=张三]
Person [age=22, name=李四]
Person [age=29, name=看]

 

3.将自身具有可比性的元素存入TreeSet或者TreeMap进行查看(TreeSet会自动将元素排序,元素作为key的时候TreeMap会根据key排序)

Person.java

public class Person implements Comparable<Person> {

    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    @Override
    public int compareTo(Person comparedUser) {
        // 返回负数表示this对象排在comparedUser前面
        if (this.age > comparedUser.getAge()) {
            return -1;
        }

        // 返回正数表示this对象排在comparedUser后面
        if (this.age < comparedUser.getAge()) {
            return 1;
        }

        return 0;
    }

    public Person(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [age=" + age + ", name=" + name + "]";
    }

}

 

测试代码:

package cn.qlq.test;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;


public class TreeSetTest {
    public static void main(String[] args) {
        Set<Person> list = new TreeSet<>();
        list.add(new Person(20, "张三"));
        list.add(new Person(22, "李四"));
        list.add(new Person(19, "王五"));
        list.add(new Person(19, "张是"));
        list.add(new Person(17, "开发"));
        list.add(new Person(29, "看"));
        
        for(Person p : list){
            System.out.println(p);
        }
        System.out.println("-----------------------------");
        
        //作为key会将key排序
        Map map = new TreeMap();
        map.put(new Person(20, "张三"), "1");
        map.put(new Person(22, "李四"), "2");
        map.put(new Person(19, "张三"), "3");
        map.put(new Person(25, "哇塞"), "4");
        for(Object key :map.keySet()){
            System.out.println(key+"\t"+map.get(key));
        }
        
        System.out.println("-----------------------------");
        //作为value无效
        Map map2 = new TreeMap();
        map2.put("1",new Person(20, "张三"));
        map2.put("2",new Person(22, "李四"));
        map2.put("3",new Person(19, "张三"));
        map2.put("4",new Person(25, "哇塞"));
        for(Object key :map2.keySet()){
            System.out.println(key+"\t"+map2.get(key));
        }
    }
}

结果:

Person [age=29, name=看]
Person [age=22, name=李四]
Person [age=20, name=张三]
Person [age=19, name=王五]
Person [age=17, name=开发]
-----------------------------
Person [age=25, name=哇塞] 4
Person [age=22, name=李四] 2
Person [age=20, name=张三] 1
Person [age=19, name=张三] 3
-----------------------------
1 Person [age=20, name=张三]
2 Person [age=22, name=李四]
3 Person [age=19, name=张三]
4 Person [age=25, name=哇塞]

 

2.  Comparator---接口(可以理解为比较器,给集合传递比较器集合具有可比性)

  Comparator相当于外部比较器,其作为参数传给具有可比性的集合,使集合具有可比性。比如:      TreeSet ts = new TreeSet(new MyComparator());   比较器需要重写compareTo(Object o1,Object o2)方法,返回值也是下面三个值:

1、返回正整数

2、返回0

3、返回负整数

  可以这么理解:返回1表示当前元素排在与之对比的元素后面,返回-1表示当前元素排在与之对比的元素前面,返回0表示不排序(按其原顺序排列)。(其实并不是1,-1,0;只要是正数负数和0就可以进行区分)

   compareTo(Object o1,Object o2)方法的第一个参数可以理解为基准,而参数上的第二个参数可以理解为与之对比的元素。

1.比如我们想比较人的时候按年龄倒序排列

public class Person{

    private int age;
    private String name;

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public String getName() {
        return name;
    }

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

    public Person(int age, String name) {
        super();
        this.age = age;
        this.name = name;
    }

    @Override
    public String toString() {
        return "Person [age=" + age + ", name=" + name + "]";
    }
    

}

 

比较器类:(可以理解为第一个参数是基准,第二个参数是与之对比的元素,返回-1表示基准排在与之对比元素前面,返回1表示基准在与之对比的元素后面)

import java.util.Comparator;

public class PersonComparator implements Comparator<Person> {

    /**
     * 第一个参数可以理解为基准,第二个是与之比较的元素 。返回负数表示排在其前面,返回正数表示排在其后面
     */
    @Override
    public int compare(Person o1, Person o2) {
        // 返回负数表示o1排在o2前面
        if (o1.getAge() > o2.getAge()) {
            return -1;
        }

        // 返回正数表示o1排在o2后面
        if (o1.getAge() < o2.getAge()) {
            return 1;
        }
        return 0;
    }

}

 

测试代码:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class Test1 {
    public static void main(String[] args) {
        List<Person> list = new ArrayList<>();
        list.add(new Person(20, "张三"));
        list.add(new Person(22, "李四"));
        list.add(new Person(19, "王五"));
        list.add(new Person(19, "张是"));
        list.add(new Person(17, "开发"));
        list.add(new Person(29, "看"));
        
        Collections.sort(list,new PersonComparator());
        for(Person p : list){
            System.out.println(p);
        }
    }
}

结果:

Person [age=29, name=看]
Person [age=22, name=李四]
Person [age=20, name=张三]
Person [age=19, name=王五]
Person [age=19, name=张是]
Person [age=17, name=开发]

 

2.写一个正序的比较器就简单了

(1)直接调用工具类反转比较器实现一个正序比较器:

Comparator<Person> reverseOrder = Collections.reverseOrder(new PersonComparator());

其内部是构造了一个反向比较器:(将基准更换)

    private static class ReverseComparator2<T> implements Comparator<T>,
        Serializable
    {
        private static final long serialVersionUID = 4374092139857L;

        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);
        }
        ...
}

 

(2)修改比较器代码:

import java.util.Comparator;

public class PersonComparator implements Comparator<Person> {

    /**
     * 第一个参数可以理解为基准,第二个是与之比较的元素 。返回负数表示排在其前面,返回正数表示排在其后面
     */
    @Override
    public int compare(Person o1, Person o2) {
        // 返回负数表示o1排在o2前面
        if (o1.getAge() < o2.getAge()) {
            return -1;
        }

        // 返回正数表示o1排在o2后面
        if (o1.getAge() > o2.getAge()) {
            return 1;
        }
        return 0;
    }

}

 

3.将一个比较器传入TreeSet或者TreeMap是集合有序,或者TreeMap的key值有序(也就是TreeMap排序是将key排序)

  TreeSet中的元素会自动排序,根据传下来的比较器对里面的元素进行排序。TreeMap传入比较器的话是元素作为key才可以排序,如果传入比较器但是key不是比较器指定的元素会报错。。。。。。。

比较器:(正序比较器)

import java.util.Comparator;

public class PersonComparator implements Comparator<Person> {

    /**
     * 第一个参数可以理解为基准,第二个是与之比较的元素 。 返回正数表示o1排在o2后面,返回负数表示o1排在o2前面
     */
    @Override
    public int compare(Person o1, Person o2) {
        if (o1.getAge() > o2.getAge()) {
            return 1;
        }

        if (o1.getAge() < o2.getAge()) {
            return -1;
        }
        return 0;
    }

}

 

测试代码:

package cn.qlq.test;

import java.util.Map;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;


public class TreeSetTest {
    public static void main(String[] args) {
        Set<Person> list = new TreeSet<>(new PersonComparator());
        list.add(new Person(20, "张三"));
        list.add(new Person(22, "李四"));
        list.add(new Person(19, "王五"));
        list.add(new Person(19, "张是"));
        list.add(new Person(17, "开发"));
        list.add(new Person(29, "看"));
        
        for(Person p : list){
            System.out.println(p);
        }
        System.out.println("-----------------------------");
        
        //作为key不会报错,会将key排序
        Map map = new TreeMap(new PersonComparator());
        map.put(new Person(20, "张三"), "1");
        map.put(new Person(22, "李四"), "2");
        map.put(new Person(19, "张三"), "3");
        map.put(new Person(25, "哇塞"), "4");
        for(Object key :map.keySet()){
            System.out.println(key+"\t"+map.get(key));
        }
        
        //作为value报错
        Map map2 = new TreeMap(new PersonComparator());
        map2.put("1",new Person(20, "张三"));
        map2.put("2",new Person(22, "李四"));
        map2.put("3",new Person(19, "张三"));
        map2.put("4",new Person(25, "哇塞"));
        for(Object key :map2.keySet()){
            System.out.println(key+"\t"+map2.get(key));
        }
    }
}

结果:

Person [age=17, name=开发]
Person [age=19, name=王五]
Person [age=20, name=张三]
Person [age=22, name=李四]
Person [age=29, name=看]
-----------------------------
Person [age=19, name=张三] 3
Person [age=20, name=张三] 1
Person [age=22, name=李四] 2
Person [age=25, name=哇塞] 4
-----------------------------
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to cn.qlq.test.Person
at cn.qlq.test.PersonComparator.compare(PersonComparator.java:1)
at java.util.TreeMap.compare(Unknown Source)
at java.util.TreeMap.put(Unknown Source)
at cn.qlq.test.TreeSetTest.main(TreeSetTest.java:37)

 

补充:Collections.sort的两种用法

  Collections.sort有两种用法,第一个是只传递一个list参数,第二个是传递两个参数(list,Comparator):

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

    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]);
        }
    }

 

(1)Collections.sort(list)默认采用升序排列;  Collections.reverse(list) 是对集合进行反转

package cn.xm.exam.test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class Test2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(5);
        list.add(4);
        list.add(6);
        list.add(3);
        list.add(1);
        System.out.println(list);
        System.out.println("===================");
        Collections.sort(list);
        System.out.println(list);
    }
}

结果:

[5, 4, 6, 3, 1]
===================
[1, 3, 4, 5, 6]

 

如果想要降序排列可以先正序排列之后再反转即可:

        Collections.sort(list); //正序排列
        Collections.reverse(list);//反转集合

 

(2)Collections.sort(list, Comparator);可以传入一个比较器,也可以将比较器反转

package cn.xm.exam.test;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;

public class Test2 {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(5);
        list.add(4);
        list.add(6);
        list.add(3);
        list.add(1);
        System.out.println(list);
        System.out.println("===================");
        Collections.sort(list, new IntegerComparator());
        System.out.println(list);
    }
}

/**
 * 一个逆序排列的比较器
 * 
 * @author Administrator
 *
 */
class IntegerComparator implements Comparator<Integer> {
    @Override
    public int compare(Integer o1, Integer o2) {
        if (o1 > o2) {
            return -1;// 大的排在前面
        } else if (o1 < o2) {
            return 1;// 小的排在后面
        }
        return 0;
    }
}

结果:

[5, 4, 6, 3, 1]
===================
[6, 5, 4, 3, 1]

 

当然了,比较器也可以反转之后再次使用,例如:

        List<Integer> list = new ArrayList<>();
        list.add(5);
        list.add(4);
        list.add(6);
        list.add(3);
        list.add(1);
        System.out.println(list);

        System.out.println("===================");
        Comparator<Integer> reverseOrder = Collections.reverseOrder(new IntegerComparator());// 将比较器反转
        Collections.sort(list, reverseOrder);// 用反转后的比较器排序

        System.out.println(list);

结果:

[5, 4, 6, 3, 1]
===================
[1, 3, 4, 5, 6]

 

补充:TreeSet也可以用于集合去重,基于TreeMap和TreeSet中的元素或者比较器返回0的时候导致元素丢失.比如

User类

package com.xm.ggn.test.comparator;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

/**
 * @Author: qlq
 * @Description
 * @Date: 8:50 2021/2/6
 */
@Data
@AllArgsConstructor
public class User {

    private String username;

    private Integer age;
}

去重工具类:

package com.xm.ggn.test.comparator;

import java.util.*;

/**
 * @Author: qlq
 * @Description
 * @Date: 9:09 2021/2/6
 */
public class MyCollectionUtils {

    public static <T> List<T> distinct(Collection<T> src, Comparator<T> comparator) {
        TreeSet<T> ts = new TreeSet<>(comparator);
        ts.addAll(src);
        return new ArrayList<>(ts);
    }
}

测试类:

package com.xm.ggn.test.comparator;

import com.google.common.collect.Lists;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * @Author: qlq
 * @Description
 * @Date: 21:55 2021/2/5
 */
public class ComparatorTest {

    public static void main(String[] args) {
        User zs = new User("zs", 1);
        User zs3 = new User("zs", 3);
        User zs1 = new User("zs", 1);
        User ls = new User("ls", 1);
        User ls3 = new User("ls", 3);
        User ls1 = new User("ls", 1);
        ArrayList<User> users = Lists.newArrayList(zs, zs3, zs1, ls, ls3, ls1);
        System.out.println(users);

        List<User> distinct = MyCollectionUtils.distinct(users, new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                if (o1.getAge().equals(o2.getAge()) && o1.getUsername().equals(o2.getUsername())) {
                    return 0;
                }
                return 1;
            }
        });
        System.out.println(distinct);
    }
}

结果:

[User(username=zs, age=1), User(username=zs, age=3), User(username=zs, age=1), User(username=ls, age=1), User(username=ls, age=3), User(username=ls, age=1)]
[User(username=zs, age=1), User(username=zs, age=3), User(username=ls, age=1), User(username=ls, age=3)]
(1)Comparator是一个函数式接口,也可以用lambda表达式,如下:
        List<User> distinct = MyCollectionUtils.distinct(users, (o1, o2) ->
                o1.getAge().equals(o2.getAge()) && o1.getUsername().equals(o2.getUsername()) ? 0 : 1
        );

(2) TreeSet去重的原因: 

 1》TreeSet 内部实际是基于TreeMap来实现的,元素作为TreeMap的key,value值是一个固定的Object对象

    /**
     * The backing map.
     */
    private transient NavigableMap<E,Object> m;

    // Dummy value to associate with an Object in the backing Map
    private static final Object PRESENT = new Object();

    TreeSet(NavigableMap<E,Object> m) {
        this.m = m;
    }
    public TreeSet() {
        this(new TreeMap<E,Object>());
    }

    public boolean add(E e) {
        return m.put(e, PRESENT)==null;
    }

2》 TreeMap 中put源码:当comparator返回为0时, 覆盖值,源码如下:

    public V put(K key, V value) {
        Entry<K,V> t = root;
        if (t == null) {
            compare(key, key); // type (and possibly null) check

            root = new Entry<>(key, value, null);
            size = 1;
            modCount++;
            return null;
        }
        int cmp;
        Entry<K,V> parent;
        // split comparator and comparable paths
        Comparator<? super K> cpr = comparator;
        if (cpr != null) {
            do {
                parent = t;
                cmp = cpr.compare(key, t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        else {
            if (key == null)
                throw new NullPointerException();
            @SuppressWarnings("unchecked")
                Comparable<? super K> k = (Comparable<? super K>) key;
            do {
                parent = t;
                cmp = k.compareTo(t.key);
                if (cmp < 0)
                    t = t.left;
                else if (cmp > 0)
                    t = t.right;
                else
                    return t.setValue(value);
            } while (t != null);
        }
        Entry<K,V> e = new Entry<>(key, value, parent);
        if (cmp < 0)
            parent.left = e;
        else
            parent.right = e;
        fixAfterInsertion(e);
        size++;
        modCount++;
        return null;
    }

补充:Comparator提供了一些简便的比较器写法,比如:Comparator.comparing, 还有nullFirst、nullLast等方法 等方法

简单查看下comparing 方法

(1) 方法源码如下:

    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

 实际上就是调用Function接口的R apply(T t); 方法返回的两个元素进行比较。

第一版重写比较器按姓名排序如下:

        List<User> distinct = MyCollectionUtils.distinct(users, Comparator.comparing(new Function<User, String>() {
            @Override
            public String apply(User user) {
                return user.getUsername();
            }
        }));

第二版:改造lambda如下:

        List<User> distinct = MyCollectionUtils.distinct(users, Comparator.comparing(user -> {
            return user.getUsername();
        }));

类型推导会自动推断类型为User类型,如果不在上下文中直接用上面lambda会编译错误。

第三版:再次改造lambda表达式:使用对象方法引用。这种方法的使用规则是:抽象方法的第一个参数类型刚好是实例方法的类型,抽象方法剩余的参数恰好可以当做实例方法的参数。

List<User> distinct = MyCollectionUtils.distinct(users, Comparator.comparing(User::getUsername));

Comparator.comparing(User::getUsername)) 这种写法实际等价于

        Comparator<User> comparing = Comparator.comparing(new Function<User, String>() {
            @Override
            public String apply(User user) {
                return user.getUsername();
            }
        });

也等价于:

        Comparator<User> comparing = new Comparator<User>() {
            @Override
            public int compare(User o1, User o2) {
                return o1.getUsername().compareTo(o2.getUsername());
            }
        };

补充: Comparator 有一个反序的方法  reversed

User:

package java8test;

import lombok.Data;

import java.sql.Timestamp;

@Data
public class User {

    private String username;

    private Timestamp createTime;
}

测试类

package java8test;

import com.google.common.collect.Lists;
import lombok.SneakyThrows;

import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Comparator;

public class Client {

    @SneakyThrows
    public static void main(String[] args) {
        User user1 = new User();
        user1.setUsername("user1");
        user1.setCreateTime(new Timestamp(System.currentTimeMillis()));

        Thread.sleep(1 * 1000);

        User user2 = new User();
        user2.setUsername("user2");
        user2.setCreateTime(new Timestamp(System.currentTimeMillis()));

        Thread.sleep(1 * 1000);

        User user3 = new User();
        user3.setUsername("user3");
        user3.setCreateTime(new Timestamp(System.currentTimeMillis()));

        ArrayList<User> users = Lists.newArrayList(user1, user3, user2);
        System.out.println(users);

        // 按时间正序
        users.sort(Comparator.comparing(User::getCreateTime));
        System.out.println(users);
        // 按时间降序
        users.sort(Comparator.comparing(User::getCreateTime).reversed());
        System.out.println(users);
    }
}

结果:

[User(username=user1, createTime=2021-02-24 15:35:45.869), User(username=user3, createTime=2021-02-24 15:35:47.87), User(username=user2, createTime=2021-02-24 15:35:46.87)]
[User(username=user1, createTime=2021-02-24 15:35:45.869), User(username=user2, createTime=2021-02-24 15:35:46.87), User(username=user3, createTime=2021-02-24 15:35:47.87)]
[User(username=user3, createTime=2021-02-24 15:35:47.87), User(username=user2, createTime=2021-02-24 15:35:46.87), User(username=user1, createTime=2021-02-24 15:35:45.869)]

 

posted @ 2018-08-06 23:08  QiaoZhi  阅读(825)  评论(3编辑  收藏  举报