java的两种比较器
比较算法
日常生活中,如果想比较两个数的大小,可采用做差的方式,做差结果的正负可用来判断两个数的大小。假设A - B = C
- 若整数C > 0,说明 A > B ;
- 若整数C = 0,说明 A = B;
- 若整数C < 0,说明 A < B;
java的两种比较器均基于以上判断逻辑,将两个待比较的Object经过某种处理,返回一个整数值,然后根据整个整数值的正负判断大小。类似地,自定义实现比较器时,也是同样道理,经过逻辑处理之后,返回一个整数。
内部比较器(基于Comparable接口)
当某一个业务类A需要具备可比较的特点时,直接实现Comparable接口,重写compareTo(T o)
方法,意味着类A支持排序。对于存储类A的列表或数组,就可以使用Collections.sort(List l)或Arrays.sort(E[] es)进行排序。
使用这种方式实现的比较器的比较逻辑代码在业务类内部,因此也被称为内部比较器。java的基本数据类型均采用这种方式。
Comparable接口源码
/*** @since 1.2*/ public interface Comparable<T> {public int compareTo(T o); }
Student类
业务类直接继承Comparabe接口,实现compareTo方法
/*** @description: 内部比较器* @Date: 2021/10/20 20:41*/ public class Student implements Comparable<Student> {private int age;private double height;...省略geter setter...public Student( int age,double height) {this.age = age;this.height = height;}/*** 通过年龄排序* @param o* @return*/@Overridepublic int compareTo(Student o) {//按照年龄升序排序,int不需要转化//return this.age - o.getAge();//按照身高排序, double类型需要转化。//用到了基本数据类型的比较器return ((Double)(this.height)).compareTo((Double)(o.getHeight())) ;}public static void main(String[] args) {Student[] ss = new Student[5];ss[0] = new Student(15,1.9);ss[1] = new Student(12,1.8);ss[2] = new Student(10,1.7);ss[3] = new Student(16,1.6);ss[4] = new Student(13,2.1);//工具类对数组排序Arrays.sort(ss);for (int i = 0; i < 5; i++) {System.out.println(ss[i].getHeight());}} }
测试代码
//两个学生比身高public static void compare1() {Student s1 = new Student(16, 1.8);Student s2 = new Student(16, 1.67);System.out.println(s1.compareTo(s2)); // 1}//对学生集合按照身高排序public static void compare1() {Student[] ss = new Student[5];ss[0] = new Student(15,1.9);ss[1] = new Student(12,1.8);ss[2] = new Student(10,1.7);ss[3] = new Student(16,1.6);ss[4] = new Student(13,2.1);//工具类对数组排序Arrays.sort(ss);for (int i = 0; i < 5; i++) {System.out.println(ss[i].getHeight());// 升序 1.6 1.7 1.8 1.9 2.1}}
外部比较器(基于Comparator接口)
在内部比较器中,比较逻辑的实现代码在业务类的内部。当比较逻辑发生变化时,就必须修改业务类的代码,这不符合设计模式的开闭原则。此时可使用基于策略模式的外部比较器。
其方式是新建一个实现Comparator接口的具体比较器类,并将比较逻辑写入int compare(T o1, T o2)
方法。同内部比较器类似,外部比较器同样是对比较算法的实现,因此int compare(T o1, T o2)
的返回值同样是整数,这就要求对于业务类的比较项做差不为整数的情况,需要将其差值转换为整数。
业务类Student2
对比Student的代码,可以发现Student2中只包含了业务自身所需要的代码,没有用于实现比较逻辑的代码
/*** @description: 待比较的业务类* @Date: 2021/10/22 10:02*/ public class Student2 {private double height;private int age;....省略getter setter....public Student2( int age,double height) {this.age = age;this.height = height;} }
外部比较器1
根据业务比较逻辑的设计,创建一个对应的比较器类,该比较器类独立于业务类,两者不存在耦合关系。
该比较器按照年龄比较Student2的”大小“,此处年龄是int型,其差值仍未int型,因此不必转换。/*** @description: 外部比较器1* 按照年龄比较* @Date: 2021/10/22 10:06*/ public class Student2Compartor1 implements Comparator<Student2> {@Overridepublic int compare(Student2 o1, Student2 o2) {return o1.getAge() - o2.getAge();} }
外部比较器2
该比较器按照身高比较Student2的”大小“,此处身高是double型,其差值是double型,因此需要转换。/*** @description: 外部比较器2* 按照身高比较* @Date: 2021/10/22 10:07*/ public class Student2Comparator2 implements Comparator<Student2> {@Overridepublic int compare(Student2 o1, Student2 o2) {//不推荐使用compareTo方法,compareTo底层仍是调用了compare方法// return ((Double)(o1.getHeight())).compareTo(((Double)(o2.getHeight())));//将两double的差值转换为intreturn Double.compare(o1.getHeight(),o2.getHeight());} }
测试代码
创建不同的比较器实例,然后可以进行两个对象的比较,也可进行集合排序public static void comparator1Test(){//父类引用指向子类对象//比较器1 按年龄比较Comparator<Student2> sc1 = new Student2Compartor1();//比较器2 按身高比较Comparator<Student2> sc2 = new Student2Comparator2();//比较两个Student2Student2 s1 = new Student2(11, 1.7);Student2 s2 = new Student2(12, 1.6);System.out.println(sc1.compare(s1, s2)); //-1 按年龄 s1 < s2System.out.println(sc2.compare(s1, s2));// 1 按身高 s1 > s2//对数组排序Student2[] ss = new Student2[5];ss[0] = new Student2(15,1.9);ss[1] = new Student2(12,1.8);ss[2] = new Student2(10,1.7);ss[3] = new Student2(16,1.6);ss[4] = new Student2(13,2.1);//工具类对数组排序,传入一个集合和对应的比较器//传入比较器1 按年龄排序Arrays.sort(ss,sc1);for (int i = 0; i < 5; i++) {System.out.println(ss[i].getAge());//升序 10 12 13 15 16}//传入比较器2 按身高排序Arrays.sort(ss,sc2);for (int i = 0; i < 5; i++) {System.out.println(ss[i].getHeight());//升序 1.6 1.7 1.8 1.9 2.1}}
外部比较器与策略模式
在测试代码中可以看到,Student2的集合ss即可以接收按年龄排序的比较器sc1,也可以接收按身高排序的比较器sc2。查看其方法签名public static <T> void sort(T[] a, Comparator<? super T> c)
,可以发现第二形参是外部比较器的基类。
在实际编码实践中,用户基于不同的比较逻辑,创建不同的外部比较器,这些比较器出现的位置完全可以互换,这实际上就是策略模式的使用。外部比较器基于策略模式组合业务类和比较器类,避免了内部比较器因使用继承造成了耦合,便于修改和扩展。其中,
- 业务类Student2 对应 策略模式中的环境角色
- 外部比较器基类Comparator 对应 策略模式中的抽象策略角色
- 各种自定义的外部比较器(Student2Comparator1、Student2Comparator2)对应 策略模式中的具体策略类
两种外部比较器使用方式
新建外部类
这种方式是显式创建一个独立的外部比较器,使用时创建对应的比较器实例,如上文的测试代码描述。
匿名内部类
在外部比较器时,一般只需实现Comparator的核心方法compare,因此在创建比较器时,可以使用匿名内部类。
//给排序数组传入一个匿名内部类的比较器 Arrays.sort(ss, new Comparator<Student2>() {@Overridepublic int compare(Student2 o1, Student2 o2) {return o1.getAge() - o2.getAge();}};);
做差结果与升学或降序的对应关系
不管是jdk自带比较器还是自定义比较器,使对应类具备可比较的特性之后,多用于对类的集合进行排序。其中,排序结果和做差的顺序有关。
在内部比较器中,待比较双方是 1)当前所在类的this实例 ;2)外部传入的同类型实例,即
int compareTo(T o)
方法的参数o在外部比较器中,待比较双方是
int compare(T o1, T o2)
的两个参数o1、o2;
为方便记忆,将内部比较器的this 和外部比较器的o1作为基准,如果他们作为被减数,即位于减号左边,那排序结果为升序排序,否则为降序排序。
升学排序
//内部比较器 基准作为被减数 public int compareTo(Test o) {return this.i - o.i; }//外部比较器 o1作为被减数public int compare(int o1, int o2) {return o1 - o2; }
降序排序
//内部比较器 this作为减数 public int compareTo(Test o) {return o.i - this.i ; }//外部比较器 o1作为减数public int compare(int o1, int o2) {return o2 - o1; }
基本数据类型的比较器
上文描述的两种比较器都是基于类实现,因此对于基本数据类型,需要先将其转换为对应的包装类,然后使用对应的比较器。JDK中默认会将基本数据类型装箱,然后使用升序逻辑的比较算法。
int类型
由于两个int类型数值的差值已经是一个整数,不必再进行转换,因此其比较大小的实现不必使用上文的两种比较器,而是直接做差即可.
public int compareTest(int a, int b){return a - b; }
String类型
字符串不是数字,不能直接做差返回一个整数,因此需要借助比较器实现字符串的比较。jdk默认使用内部比较器,即实现Comparable接口。其比较逻辑基于字符串的字符的Unicode值,按照顺序逐个对比字符串的字符unicode值大小。以下为String类的比较逻辑实现
// anotherString为待比较的字符串 public int compareTo(String anotherString) {//value为当前字符串对应的字符数组int len1 = value.length;//获取另一个字符串的字符数组int len2 = anotherString.value.length;//获取较短字符串的长度int lim = Math.min(len1, len2);//另存字符数组引用char v1[] = value;//另存字符数组引用char v2[] = anotherString.value;//遍历两个字符数组//由于字符和int可以互换,因此将两个字符做差等同于int型做差,//返回差值int k = 0;while (k < lim) {char c1 = v1[k];char c2 = v2[k];if (c1 != c2) {//使用this作为被减数,因此排序是升序return c1 - c2;}k++;}//若已遍历完较短的字符串后,仍无法判断大小,则根据长度判断,较长的更大。//使用this作为被减数,因此排序是升序return len1 - len2;}/*** String内部比较器 *this作为被减数,若排序,则结果为升序*/static void stringTest(){String a = "aaaa";String b = "bbbb";String d = "dddd";//两个字符串比较System.out.println(a.compareTo(b)); //-1. 底层执行'a' - 'b',等价于97 - 98System.out.println(a.compareTo(d));//-3. 底层执行‘a’ - 'd',等价于97 - 100//字符串集合排序String[] ss = new String[5];ss[0] = "abc";ss[1] = "xxxxx";ss[2] = "bcdeeee";ss[3] = "bc";ss[4] = "dfff";Arrays.sort(ss);for(String s : ss){System.out.println(s); // abc bc bcdeeee dfff xxxxx }}
double类型
两double数据做差,其结果仍是double类型。若将结果强制类型转换为int型,会丢失小数部分,其结果不再适用于判断算法。此时可以借助其包装类型Double。
在排序时。jdk默认会为基本数据类型装箱,并使用升序排序。特别地,包装类提供了两个比较方法1)静态方法
static int compare(double d1, double d2)
;2)重写comparable接口方法Double.compareTo(Double d)
,该方法底层仍是调用静态方法compare,在使用时建议使用compare()方法。/*** 基本数据类型使用其对应的包装类,* jdk默认提供內部比较器,并自动装箱*/static void doubleTest(){double a = 1.2;double b = 1.8;int c = (int)(a - b);//0.按照算法逻辑可得 a 等于 b,实际上是 a < bSystem.out.println(c);//两个doule比较//Double的内部比较器System.out.println(((Double) a).compareTo((Double) b));//-1//Double的静态方法System.out.println(Double.compare((Double) a, (Double) b));//-1//double集合排序double[] ds = new double[5];ds[0] = 1.7;ds[1] = 1.3;ds[2] = 1.9;ds[3] = 1.5;ds[4] = 1.6;Arrays.sort(ds);for(double d : ds){System.out.println(d);// 1.3 1.5 1.6 1.7 1.9}}
其他基本数据类型同double类似
延申
- 将double类型强转为Double类型,然后调用Double的方法,需要三个括号。
((Double)(this.height))
参考资料
- Unicode,ASCII,UTF-8的区别
java的两种比较器相关推荐
- Java多线程两种实现方式的对比
Java多线程两种实现方式的对比 一种,直接继承Thread类 一种,实现Thread类的Runnable接口 两种方式的区别 比如,售票厅有四个窗口,可以发售某日某次列出的100张车票,此时,100 ...
- java的两种运行方式Applet和Application你真的懂吗
对两者的简介 他们是java的两种程序,能够独立运行的程序称为Java应用程序也包含我们正常写的java文件所生成的可执行程序(Application)其运行和普通的java文件相同.Java语言还有 ...
- 十进制转二进制,用java的两种基本方法,适合新手
十进制转二进制,用java的两种基本方法,适合新手 1.String字符串拼接法 package cn.sxt;import java.util.Scanner;/*** 6. 从键盘输入某个十进制整 ...
- java中两种异常类型_Java中的三种异常类型
java中两种异常类型 Errors are the bane of users and programmers alike. Developers obviously don't want thei ...
- Java实现两种方式 RSA签名, RSA签名校验
Java实现两种方式 RSA签名, RSA签名校验 通过 .keystore密钥文件实现 生成密钥文件 test2.keystore 相关使用 通过密钥生成器实现 Byte数据转换成 Hex字符串 相 ...
- 分析Java的两种数据类型
一.Java的两种数据类型 1.基本数据类型:四类八种 整数型:byte short int long 浮点数型:float double 布尔型:boolean 字符型:char 2.引用数据类型: ...
- Java中两种抛出异常的方式
Java中两种抛出异常的方式 在Java中有两种抛出异常的方式,一种是throw,直接抛出异常,另一种是throws,间接抛出异常. 直接抛出异常是在方法中用关键字throw引发明确的异常.当thro ...
- java提供两种处理异常的机制_浅析Java异常处理机制
关于异常处理的文章已有相当的篇幅,本文简单总结了Java的异常处理机制,并结合代码分析了一些异常处理的最佳实践,对异常的性能开销进行了简单分析. 博客另一篇文章<[译]Java异常处理的最佳实践 ...
- java的两种运行机制_Java☞JVM工作原理
参考博客:1 2 3 JVM工作原理 java虚拟机体系结构 Java平台由Java虚拟机和Java应用程序接口搭建,Java语言则是进入这个平台的通道,用Java语言编写并编译的程序可以运行在这个平 ...
最新文章
- C#调用TSC条码打印机打印二维码(转)
- Spring-AOP实现的两种方式
- 数据结构(严蔚敏)之六——链式队列c语言实现
- 使用SSL和Spring Security保护Tomcat应用程序的安全
- python基于值的内存管理模式_为什么说python采用的是基于值的内存管理模式
- 三大运营商回应“提速降费”:认真落实各项要求
- loopback-detection(环路检测)
- 计算机win7截长屏,怎么用截图工具截比电脑屏幕长的图片?-WIN7截长图,win7怎么滚动截长图...
- 《数值分析》-- 复化求积公式
- cogs 2. 旅行计划
- Vbox虚拟机无法启动错误Mark
- 【Python黑科技】孤独的程序员和AI机器人朋友聊天解闷(免费接口+保姆级图文+实现代码注释)
- 【TensorFlow】使用slim从ckpt里导出指定层的参数
- html5页面 学生作品,最完整长页面H5制作教程来啦!
- php的qq邮箱正则表达式语法_正则表达式综合应用:qq邮箱提取
- “大牌”纷纷入驻智能家居,小米能否守住高地?
- Python实现Excel办公自动化
- Robolectric单元测试 - Android Could not determine artifacts for XXXX: Skipped due to earlier error
- 2021年PMP®考试时间报名时间
- 极点五笔/xshell远程/桌面or命令启动/配制打印机/