10.TreeSet、比较器
Comparable 简介
Comparable 是排序接口。
若一个类实现了Comparable接口,就意味着“该类支持排序”。 即然实现Comparable接口的类支持排序,假设现在存在“实现Comparable接口的类的对象的List列表(或数组)”,则该List列表(或数组)可以通过 Collections.sort(或 Arrays.sort)进行排序。
此外,“实现Comparable接口的类的对象”可以用作“有序映射(如TreeMap)”中的键或“有序集合(TreeSet)”中的元素,而不需要指定比较器。
Comparable 定义
Comparable 接口仅仅只包括一个函数,它的定义如下:
package java.lang;
import java.util.*;
public interface Comparable<T> {
public int compareTo(T o);
}
说明:
假设我们通过 x.compareTo(y) 来“比较x和y的大小”。若返回“负数”,意味着“x比y小”;返回“零”,意味着“x等于y”;返回“正数”,意味着“x大于y”。
Comparator 简介
Comparator 是比较器接口。
我们若需要控制某个类的次序,而该类本身不支持排序(即没有实现Comparable接口);那么,我们可以建立一个“该类的比较器”来进行排序。这个“比较器”只需要实现Comparator接口即可。
也就是说,我们可以通过“实现Comparator类来新建一个比较器”,然后通过该比较器对类进行排序。
Comparator 定义
Comparator 接口仅仅只包括两个个函数,它的定义如下:
package java.util;
public interface Comparator<T> {
int compare(T o1, T o2);
boolean equals(Object obj);
}
说明:
(01) 若一个类要实现Comparator接口:它一定要实现compareTo(T o1, T o2) 函数,但可以不实现 equals(Object obj) 函数。
为什么可以不实现 equals(Object obj) 函数呢? 因为任何类,默认都是已经实现了equals(Object obj)的。 Java中的一切类都是继承于java.lang.Object,在Object.java中实现了equals(Object obj)函数;所以,其它所有的类也相当于都实现了该函数。
(02) int compare(T o1, T o2) 是“比较o1和o2的大小”。返回“负数”,意味着“o1比o2小”;返回“零”,意味着“o1等于o2”;返回“正数”,意味着“o1大于o2”。
Comparator 和 Comparable 比较
Comparable是排序接口;若一个类实现了Comparable接口,就意味着“该类支持排序”。
而Comparator是比较器;我们若需要控制某个类的次序,可以建立一个“该类的比较器”来进行排序。
我们不难发现:Comparable相当于“内部比较器”,而Comparator相当于“外部比较器”。
我们通过一个测试程序来对这两个接口进行说明。源码如下:
import java.util.*;
import java.lang.Comparable;
/**
* @desc "Comparator"和“Comparable”的比较程序。
* (01) "Comparable"
* 它是一个排序接口,只包含一个函数compareTo()。
* 一个类实现了Comparable接口,就意味着“该类本身支持排序”,它可以直接通过Arrays.sort() 或 Collections.sort()进行排序。
* (02) "Comparator"
* 它是一个比较器接口,包括两个函数:compare() 和 equals()。
* 一个类实现了Comparator接口,那么它就是一个“比较器”。其它的类,可以根据该比较器去排序。
*
* 综上所述:Comparable是内部比较器,而Comparator是外部比较器。
* 一个类本身实现了Comparable比较器,就意味着它本身支持排序;若它本身没实现Comparable,也可以通过外部比较器Comparator进行排序。
*/
public class CompareComparatorAndComparableTest{
public static void main(String[] args) {
// 新建ArrayList(动态数组)
ArrayList<Person> list = new ArrayList<Person>();
// 添加对象到ArrayList中
list.add(new Person("ccc", 20));
list.add(new Person("AAA", 30));
list.add(new Person("bbb", 10));
list.add(new Person("ddd", 40));
// 打印list的原始序列
System.out.printf("Original sort, list:%s\n", list);
// 对list进行排序
// 这里会根据“Person实现的Comparable<String>接口”进行排序,即会根据“name”进行排序
Collections.sort(list);
System.out.printf("Name sort, list:%s\n", list);
// 通过“比较器(AscAgeComparator)”,对list进行排序
// AscAgeComparator的排序方式是:根据“age”的升序排序
Collections.sort(list, new AscAgeComparator());
System.out.printf("Asc(age) sort, list:%s\n", list);
// 通过“比较器(DescAgeComparator)”,对list进行排序
// DescAgeComparator的排序方式是:根据“age”的降序排序
Collections.sort(list, new DescAgeComparator());
System.out.printf("Desc(age) sort, list:%s\n", list);
// 判断两个person是否相等
testEquals();
}
/**
* @desc 测试两个Person比较是否相等。
* 由于Person实现了equals()函数:若两person的age、name都相等,则认为这两个person相等。
* 所以,这里的p1和p2相等。
*
* TODO:若去掉Person中的equals()函数,则p1不等于p2
*/
private static void testEquals() {
Person p1 = new Person("eee", 100);
Person p2 = new Person("eee", 100);
if (p1.equals(p2)) {
System.out.printf("%s EQUAL %s\n", p1, p2);
} else {
System.out.printf("%s NOT EQUAL %s\n", p1, p2);
}
}
/**
* @desc Person类。
* Person实现了Comparable接口,这意味着Person本身支持排序
*/
private static class Person implements Comparable<Person>{
int age;
String name;
public Person(String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String toString() {
return name + " - " +age;
}
/**
* 比较两个Person是否相等:若它们的name和age都相等,则认为它们相等
*/
boolean equals(Person person) {
if (this.age == person.age && this.name == person.name)
return true;
return false;
}
/**
* @desc 实现 “Comparable<String>” 的接口,即重写compareTo<T t>函数。
* 这里是通过“person的名字”进行比较的
*/
@Override
public int compareTo(Person person) {
return name.compareTo(person.name);
//return this.name - person.name;
}
}
/**
* @desc AscAgeComparator比较器
* 它是“Person的age的升序比较器”
*/
private static class AscAgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
/**
* @desc DescAgeComparator比较器
* 它是“Person的age的升序比较器”
*/
private static class DescAgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p2.getAge() - p1.getAge();
}
}
}
下面对这个程序进行说明。
a) Person类定义。如下:
private static class Person implements Comparable<Person>{
int age;
String name;
...
/**
* @desc 实现 “Comparable<String>” 的接口,即重写compareTo<T t>函数。
* 这里是通过“person的名字”进行比较的
*/
@Override
public int compareTo(Person person) {
return name.compareTo(person.name);
//return this.name - person.name;
}
}
说明:
(01) Person类代表一个人,Persong类中有两个属性:age(年纪) 和 name“人名”。
(02) Person类实现了Comparable接口,因此它能被排序。
b) 在main()中,我们创建了Person的List数组(list)。如下:
// 新建ArrayList(动态数组)
ArrayList<Person> list = new ArrayList<Person>();
// 添加对象到ArrayList中
list.add(new Person("ccc", 20));
list.add(new Person("AAA", 30));
list.add(new Person("bbb", 10));
list.add(new Person("ddd", 40));
c) 接着,我们打印出list的全部元素。如下:
// 打印list的原始序列
System.out.printf("Original sort, list:%s\n", list);
d) 然后,我们通过Collections的sort()函数,对list进行排序。
由于Person实现了Comparable接口,因此通过sort()排序时,会根据Person支持的排序方式,即 compareTo(Person person) 所定义的规则进行排序。如下:
// 对list进行排序
// 这里会根据“Person实现的Comparable<String>接口”进行排序,即会根据“name”进行排序
Collections.sort(list);
System.out.printf("Name sort, list:%s\n", list);
e) 对比Comparable和Comparator
我们定义了两个比较器 AscAgeComparator 和 DescAgeComparator,来分别对Person进行 升序 和 降低 排序。
e.1) AscAgeComparator比较器
它是将Person按照age进行升序排序。代码如下:
/**
* @desc AscAgeComparator比较器
* 它是“Person的age的升序比较器”
*/
private static class AscAgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p1.getAge() - p2.getAge();
}
}
e.2) DescAgeComparator比较器
它是将Person按照age进行降序排序。代码如下:
/**
* @desc DescAgeComparator比较器
* 它是“Person的age的升序比较器”
*/
private static class DescAgeComparator implements Comparator<Person> {
@Override
public int compare(Person p1, Person p2) {
return p2.getAge() - p1.getAge();
}
}
f) 运行结果
运行程序,输出如下:
Original sort, list:[ccc - 20, AAA - 30, bbb - 10, ddd - 40]
Name sort, list:[AAA - 30, bbb - 10, ccc - 20, ddd - 40]
Asc(age) sort, list:[bbb - 10, ccc - 20, AAA - 30, ddd - 40]
Desc(age) sort, list:[ddd - 40, AAA - 30, ccc - 20, bbb - 10]
eee - 100 EQUAL eee - 100
1、概述:TreeSet可以对Set集合中的元素进行排序。底层数据结构是二叉树。保证元素唯一性的依据:compareTo方法return 0.
* TreeSet存储对象的时候, 可以排序, 但是需要指定排序的算法
*
* Integer能排序(有默认顺序), String能排序(有默认顺序), 自定义的类存储的时候出现异常(没有顺序)
*
* 如果想把自定义类的对象存入TreeSet进行排序, 那么必须实现Comparable接口
* 在类上implement Comparable
* 重写compareTo()方法
* 在方法内定义比较算法, 根据大小关系, 返回正数负数或零
* 在使用TreeSet存储对象的时候, add()方法内部就会自动调用compareTo()方法进行比较, 根据比较结果使用二叉树形式进行存储
*/
2.TreeSet是依靠TreeMap来实现的。
TreeSet是一个有序集合,TreeSet中的元素将按照升序排列(指排序的顺序),缺省是按照自然排序进行排列,意味着TreeSet中的元素要实现Comparable接口。或者有一个自定义的比较器。
我们可以在构造TreeSet对象时,传递实现Comparator接口的比较器对象。

