跳到主要内容

第17章-2-比较器

概述

  • 在实际业务中,经常会需要进行各种数据进行两两比较,如甲比乙高、丙比丁重等
  • 同时,也会使用这种两两比较来实现排序,如产品销量排行榜、库存数量排行、从高到矮排序等
  • Java语言中,针对这种两两比较,提供了相应的支持
    • 基本数据类型(除boolean类型)外,使用关系运算符直接进行两两比较
    • 引用数据类型,使用比较器进行两两比较
      • Java自带类,多已实现比较器相应方法,如包装类
      • 自定义类,需要自己实现比较器方法进行,如进行员工比较、订单比较、产品数量排行
  • 比较器,主要通过2个接口,来规范两两比较逻辑,分别Comparable、Comparator
  • 这两个比较接口,也被用于集合工具Collections类中对集合进行排序

Comparable接口

概述
  • 提供compareTo抽象方法,进行当前对象和另一个对象的比较,并对int类型返回值约定如下(升序,降序则反过来)

    • 正数,当前对象大于被比较对象,一般使用1
    • 负数,当前对象小于被比较对象,一般使用-1
    • 0,当前对象等于被比较对象
  • 使用此接口进行的排序,俗称自然排序

  • 接口源码如下:

    package java.lang;

    public interface Comparable<T> {
    public int compareTo(T o);
    }
  • 实现业务比较,需要进行两两比较的自定义类,必须实现Comparable接口并覆盖compareTo方法,具体比较逻辑按业务需要实现即可

应用实例
  • 应用实例1,基本使用

    • 自定义业务类,Employee类,实现Comparable接口

      package com.bjpowernode.demo;

      /**
      * 员工类,实现Comparable接口,覆盖其compareTo方法
      */
      public class Employee implements Comparable <Employee> {
      private Integer id;
      private String name;
      private Character sex;
      private Integer age;
      private Boolean isLeader;

      /**
      * 当前对象与另外一个对象进行比较
      *
      * @param o the object to be compared.
      * @return
      */
      @Override
      public int compareTo(Employee o) {
      //方式1
      if (this.id.intValue() > o.id.intValue()) {
      return 1;
      } else if (this.id.intValue() < o.id.intValue()) {
      return -1;
      } else {
      return 0;
      }

      //方式2
      // return this.id-o.id;
      }

      public Employee() {
      }

      public Employee(Integer id, String name, Character sex, Integer age, Boolean isLeader) {
      this.id = id;
      this.name = name;
      this.sex = sex;
      this.age = age;
      this.isLeader = isLeader;
      }

      public Integer getId() {
      return id;
      }

      public void setId(Integer id) {
      this.id = id;
      }

      public String getName() {
      return name;
      }

      public void setName(String name) {
      this.name = name;
      }

      public Character getSex() {
      return sex;
      }

      public void setSex(Character sex) {
      this.sex = sex;
      }

      public Integer getAge() {
      return age;
      }

      public void setAge(Integer age) {
      this.age = age;
      }

      public Boolean getLeader() {
      return isLeader;
      }

      public void setLeader(Boolean leader) {
      isLeader = leader;
      }

      @Override
      public String toString() {
      return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", sex=" + sex + ", age=" + age + ", isLeader=" + isLeader + '}';
      }
      }
    • 自定义类对象进行比较

      package com.bjpowernode.demo;

      public class EmployeeTest {
      public static void main(String[] args) {
      //员工1
      Employee e1 = new Employee(11, "张三", '男', 25, true);
      //员工2
      Employee e2 = new Employee(13, "李四", '女', 27, false);

      //比较两个员工
      int result = e1.compareTo(e2);
      System.out.println("比较结果:" + result);
      }
      }
  • 应用实例2,进行自然排序

    • 自定义业务类,Employee类,应用实例1中的此类,此处略

    • 排序工具方法,使用冒泡排序

      package com.bjpowernode.demo;

      /**
      * 排序工具
      */
      public class SortUtil {
      /**
      * 冒泡排序,基于Comparable接口实现类进行
      * @param datas
      * @param <T>
      */
      public static <T extends Comparable> void sort(T[] datas) {
      //外层循环,控制排序轮数
      for (int i = 0; i < datas.length; i++) {
      //内层循环,控制当前轮比较次数
      for (int j = 0; j < datas.length - 1 - i; j++) {
      //两两数据比较,使用compareTo方法
      if (datas[j].compareTo(datas[j + 1]) > 0) {
      //交换数据
      T temp = datas[j];
      datas[j] = datas[j + 1];
      datas[j + 1] = temp;
      }
      }
      }
      }
      }
    • 自定义类对象数组进行排序

      package com.bjpowernode.demo;

      public class EmployeeTest {
      public static void main(String[] args) {
      //员工数组
      Employee[] employees = new Employee[5];
      Employee e1 = new Employee(11, "张三", '男', 25, true);
      Employee e2 = new Employee(13, "李四", '女', 27, false);
      Employee e3 = new Employee(5, "王五", '女', 18, false);
      Employee e4 = new Employee(3, "赵六", '男', 26, true);
      Employee e5 = new Employee(9, "田七", '女', 33, false);
      employees[0] = e1;
      employees[1] = e2;
      employees[2] = e3;
      employees[3] = e4;
      employees[4] = e5;

      //排序前
      System.out.println("-------------排序前-------------");
      for(Employee employee:employees){
      System.out.println(employee);
      }
      //排序
      SortUtil.sort(employees);
      //排序后
      System.out.println("-------------排序后-------------");
      for(Employee employee:employees){
      System.out.println(employee);
      }
      }
      }
