第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中的一些类的排序,也是固定的升序,如包装类,也同样有相应局限性
【练习】
- 练习应用实例内容,完成代码编写
- 尝试使用其他属性进行自然排序,如姓名、性别、年龄等
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方式类似,但由于无需在类定义时实现接口并覆盖方法,更灵活,对原业务类也无侵入
- 结合匿名类应用,能对排序的属性、排序方向(升序、降序)进行更灵活的处理
【练习】
- 练习应用实例内容,完成代码编写
- 尝试使用其他属性进行定制排序,如姓名、性别、年龄等
使用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
}
}
【练习】
- 练习应用实例内容,完成代码编写
实战和作业
- 【扩展】重构程序,重构第16章实战和作业的仓库程序,要求如下
- 对仓库产品是否相同使用产品编号+产品名称做为唯一标识,并重构入库、出库方法,形式参数为一个产品对象(主要有产品编号、产品名称、产品数量三个值,其中产品数量是本次要入库的数量)
- 对仓库产品根据编号进行升序排序,使用自然排序
- 对仓库产品根据数量进行降序排序,使用定制排序
- 对仓库产品根据总价值进行降序排序,使用定制排序
- 在测试类中,构造仓库中的测试产品数据,进行测试