搞懂Java对象比较、排序,从TreeSet深入讲解Java两种比较规则

搞懂Java对象比较、排序,从TreeSet深入讲解Java两种比较规则啤酒 满怀忧思 不如先干再说 做干净纯粹的技术分享 欢迎评论区或私信交流 灵光一闪 文章源码收录于 https gitee com stt0626 stt open

大家好,欢迎来到IT知识分享网。

[啤酒]满怀忧思,不如先干再说!做干净纯粹的技术分享!欢迎评论区或私信交流!

​[灵光一闪] 文章源码收录于 https://gitee.com/stt0626/stt-open.git

Java的Set集合中有一个特别的分支TreeSet,可以保障数据不重复的同时还可以对元素进行排序,本文以TreeSet存储Integer类型数据为切入点,逐步深入介绍Java中的排序规则和实现方式,通过本文你可以:

  • 掌握TreeSet排序的原理
  • 自定义对象定义排序规则
  • 修改默认排序规则,定制化排序
  • Comparable接口和Comparator接口区别

TreeSet存储Integer数据

随机生成10个100以内的整数存储到set集合中,打印每次生成的数据和set集合中存储的数据

// 创建Set集合 Set<Integer> set = new TreeSet<>(); // 使用随机数生成10个整数 Random random = new Random(); for (int i = 0; i < 10; i++) { // +1 是因为nextInt(n)是生成 0-n,不需要0,就在数据基础上+1 int result = random.nextInt(100) + 1; System.out.println(result); set.add(result); } System.out.println(set);

发现打印set集合时,数据的顺序是从小到大自动排序

68 58 91 31 44 93 5 4 66 3 [3, 4, 5, 31, 44, 58, 66, 68, 91, 93]

这是因为存储进TreeSet的数据类型为Integer,Integer类实现 Comparable接口

搞懂Java对象比较、排序,从TreeSet深入讲解Java两种比较规则

实现接口随之就会重写接口中的抽象方法,重写comparTo方法,定义排序规则,所以TreeSet在存储数据时根据此规则对数据进行排序

搞懂Java对象比较、排序,从TreeSet深入讲解Java两种比较规则

常用的其他基本数据类型的包装类和String类也都实现Comparable接口,并重写comparTo方法,所以可以使用TreeSet进行存储

存储自定义对象

但如果是自定义对象,比如项目中定义的Book类,直接存储进TreeSet又当如何呢?

包含编号名字类型价格阅读量

