比较算法

日常生活中,如果想比较两个数的大小,可采用做差的方式,做差结果的正负可用来判断两个数的大小。假设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的两种比较器相关推荐

  1. Java多线程两种实现方式的对比

    Java多线程两种实现方式的对比 一种,直接继承Thread类 一种,实现Thread类的Runnable接口 两种方式的区别 比如,售票厅有四个窗口,可以发售某日某次列出的100张车票,此时,100 ...

  2. java的两种运行方式Applet和Application你真的懂吗

    对两者的简介 他们是java的两种程序,能够独立运行的程序称为Java应用程序也包含我们正常写的java文件所生成的可执行程序(Application)其运行和普通的java文件相同.Java语言还有 ...

  3. 十进制转二进制,用java的两种基本方法,适合新手

    十进制转二进制,用java的两种基本方法,适合新手 1.String字符串拼接法 package cn.sxt;import java.util.Scanner;/*** 6. 从键盘输入某个十进制整 ...

  4. java中两种异常类型_Java中的三种异常类型

    java中两种异常类型 Errors are the bane of users and programmers alike. Developers obviously don't want thei ...

  5. Java实现两种方式 RSA签名, RSA签名校验

    Java实现两种方式 RSA签名, RSA签名校验 通过 .keystore密钥文件实现 生成密钥文件 test2.keystore 相关使用 通过密钥生成器实现 Byte数据转换成 Hex字符串 相 ...

  6. 分析Java的两种数据类型

    一.Java的两种数据类型 1.基本数据类型:四类八种 整数型:byte short int long 浮点数型:float double 布尔型:boolean 字符型:char 2.引用数据类型: ...

  7. Java中两种抛出异常的方式

    Java中两种抛出异常的方式 在Java中有两种抛出异常的方式,一种是throw,直接抛出异常,另一种是throws,间接抛出异常. 直接抛出异常是在方法中用关键字throw引发明确的异常.当thro ...

  8. java提供两种处理异常的机制_浅析Java异常处理机制

    关于异常处理的文章已有相当的篇幅,本文简单总结了Java的异常处理机制,并结合代码分析了一些异常处理的最佳实践,对异常的性能开销进行了简单分析. 博客另一篇文章<[译]Java异常处理的最佳实践 ...

  9. java的两种运行机制_Java☞JVM工作原理

    参考博客:1 2 3 JVM工作原理 java虚拟机体系结构 Java平台由Java虚拟机和Java应用程序接口搭建,Java语言则是进入这个平台的通道,用Java语言编写并编译的程序可以运行在这个平 ...

最新文章

  1. C#调用TSC条码打印机打印二维码(转)
  2. Spring-AOP实现的两种方式
  3. 数据结构(严蔚敏)之六——链式队列c语言实现
  4. 使用SSL和Spring Security保护Tomcat应用程序的安全
  5. python基于值的内存管理模式_为什么说python采用的是基于值的内存管理模式
  6. 三大运营商回应“提速降费”:认真落实各项要求
  7. loopback-detection(环路检测)
  8. 计算机win7截长屏,怎么用截图工具截比电脑屏幕长的图片?-WIN7截长图,win7怎么滚动截长图...
  9. 《数值分析》-- 复化求积公式
  10. cogs 2. 旅行计划
  11. Vbox虚拟机无法启动错误Mark
  12. 【Python黑科技】孤独的程序员和AI机器人朋友聊天解闷(免费接口+保姆级图文+实现代码注释)
  13. 【TensorFlow】使用slim从ckpt里导出指定层的参数
  14. html5页面 学生作品,最完整长页面H5制作教程来啦!
  15. php的qq邮箱正则表达式语法_正则表达式综合应用:qq邮箱提取
  16. “大牌”纷纷入驻智能家居,小米能否守住高地?
  17. Python实现Excel办公自动化
  18. Robolectric单元测试 - Android Could not determine artifacts for XXXX: Skipped due to earlier error
  19. 2021年PMP®考试时间报名时间
  20. 极点五笔/xshell远程/桌面or命令启动/配制打印机/

热门文章

  1. 如何开发微信公众号以及如何运营微信公众号
  2. 桌面一直刷新动不了计算机,电脑桌面不会自动刷新怎么办?
  3. [iBoard 电子学堂][第〇卷 电子基础 ]第三篇 单片微控制器、微处理器
  4. 每天六点起床!真的是考研标配么?
  5. Java异常-受查与非受查
  6. espcms5.7.13 sql注入漏洞复现
  7. 用Python实现超级玛丽游戏【示例代码】
  8. 我被39岁阿姨包全天讲代码
  9. 不要在二三十岁时就开始老去
  10. JMF 利用RTPManager进行视频音频流传输聊天