局限性
  • 上面实例中的Employee类,一旦定义,一般不会做修改,也就是固定只能支持一种排序,灵活度不够;比如想进行按年龄进行排序、按id进行降序排序等
  • Java中的一些类的排序,也是固定的升序,如包装类,也同样有相应局限性

【练习】

  1. 练习应用实例内容,完成代码编写
  2. 尝试使用其他属性进行自然排序,如姓名、性别、年龄等

Comparator接口

概述
  • 提供compare抽象方法,进行两个对象的比较,并对返回值约定如下(升序,降序则反过来)

    • 正数,前一个对象大于后一个对象,一般使用1
    • 负数,前一个小于后一个对象,一般使用-1
    • 0,前一个对象等于后一个对象
  • 使用此接口进行的排序,俗称定制排序

  • 接口部分源码如下:

    package java.lang;

    public interface Comparator<T> {
    int compare(T o1, T o2);
    }
  • 实现业务比较,需要进行两两比较的自定义类,单独定义一个比较类或使用匿名类实现Comparator接口并覆盖compare方法,具体比较逻辑按业务需要实现即可

应用实例
  • 应用实例1,使用单独定义比较类进行比较,不推荐

    • 自定义业务类,Employee类

      package com.bjpowernode.demo;

      /**
      * 员工类
      */
      public class Employee {
      private Integer id;
      private String name;
      private Character sex;
      private Integer age;
      private Boolean isLeader;

      public Employee() {
      }

      public Employee(Integer id, String name, Character sex, Integer age, Boolean isLeader) {
      this.id = id;
      this.name = name;
      this.sex = sex;
      this.age = age;
      this.isLeader = isLeader;
      }

      public Integer getId() {
      return id;
      }

      public void setId(Integer id) {
      this.id = id;
      }

      public String getName() {
      return name;
      }

      public void setName(String name) {
      this.name = name;
      }

      public Character getSex() {
      return sex;
      }

      public void setSex(Character sex) {
      this.sex = sex;
      }

      public Integer getAge() {
      return age;
      }

      public void setAge(Integer age) {
      this.age = age;
      }

      public Boolean getLeader() {
      return isLeader;
      }

      public void setLeader(Boolean leader) {
      isLeader = leader;
      }

      @Override
      public String toString() {
      return "Employee{" + "id=" + id + ", name='" + name + '\'' + ", sex=" + sex + ", age=" + age + ", isLeader=" + isLeader + '}';
      }
      }
    • 定义比较类

      package com.bjpowernode.demo;

      import java.util.Comparator;

      /**
      * 员工比较类,实现Comparator接口,覆盖compare抽象方法
      * 可灵活使用Employee的任一属性进行排序
      */
      public class EmployeeComarator implements Comparator <Employee> {
      /**
      * 两两比较方法,按年龄进行比较
      * @param o1 the first object to be compared.
      * @param o2 the second object to be compared.
      * @return
      */
      @Override
      public int compare(Employee o1, Employee o2) {
      if (o1.getAge().intValue() > o2.getAge().intValue()) {
      return 1;
      } else if (o1.getAge().intValue() < o2.getAge().intValue()) {
      return -1;
      } else {
      return 0;
      }
      }
      }
    • 自定义类对象进行比较

      package com.bjpowernode.demo;

      import java.util.Comparator;

      public class EmployeeTest {
      public static void main(String[] args) {
      //员工1
      Employee e1 = new Employee(11, "张三", '男', 25, true);
      //员工2
      Employee e2 = new Employee(13, "李四", '女', 27, false);

      //比较两个员工
      Comparator comparator = new EmployeeComarator();
      int result = comparator.compare(e1, e2);
      System.out.println("比较结果:" + result);
      }
      }
  • 应用实例2,使用匿名比较类进行比较,推荐

    • 自定义业务类,Employee类,应用实例1中的此类,此处略

    • 自定义类对象进行比较,使用匿名比较类

      package com.bjpowernode.demo;

      import java.util.Comparator;

      public class EmployeeTest {
      public static void main(String[] args) {
      //员工1
      Employee e1 = new Employee(11, "张三", '男', 25, true);
      //员工2
      Employee e2 = new Employee(13, "李四", '女', 27, false);

      //比较两个员工
      Comparator <Employee> comparator = new Comparator <Employee>() {
      @Override
      public int compare(Employee o1, Employee o2) {
      if (o1.getAge().intValue() > o2.getAge().intValue()) {
      return 1;
      } else if (o1.getAge().intValue() < o2.getAge().intValue()) {
      return -1;
      } else {
      return 0;
      }
      }
      };
      int result = comparator.compare(e1, e2);
      System.out.println("比较结果:" + result);
      }
      }
  • 应用实例3,进行定制排序

    • 自定义业务类,Employee类,应用实例1中的此类,此处略

    • 排序工具,使用冒泡排序

      package com.bjpowernode.demo;

      import java.util.Comparator;

      /**
      * 排序工具
      */
      public class SortUtil {
      /**
      * 冒泡排序,通过传递Comparator比较器实现
      *
      * @param datas 需排序的数组
      * @param comparator Comparator比较对象
      */
      public static <T> void sort(T[] datas, Comparator <T> comparator) {
      //外层循环,控制排序轮数
      for (int i = 0; i < datas.length; i++) {
      //内层循环,控制当前轮比较次数
      for (int j = 0; j < datas.length - 1 - i; j++) {
      //两两数据比较,使用compareTo方法
      if (comparator.compare(datas[j], datas[j + 1]) > 0) {
      //交换数据
      T temp = datas[j];
      datas[j] = datas[j + 1];
      datas[j + 1] = temp;
      }
      }
      }
      }
      }
  • 自定义类对象数组进行排序

      package com.bjpowernode.demo;

    import java.util.Comparator;

    public class EmployeeTest {
    public static void main(String[] args) {
    //员工数组
    Employee[] employees = new Employee[5];
    Employee e1 = new Employee(11, "张三", '男', 25, true);
    Employee e2 = new Employee(13, "李四", '女', 27, false);
    Employee e3 = new Employee(5, "王五", '女', 18, false);
    Employee e4 = new Employee(3, "赵六", '男', 26, true);
    Employee e5 = new Employee(9, "田七", '女', 33, false);
    employees[0] = e1;
    employees[1] = e2;
    employees[2] = e3;
    employees[3] = e4;
    employees[4] = e5;

    //排序前
    System.out.println("-------------排序前-------------");
    for (Employee employee : employees) {
    System.out.println(employee);
    }
    //比较器对象
    Comparator <Employee> comparator = new Comparator <Employee>() {
    @Override
    public int compare(Employee o1, Employee o2) {
    if (o1.getAge().intValue() > o2.getAge().intValue()) {
    return 1;
    } else if (o1.getAge().intValue() < o2.getAge().intValue()) {
    return -1;
    } else {
    return 0;
    }
    }
    };
    //排序
    SortUtil.sort(employees, comparator);
    //排序后
    System.out.println("-------------排序后-------------");
    for (Employee employee : employees) {
    System.out.println(employee);
    }
    }
    }
