大家好,欢迎来到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接口
实现接口随之就会重写接口中的抽象方法,重写comparTo方法,定义排序规则,所以TreeSet在存储数据时根据此规则对数据进行排序
常用的其他基本数据类型的包装类和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即类型转换异常
也就是存储进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比较器,可以通过该构造方法,定义比较规则
比如: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类中的比较规则执行了吗?我们不妨输出一下看看
发现原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