package com.stt.comparable.test2; / * @author ShiTian * @date 2023/3/6 11:01 */ public class Book { private long id; private String name; private String type; // 价格 private long price; // 阅读量 private long readNum; public Book(long id, String name, String type, long price, long readNum) { this.id = id; this.name = name; this.type = type; this.price = price; this.readNum = readNum; } public long getId() { return id; } public void setId(long id) { this.id = id; } public String getName() { return name; } public void setName(String name) { this.name = name; } public String getType() { return type; } public void setType(String type) { this.type = type; } public long getPrice() { return price; } public void setPrice(long price) { this.price = price; } public long getReadNum() { return readNum; } public void setReadNum(long readNum) { this.readNum = readNum; } @Override public String toString() { return "Book{" + "id=" + id + ", name='" + name + '\'' + ", type='" + type + '\'' + ", price=" + price + ", readNum=" + readNum + '}'; } }

存储进TreeSet

// 1、创建TreeSet集合 TreeSet<Book> books = new TreeSet<>(); // 2、存储数据 books.add(new Book(1L,"骆驼祥子","文学",4600L,2000L)); books.add(new Book(2L,"纳瓦尔宝典","经济理财",2600L,5943L)); books.add(new Book(3L,"法治的细节","社会文化",7300L,3415L)); books.add(new Book(4L,"植物的战斗","科学技术",4200L,727L)); // 输出set集合 System.out.println(books);

因为TreeSet集合需要履行它的职责,也就是对数据去重的同时进行排序【本文重点是排序,去重先不谈】,现在没有指定排序规则,就会发生如下的ClassCastException即类型转换异常

搞懂Java对象比较、排序,从TreeSet深入讲解Java两种比较规则

也就是存储进TreeSet的数据必须是Comparable类型,所以需要Book类实现该接口,根据多态向上转型,Book类也可认为是一个Comparable类型

根据书价从小到大排序,代码实现如下:

public class Book implements Comparable<Book>{ // 省略 属性,构造方法,getter,setter,toString方法 / * 定义比较规则,按照价格从低到高排序,该方法可能多次被调用 * 返回值:该方法返回int类型数据,简单来说: * 小于0:当前存储进来的对象小于集合中已存在的book,存储到前边 * 大于0:当前存储进来的对象大于集合中已存在的book,存储到后边 * 等于0:两者相等,不需要发生变化 */ @Override public int compareTo(Book book) { // 输出数据帮助理解 // this:当前要存进来的对象 // book:现集合中与this对比的对象 System.out.println("比较对象======"); System.out.println("this===>" + this); System.out.println("book===>" + book); if(this.getPrice() - book.getPrice() > 0) { return 1; }else if(this.getPrice() - book.getPrice() < 0) { return -1; }else { return 0; } } }

再次运行结果如下:发现在存储数据时就会调用comparTo方法进行比较,而且comparTo方法会被多次调用,最终保障数据有序

比较对象====== this===>Book{id=1, name='骆驼祥子', type='文学', price=4600, readNum=2000} book===>Book{id=1, name='骆驼祥子', type='文学', price=4600, readNum=2000} 比较对象====== this===>Book{id=2, name='纳瓦尔宝典', type='经济理财', price=2600, readNum=5943} book===>Book{id=1, name='骆驼祥子', type='文学', price=4600, readNum=2000} 比较对象====== this===>Book{id=3, name='法治的细节', type='社会文化', price=7300, readNum=3415} book===>Book{id=1, name='骆驼祥子', type='文学', price=4600, readNum=2000} 比较对象====== this===>Book{id=4, name='植物的战斗', type='科学技术', price=4200, readNum=727} book===>Book{id=1, name='骆驼祥子', type='文学', price=4600, readNum=2000} 比较对象====== this===>Book{id=4, name='植物的战斗', type='科学技术', price=4200, readNum=727} book===>Book{id=2, name='纳瓦尔宝典', type='经济理财', price=2600, readNum=5943} [Book{id=2, name='纳瓦尔宝典', type='经济理财', price=2600, readNum=5943}, Book{id=4, name='植物的战斗', type='科学技术', price=4200, readNum=727}, Book{id=1, name='骆驼祥子', type='文学', price=4600, readNum=2000}, Book{id=3, name='法治的细节', type='社会文化', price=7300, readNum=3415}]

compareTo方法返回的是正整数,负整数和0,所以上边的比较代码可以简化为一行:

@Override public int compareTo(Book book) { // 因为价格是long类型需要转换为int类型返回 return (int) (this.getPrice() - book.getPrice()); }

建议:在代码中添加输出,便于理解,多多动手,远比空想有用的多!

多条件比较

如果有的书价相同,即纳瓦尔宝典植物的战斗都是26块钱

// 1、 创建TreeSet集合 TreeSet<Book> books = new TreeSet<>(); // 2、存储数据 books.add(new Book(1L,"骆驼祥子","文学",4600L,2000L)); books.add(new Book(2L,"纳瓦尔宝典","经济理财",2600L,5943L)); books.add(new Book(3L,"法治的细节","社会文化",7300L,3415L)); books.add(new Book(4L,"植物的战斗","科学技术",2600L,727L)); // 输出set集合 System.out.println(books);

如果此时再根据阅读量从大到小排序,即多个条件,可以通过以下方法实现

@Override public int compareTo(Book book) { // 价格相同 if(this.getPrice() - book.getPrice() == 0) { // 比较阅读量,需要升序排序让其返回负数时交换位置 return (int) (book.getReadNum() - this.getReadNum()); } // 否则根据价格比较 return (int) (this.getPrice() - book.getPrice()); }

输出结果,价格相同的,阅读量多的在前边

[ Book{id=2, name='纳瓦尔宝典', type='经济理财', price=2600, readNum=5943}, Book{id=4, name='植物的战斗', type='科学技术', price=2600, readNum=727}, Book{id=1, name='骆驼祥子', type='文学', price=4600, readNum=2000}, Book{id=3, name='法治的细节', type='社会文化', price=7300, readNum=3415} ]

比较规则总结

  • 每次数据都会先与第一个存储进集合的元素比较;
  • 返回值为负整数:表示左边的对象比右边的数小,左右的数据不进行交换;
  • 返回值为0:表示左边的对象等于右边的对象,左右的对象不进行交换;
  • 返回值为正整数:表示左边的对象比右边的对象大,左右的对象进行交换;
  • 存储进一个元素时comparTo方法可能调用多次进行比较确定元素存放位置

此时发现比较规则是在对象中编写的,但是如果这个对象的排序规则不符合我们的需求,而且这个对象我们也无法修改,此时就需要使用Comparator实现定制化排序

Comparator定制化比较规则

比如Integer类默认从小到大排序,此时如果需要从大到小排序怎么办?Integer的源码我们又修改不了,此时可以使用Comparator定制化排序

TreeSet有个构造方法可以接收Comparator比较器,可以通过该构造方法,定义比较规则

搞懂Java对象比较、排序,从TreeSet深入讲解Java两种比较规则

比如:TreeSet中存储整数,需要从大到小排序

// 1、创建TreeSet,并定义排序规则 Set<Integer> set = new TreeSet<>(new Comparator<Integer>() { // 重写compare方法,规则和Comparable接口的compareTo方法一致 // o1是新增进来的数据,o2是集合中已存在的数据,相互比较 @Override public int compare(Integer o1, Integer o2) { // 降序排序 return o2 - o1; } }); // 2、添加数据 Random random = new Random(); for (int i = 0; i < 10; i++) { // +1 是因为nextInt(n)是生成 0-n,不需要0,就在数据基础上+1 int result = random.nextInt(100) + 1; System.out.println(result); set.add(result); } System.out.println(set);

输出结果如下:从大到小排序

30 15 94 21 84 32 82 95 7 29 [95, 94, 84, 82, 32, 30, 29, 21, 15, 7]

此方式与Comparable接口规则一样,根据返回正整数,负整数或者零判断大小关系

自定义对象

比如上述的Book类,定义了比较规则,现平台决定扶持阅读量低的书本,按照阅读量升序排序,即阅读量低的排到前边,原Book类比较规则不变,仍然是价格升序和阅读量降序

@Override public int compareTo(Book book) { // 价格相同 if(this.getPrice() - book.getPrice() == 0) { // 比较阅读量,需要升序排序让其返回负数时交换位置 return (int) (book.getReadNum() - this.getReadNum()); } // 否则根据价格比较 return (int) (this.getPrice() - book.getPrice()); }

定制化排序规则

// 1、创建集合,通过lambda表达式实现接口 Set<Book> books = new TreeSet<Book>((book1, book2) -> { // 根据阅读量降序排序 return (int) (book1.getReadNum() - book2.getReadNum()); }); // 2、添加数据 books.add(new Book(1L,"骆驼祥子","文学",4600L,2000L)); books.add(new Book(2L,"纳瓦尔宝典","经济理财",2600L,5943L)); books.add(new Book(3L,"法治的细节","社会文化",7300L,3415L)); books.add(new Book(4L,"植物的战斗","科学技术",2600L,727L)); System.out.println(books);

运行结果,阅读量少的在前边

[ Book{id=4, name='植物的战斗', type='科学技术', price=2600, readNum=727}, Book{id=1, name='骆驼祥子', type='文学', price=4600, readNum=2000}, Book{id=3, name='法治的细节', type='社会文化', price=7300, readNum=3415}, Book{id=2, name='纳瓦尔宝典', type='经济理财', price=2600, readNum=5943} ]

发现比较规则并没有使用Book类中的规则,而是使用了TreeSet集合传入的Comparator接口的比较规则

思考:原Book类中的比较规则执行了吗?我们不妨输出一下看看

搞懂Java对象比较、排序,从TreeSet深入讲解Java两种比较规则

发现原Book类中定义的比较并没有执行,所以面试官问你Comparable和Comparator同时存在怎么比较?相信你知道怎么回答

总结

  • Java中的有一些可以自动排序的容器如:TreeSet、TreeMap,还有一些工具类可以对List集合排序,如Collections.sort()方法,Stream中的排序,比较等,都是需要定义比较规则才能实现;
  • Java中定义比较规则有对象实现Comparable接口,和在容器构造方法或者类似于sort这样的排序方法中指定比较器,即通过Comparator接口指定临时的比较规则【因为换一个容器或者方法这个规则就不生效,所以称为临时】;
  • Comparable位于包java.lang下,Comparator位于包java.util下,Comparable接口将比较代码嵌入自身类中,Comparator既可以嵌入到自身类中,也可以在使用时比较,通常情况下Comparator相较于Comparable来说耦合性低;
  • Comparator与Comparable同时存在的情况下,则使用Comparator的比较规则;
  • 如果目标类的比较规则不符合我们需求,或者并没有定义比较规则,则可以使用Comparator实现比较规则。

Java的比较都是基于这两种方式实现,如果你有更多的思考和任何疑虑欢迎评论区交流!

免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/92794.html

(0)
上一篇 2024-10-24 13:00
下一篇 2024-10-24 15:45

相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注微信