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)]
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· Obsidian + DeepSeek:免费 AI 助力你的知识管理,让你的笔记飞起来!
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
2017-08-06 spring的事务控制
2017-08-06 mybatis查询缓存——(十三)
2017-08-06 mybatis延迟加载——(十二)
2017-08-06 关联查询resultMap使用规则总结——(十一)
2017-08-06 mybatis多对多关联查询——(十)
2017-08-06 mybatis一对多关联查询——(九)
2017-08-06 mybatis一对一关联查询——(八)