import java.util.Iterator;
import java.util.*;
public class TreeSetTest {
public static void main(String[] args) {
Set ts = new TreeSet();
ts.add("abc");
ts.add("xyz");
ts.add("rst");
Iterator it = ts.iterator();
while (it.hasNext()) {
System.out.println(it.next());
}
}
}

输出结果:
abc
rst
xyz
打印结果不是和先前加入的顺序一样,它是按照一个字母的排序法进行排序的。这是因为String 类实现了Comparable接口。
如果我们自己定义的一个类的对象要加入到TreeSet当中,那么这个类必须要实现Comparable接口。
3、关于二叉树,见图说话:


二叉树的存储方式就像树叉一样,所以称为二叉树。它的特点就是把大的数放在右边,小的数放在左边,取值时会按照从小到大的顺序取值。如果数据较多,二叉树会自动折中,然后再去判断,如图中就会折中到第五个位置上。这样就大大提高了存取的效率。
5、排序的两种实现方式,让元素本身具备比较性(Comparable)和让容器具备比较性(比较器Comparator)。
1)让元素本身具备比较性,有些元素本身就具备比较性如String和Integer,这是因为他们实现Comparable接口,这种排序方式也称为自然顺序排序。下面的方法用在list也可以,因为接口是定义在要比较的对象里的
public class TreeSetDemo {public static void main(String[] args) {TreeSet ts = new TreeSet(//new ComparatorByName());//传入了下边的ComparatorByName()就可以直接按姓名比较person有了自然排序,treeset有了比较器,以比较器为主,比较器常用(结合下边看)- ));
ts.add(new Person("lisi",21));ts.add(new Person("zhouqi",29));ts.add(new Person("zhaoliu",25));ts.add(new Person("wangu",24));Iterator it = ts.iterator();while(it.hasNext()){Person p = (Person)it.next();System.out.println(p.getName()+":"+p.getAge());}}}

上面这种方式是元素本身就具备比较性,但是要是没有具备比较性那么我们就必须自己去实现comparable接口去重写compareTo方法,自己建立需要的比较性。根本不看hashcode和equals.
// int temp = this.name.compareTo(p.name);按姓名排// return temp==0?this.age-p.age:temp; /* if(this.age>p.age) return 1; if(this.age<p.age) return -1; else{//=0时继续按次要条件(姓名)比较 return this.name.compareTo(p.name);比较名字string里也有compareTo } */ } }
return o1.getS().compareTo(o2.getS()); } return num; } } class ComString { private String s; ComString(String s) { this.s = s; } public String getS() { return s; } public void setS(String s) { this.s = s; } 
10.TreeSet、比较器的更多相关文章
- TreeSet 比较器排序 自定义对象
package cn.itcast.day21.treeset2; import java.util.Comparator; import java.util.TreeSet; /* * TreeSe ...
- Java提高十七:TreeSet 深入分析
前一篇我们分析了TreeMap,接下来我们分析TreeSet,比较有意思的地方是,似乎有Map和Set的地方,Set几乎都成了Map的一个马甲.此话怎讲呢?在前面一篇讨论HashMap和HashSet ...
- (转)TreeSet简单介绍与使用方法
TreeSet简介 TreeSet是JAVA中集合的一种,TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet< ...
- Java集合系列(三):HashSet、LinkedHashSet、TreeSet的使用方法及区别
本篇博客主要讲解Set接口的三个实现类HashSet.LinkedHashSet.TreeSet的使用方法以及三者之间的区别. 注意:本文中代码使用的JDK版本为1.8.0_191 1. HashSe ...
- HashSet vs TreeSet vs LinkedHashSet
使用Set集合的主要原因是因为Set集合里面没有重复的元素.Set集合有三个常见的实现类:HashSet,TreeSet,LinkedHashSet.什么时候,选择哪一个使用非常重要.简单的说,如果你 ...
- Java 容器 & 泛型:三、HashSet,TreeSet 和 LinkedHashSet比较
Writer:BYSocket(泥沙砖瓦浆木匠) 微博:BYSocket 豆瓣:BYSocket 上一篇总结了下ArrayList .LinkedList和Vector比较,今天泥瓦匠总结下Hash ...
- Java特殊数据结构-TreeSet
资料来源 TreeSet初步入门总结 https://www.cnblogs.com/yzssoft/p/7127894.html TreeSet自然排序与比较器排序精讲 https://blog.c ...
- TreeSet简单介绍与使用方法
TreeSet简介 TreeSet是JAVA中集合的一种,TreeSet 是一个有序的集合,它的作用是提供有序的Set集合.它继承于AbstractSet抽象类,实现了NavigableSet< ...
- Java:集合框架的工具类
集合框架的工具类 Arrays:里面都是静态方法,直接用来对各种集合进行操作的公有方法. Collections:里面都是静态方法,直接用来对各种集合进行操作的公有方法. 包括: 1.asList将数 ...
随机推荐
- background url base64
各自含义:data: ----获取数据类型名称image/gif; -----指数据类型名称base64 -----指编码模式AAAAA ------指编码以后的结果. background-imag ...
- GDI+_入门教程【一】
GDI For VisualBasic6.0 [一]文件下载:GDI+ For VB6[一] 简单绘图实例演示百度网盘 1 '以下为作者[vIsiaswx]的教程 '(该教程发布的原地址已无法访问,此 ...
- 2018面向对象程序设计(Java)第18周学习指导及要求
2018面向对象程序设计(Java) 第18周学习指导及要求(2018.12.27-2018.12.30) 学习目标 (1) 综合掌握java基本程序结构: (2) 综合掌握java面向对象程序设 ...
- m3u8文件什么合成便于播放的MP4的方法
先大家要知道M3U8文件不是一个视频文件,里面全是一些路径,说白了就是一个目录而已,所以要看视频,要找到对应存放视频的文件夹.如果不知道怎么找,可以使用文本格式打开M3U8文件,里面会有路径提示. ...
- 基于Gitlab统计代码行--统计所有仓库、所有提交人的代码总行数(新增加-删除)
公司绩效考核要求,统计GITLAB仓库所有人提示有效代码行业 脚本1: 统计所有仓库.所有提交人的代码总行数(新增加-删除) 脚本2: 统计所有仓库.所有提交人的代码提交汇总与删除汇总 脚本3: 统计 ...
- easyui combobox下拉框文字超出宽度有横向滚轮
//下拉框显示横向滚轮 $(".combo").mouseenter(function(){ $(this).prev().combobox("showPanel&quo ...
- Quartz.Net进阶之二:关于触发器的更多信息
与作业一样,触发器相对容易使用,但是在您可以充分利用Quartz.NET之前,确实需要了解和理解各种可自定义的选项. 此外,如前所述,您可以选择不同类型的触发器来满足不同的调度需求. 1.常见触发器属 ...
- mybatis进阶--一对多查询
首先,我们还是先给出一个需求:根据订单id查询订单明细——我们知道,一个订单里面可以有多个订单的明细(需求不明确的同学,请留言或者去淘宝网上的订单处点一下就知道了).这个时候,一个订单,对应多个订单的 ...
- osg探究补充:Node::accept(NodeVisitor& nv)及NodeVisitor简介
前言 在前几节中,我自己觉得讲的比较粗糙,因为实在是时间上不是很充足,今天我想弥补一下,希望不是亡羊补牢.我们在osgViewer::Viewer::eventTraversal()函数中经常看到这么 ...
- 【Spark2.0源码学习】-7.Driver与DriverRunner
承接上一节内容,Client向Master发起RequestSubmitDriver请求,Master将DriverInfo添加待调度列表中(waitingDrivers),下面针对于Dri ...