优点
  • 在对象比较的逻辑方面,与Comparable方式类似,但由于无需在类定义时实现接口并覆盖方法,更灵活,对原业务类也无侵入
  • 结合匿名类应用,能对排序的属性、排序方向(升序、降序)进行更灵活的处理

【练习】

  1. 练习应用实例内容,完成代码编写
  2. 尝试使用其他属性进行定制排序,如姓名、性别、年龄等

使用equals比较

概述
  • 属于Object类的方法
  • 多用于两个对象间按实际业务逻辑进行比较,判断业务上是否相等
  • 需要结合hashCode方法一起使用,如果业务上相等,返回的hashCode值也必须一样,一般表现为
    • 通过equals()方法比较相等的两个对象(返回结果为true),通过hashCode()返回的整数要保证一定相同
    • 通过hashCode()返回的整数相同的两个对象,通过equals()方法比较并不一定相等(大概率相等),主要是哈希冲突问题
  • 根据上述的原则,就能保证在一些工具中(如HashMap)使用equals()比较相等的情况下,通过使用hashCode()方法计算的存储位置才能保证相同
  • 覆盖Object类的euqals方法和hashCode方法,可通过右键generate...菜单中的equals() and hashCode()子菜单自动实现
Objects工具类
  • 主要用其根据数据生成int类型的hashCode值

  • 应用实例

    • 应用实例1,生成hashCode

      package com.bjpowernode.demo;

      import java.util.Objects;

      /**
      * Objects工具类的使用,生成hashCode
      */
      public class ObjectsDemo {
      public static void main(String[] args) {
      //根据int类型生成hashCode
      int result1 = Objects.hash(1);
      System.out.println(result1);

      //根据boolean类型生成hashCode
      int result2 = Objects.hash(true);
      System.out.println(result2);

      //根据字符串生成hashCode
      int result3 = Objects.hash("abc");
      System.out.println(result3);

      //组合生成hashCode
      int result4 = Objects.hash(31,true,"abc");
      System.out.println(result4);
      }
      }
应用实例
  • 应用实例1,不覆盖equals方法的比较效果

    • 学生类

      package com.bjpowernode.demo;

      /**
      * 学生类
      */
      public class Student {
      private Integer id;
      private String name;

      public Student(Integer id, String name) {
      this.id = id;
      this.name = name;
      }
      }
    • 学生类对象比较 ,结果为false

      package com.bjpowernode.demo;

      public class StudentTest {
      public static void main(String[] args) {
      //学生1,业务上来说,与学生2相等
      Student s1 = new Student(1, "张三");
      //学生2,业务上来说,与学生1相等
      Student s2 = new Student(1, "张三");

      //使用equals比较
      System.out.println(s1.equals(s2)); //结果为false
      }
      }
  • 应用实例2,覆盖equals方法的比较效果

    • 学生类

      package com.bjpowernode.demo;

      import java.util.Objects;

      /**
      * 学生类
      */
      public class Student {
      private Integer id;
      private String name;

      public Student(Integer id, String name) {
      this.id = id;
      this.name = name;
      }

      /**
      * 覆盖Object类的equals方法,实现按业务逻辑比较
      * @param o 被比较对象
      * @return 是否相等
      */
      @Override
      public boolean equals(Object o) {
      //如果当前对象与被比较对象使用==比较相等,表示两个引用指向同一个地址,肯定相等,直接返回true
      if (this == o) return true;
      //如果当前对象与被比较对象类型都不相同,则肯定不相等,返回false
      if (o == null || getClass() != o.getClass()) return false;
      //将被比较对象转型为当前对象类型,即Student
      Student student = (Student) o;
      //比较相应属性的值
      return Objects.equals(id, student.id) && Objects.equals(name, student.name);
      }

      /**
      * 覆盖Object类的hashCode方法,保证equals相等时,返回相同的hashCode
      * @return
      */
      @Override
      public int hashCode() {
      return Objects.hash(id, name);
      }
      }
    • 学生类对象比较,结果为true

      package com.bjpowernode.demo;

      public class StudentTest {
      public static void main(String[] args) {
      //学生1,业务上来说,与学生2相等
      Student s1 = new Student(1, "张三");
      //学生2,业务上来说,与学生1相等
      Student s2 = new Student(1, "张三");

      //使用equals比较
      System.out.println(s1.equals(s2)); //结果为false
      }
      }

【练习】

  1. 练习应用实例内容,完成代码编写

实战和作业

  1. 【扩展】重构程序,重构第16章实战和作业的仓库程序,要求如下
    1. 对仓库产品是否相同使用产品编号+产品名称做为唯一标识,并重构入库、出库方法,形式参数为一个产品对象(主要有产品编号、产品名称、产品数量三个值,其中产品数量是本次要入库的数量)
    2. 对仓库产品根据编号进行升序排序,使用自然排序
    3. 对仓库产品根据数量进行降序排序,使用定制排序
    4. 对仓库产品根据总价值进行降序排序,使用定制排序
    5. 在测试类中,构造仓库中的测试产品数据,进行测试