Java对象的比较 - 完整学习指南
📊 知识体系总览
mindmap
root((Java对象的比较))
比较的必要性
基本类型比较
直接使用运算符
简单直接
支持所有运算符
对象比较问题
不能使用运算符
默认比较地址
需要自定义比较
优先级队列需求
比较应用场景
集合排序
优先级队列
二叉搜索树
查找算法
比较的三种方式
默认的equals方法
继承自Object
比较对象地址
覆写实现内容比较
只支持相等性比较
使用规范
覆写步骤
注意事项
Comparable接口
内部比较接口
实现自然排序
侵入性较强
实现步骤
返回值含义
使用场景
注意事项
Comparator接口
外部比较器
策略模式
灵活性高
实现步骤
多种比较器
匿名内部类
Lambda表达式
使用场景
注意事项
三种方式对比
实现位置
内部 vs 外部
侵入性差异
灵活性对比
使用场景
默认排序
多种排序
临时比较
性能考虑
内存开销
创建成本
缓存优化
最佳实践
何时用Comparable
何时用Comparator
如何选择
组合使用
PriorityQueue的比较
底层比较机制
内部比较器字段
构造方法差异
向上调整实现
向下调整实现
使用方式
默认Comparable
传入Comparator
匿名内部类
Lambda表达式
大小堆创建
小根堆:默认
大根堆:自定义比较器
复杂比较器
多重条件排序
注意事项
元素必须可比较
不能插入null
比较一致性
线程安全问题
Top-K问题
问题描述
最大K个元素
最小K个元素
海量数据处理
实时更新
解决方案
排序法
堆解法
快速选择
分治法
堆解法实现
最小K:大根堆
最大K:小根堆
建堆策略
更新策略
时间复杂度
空间复杂度
优化技巧
预分配空间
批量处理
并行计算
内存映射
常见错误
空指针异常
比较不一致
违反约定
性能问题
内存泄漏
1. 比较的必要性
1.1 基本类型 vs 对象比较
graph TD
A[Java比较机制] --> B[基本类型比较]
A --> C[对象比较]
B --> B1[使用运算符]
B --> B2[直接比较值]
B --> B3[支持所有运算符]
B --> B4[简单直接]
C --> C1[不能使用运算符]
C --> C2[默认比较地址]
C --> C3[需要自定义比较]
C --> C4[多种比较方式]
B1 --> B11[> < >= <=]
B1 --> B12[== !=]
B1 --> B13[自动类型转换]
C3 --> C31[覆写equals]
C3 --> C32[实现Comparable]
C3 --> C33[使用Comparator]
C4 --> C41[相等性比较]
C4 --> C42[大小比较]
C4 --> C43[多重排序]
C4 --> C44[临时比较]
代码示例:
// 基本类型比较
class PrimitiveComparison {
public static void main(String[] args) {
System.out.println("=== 基本类型比较 ===");
int a = 10;
int b = 20;
int c = 10;
// 可以直接使用比较运算符
System.out.println("a > b: " + (a > b)); // false
System.out.println("a < b: " + (a < b)); // true
System.out.println("a == b: " + (a == b)); // false
System.out.println("a == c: " + (a == c)); // true
char c1 = 'A';
char c2 = 'B';
System.out.println("c1 < c2: " + (c1 < c2)); // true
double d1 = 3.14;
double d2 = 2.71;
System.out.println("d1 >= d2: " + (d1 >= d2)); // true
// 不同类型可以自动转换
int i = 10;
double d = 10.0;
System.out.println("i == d: " + (i == d)); // true
}
}
// 对象比较问题
class ObjectComparisonProblem {
static class Person {
String name;
int age;
Person(String name, int age) {
this.name = name;
this.age = age;
}
}
public static void main(String[] args) {
System.out.println("\n=== 对象比较问题 ===");
Person p1 = new Person("张三", 20);
Person p2 = new Person("张三", 20);
Person p3 = p1;
// 以下代码会编译错误
// System.out.println("p1 > p2: " + (p1 > p2)); // 编译错误
// System.out.println("p1 < p2: " + (p1 < p2)); // 编译错误
// == 比较的是引用地址
System.out.println("p1 == p2: " + (p1 == p2)); // false,不是同一个对象
System.out.println("p1 == p3: " + (p1 == p3)); // true,是同一个对象
// 对象的默认equals方法比较的也是地址
System.out.println("p1.equals(p2): " + p1.equals(p2)); // false
System.out.println("p1.equals(p3): " + p1.equals(p3)); // true
// 问题:我们通常想要比较的是内容,而不是地址
}
}
注意易错点:
-
对象不能使用
>、<等比较运算符 -
==比较的是对象地址,不是内容 -
默认的
equals()方法比较的也是地址 -
需要根据实际需求实现自定义比较
1.2 比较的应用场景
// 比较的应用场景
class ComparisonUseCases {
public static void main(String[] args) {
System.out.println("=== 比较的应用场景 ===");
// 1. 集合排序
System.out.println("\n1. 集合排序:");
java.util.List<Integer> list = java.util.Arrays.asList(5, 2, 8, 1, 3);
java.util.Collections.sort(list);
System.out.println("排序后: " + list);
// 2. 优先级队列
System.out.println("\n2. 优先级队列:");
java.util.PriorityQueue<Integer> pq = new java.util.PriorityQueue<>();
pq.offer(5);
pq.offer(2);
pq.offer(8);
pq.offer(1);
pq.offer(3);
System.out.print("出队顺序: ");
while (!pq.isEmpty()) {
System.out.print(pq.poll() + " ");
}
System.out.println();
// 3. 二叉搜索树
System.out.println("\n3. 二叉搜索树:");
java.util.TreeSet<Integer> treeSet = new java.util.TreeSet<>();
treeSet.add(5);
treeSet.add(2);
treeSet.add(8);
treeSet.add(1);
treeSet.add(3);
System.out.println("有序集合: " + treeSet);
// 4. 查找算法
System.out.println("\n4. 二分查找:");
java.util.Arrays.sort(list.toArray());
int index = java.util.Collections.binarySearch(list, 3);
System.out.println("3的位置: " + index);
// 5. 最大值/最小值
System.out.println("\n5. 最大值/最小值:");
java.util.List<Integer> nums = java.util.Arrays.asList(5, 2, 8, 1, 3);
Integer max = java.util.Collections.max(nums);
Integer min = java.util.Collections.min(nums);
System.out.println("最大值: " + max + ", 最小值: " + min);
}
}
2. 三种比较方式
2.1 覆写equals方法
graph TD
A[覆写equals方法] --> B[核心目的]
A --> C[实现步骤]
A --> D[注意事项]
A --> E[使用场景]
B --> B1[比较对象内容]
B --> B2[实现相等性比较]
B --> B3[不比较大小]
C --> C1[检查是否为同一对象]
C --> C2[检查是否为null]
C --> C3[检查类型]
C --> C4[比较内容]
C --> C5[比较所有字段]
D --> D1[同时覆写hashCode]
D --> D2[保持一致性]
D --> D3[处理null值]
D --> D4[对称性传递性]
D --> D5[性能考虑]
E --> E1[集合contains]
E --> E2[Map键查找]
E --> E3[对象相等判断]
E --> E4[删除重复元素]
代码示例:
// 正确覆写equals方法的步骤
class Person {
private String name;
private int age;
private String idCard; // 身份证号
public Person(String name, int age, String idCard) {
this.name = name;
this.age = age;
this.idCard = idCard;
}
// 1. 覆写equals方法
@Override
public boolean equals(Object obj) {
// 步骤1: 检查是否为同一对象
if (this == obj) {
return true;
}
// 步骤2: 检查是否为null
if (obj == null) {
return false;
}
// 步骤3: 检查类型
if (getClass() != obj.getClass()) {
return false;
}
// 步骤4: 类型转换
Person other = (Person) obj;
// 步骤5: 比较所有重要字段
// 注意:基本类型用==,对象用equals,注意处理null
// 身份证号是唯一标识
if (idCard == null) {
if (other.idCard != null) {
return false;
}
} else if (!idCard.equals(other.idCard)) {
return false;
}
// 其他字段可选择性比较
if (age != other.age) {
return false;
}
if (name == null) {
if (other.name != null) {
return false;
}
} else if (!name.equals(other.name)) {
return false;
}
return true;
}
// 2. 必须同时覆写hashCode方法
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + age;
result = prime * result + ((idCard == null) ? 0 : idCard.hashCode());
result = prime * result + ((name == null) ? 0 : name.hashCode());
return result;
}
// 3. 通常也会覆写toString方法
@Override
public String toString() {
return "Person[name=" + name + ", age=" + age + ", idCard=" + idCard + "]";
}
}
// equals方法测试
class EqualsTest {
public static void main(String[] args) {
System.out.println("=== equals方法测试 ===");
Person p1 = new Person("张三", 20, "110101199001011234");
Person p2 = new Person("张三", 20, "110101199001011234");
Person p3 = new Person("李四", 20, "110101199001011235");
Person p4 = p1;
Person p5 = null;
// 测试equals
System.out.println("p1.equals(p2): " + p1.equals(p2)); // true
System.out.println("p1.equals(p3): " + p1.equals(p3)); // false
System.out.println("p1.equals(p4): " + p1.equals(p4)); // true
System.out.println("p1.equals(p5): " + p1.equals(p5)); // false
// 测试==
System.out.println("p1 == p2: " + (p1 == p2)); // false
System.out.println("p1 == p4: " + (p1 == p4)); // true
// 测试hashCode
System.out.println("p1.hashCode() == p2.hashCode(): " +
(p1.hashCode() == p2.hashCode())); // 应该为true
// 测试在集合中的使用
java.util.Set<Person> set = new java.util.HashSet<>();
set.add(p1);
set.add(p2); // 由于equals返回true,这个不会被添加
set.add(p3);
System.out.println("集合大小: " + set.size()); // 2
// 测试contains
Person p6 = new Person("张三", 20, "110101199001011234");
System.out.println("集合包含p6: " + set.contains(p6)); // true
}
}
// equals方法的常见错误
class EqualsCommonErrors {
static class WrongPerson {
String name;
int age;
public WrongPerson(String name, int age) {
this.name = name;
this.age = age;
}
// 错误1: 没有检查类型
public boolean equals(WrongPerson other) { // 这不是覆写!
return this.name.equals(other.name) && this.age == other.age;
}
// 错误2: 没有覆写hashCode
// 错误3: 违反了对称性
// 错误4: 没有处理null
}
public static void main(String[] args) {
System.out.println("\n=== equals常见错误 ===");
WrongPerson wp1 = new WrongPerson("张三", 20);
WrongPerson wp2 = new WrongPerson("张三", 20);
Object obj = wp2;
System.out.println("wp1.equals(wp2): " + wp1.equals(wp2)); // true
System.out.println("wp1.equals(obj): " + wp1.equals(obj)); // false! 应该是true
// 正确的equals方法签名应该是:
// public boolean equals(Object obj)
}
}
equals方法规范:
-
自反性:x.equals(x) 必须返回 true
-
对称性:如果 x.equals(y) 返回 true,那么 y.equals(x) 也必须返回 true
-
传递性:如果 x.equals(y) 返回 true,y.equals(z) 返回 true,那么 x.equals(z) 必须返回 true
-
一致性:只要对象没有被修改,多次调用 equals 应该返回相同结果
-
非空性:x.equals(null) 必须返回 false
-
必须覆写 hashCode:如果两个对象 equals 返回 true,它们的 hashCode 必须相等
2.2 实现Comparable接口
classDiagram
class Comparable~T~ {
<<interface>>
+compareTo(T o) int
}
class Student {
-String name
-int score
-int age
+Student(String, int, int)
+compareTo(Student) int
+toString() String
}
class Product {
-String name
-double price
-int stock
+Product(String, double, int)
+compareTo(Product) int
+toString() String
}
Comparable~T~ <|.. Student
Comparable~T~ <|.. Product
代码示例:
// Comparable接口实现
class Student implements Comparable<Student> {
private String name;
private int score;
private int age;
public Student(String name, int score, int age) {
this.name = name;
this.score = score;
this.age = age;
}
// 实现Comparable接口
@Override
public int compareTo(Student other) {
if (other == null) {
return 1; // 非空对象大于null
}
// 主要按分数降序排列
if (this.score != other.score) {
return other.score - this.score; // 分数高的在前
}
// 分数相同按年龄升序
if (this.age != other.age) {
return this.age - other.age;
}
// 年龄相同按姓名升序
return this.name.compareTo(other.name);
}
@Override
public String toString() {
return name + "(" + score + ", " + age + ")";
}
// 通常也会覆写equals和hashCode
@Override
public boolean equals(Object obj) {
if (this == obj) return true;
if (obj == null || getClass() != obj.getClass()) return false;
Student student = (Student) obj;
return score == student.score &&
age == student.age &&
name.equals(student.name);
}
@Override
public int hashCode() {
int result = name.hashCode();
result = 31 * result + score;
result = 31 * result + age;
return result;
}
}
// Comparable使用测试
class ComparableTest {
public static void main(String[] args) {
System.out.println("=== Comparable接口测试 ===");
// 创建学生对象
Student s1 = new Student("张三", 85, 20);
Student s2 = new Student("李四", 92, 19);
Student s3 = new Student("王五", 85, 21);
Student s4 = new Student("赵六", 85, 20);
Student s5 = null;
// 测试compareTo
System.out.println("s1.compareTo(s2): " + s1.compareTo(s2)); // >0, s1分数低
System.out.println("s2.compareTo(s1): " + s2.compareTo(s1)); // <0, s2分数高
System.out.println("s1.compareTo(s3): " + s1.compareTo(s3)); // >0, 年龄s1小
System.out.println("s1.compareTo(s4): " + s1.compareTo(s4)); // 0, 相等
// System.out.println("s1.compareTo(s5): " + s1.compareTo(s5)); // 1
// 测试排序
java.util.List<Student> students = new java.util.ArrayList<>();
students.add(s1);
students.add(s2);
students.add(s3);
students.add(new Student("钱七", 78, 22));
System.out.println("\n排序前:");
for (Student s : students) {
System.out.println(" " + s);
}
java.util.Collections.sort(students);
System.out.println("\n排序后(按分数降序,年龄升序,姓名升序):");
for (Student s : students) {
System.out.println(" " + s);
}
// 测试在TreeSet中使用
System.out.println("\n=== TreeSet自动排序 ===");
java.util.TreeSet<Student> treeSet = new java.util.TreeSet<>();
treeSet.add(new Student("AA", 80, 20));
treeSet.add(new Student("BB", 90, 19));
treeSet.add(new Student("CC", 80, 21));
treeSet.add(new Student("DD", 85, 20));
for (Student s : treeSet) {
System.out.println(" " + s);
}
// 测试在PriorityQueue中使用
System.out.println("\n=== PriorityQueue优先级队列 ===");
java.util.PriorityQueue<Student> pq = new java.util.PriorityQueue<>();
pq.offer(new Student("A", 80, 20));
pq.offer(new Student("B", 90, 19));
pq.offer(new Student("C", 70, 22));
pq.offer(new Student("D", 85, 21));
System.out.print("出队顺序: ");
while (!pq.isEmpty()) {
System.out.print(pq.poll() + " ");
}
System.out.println();
}
}
// Comparable接口的返回值含义
class CompareToReturnValue {
public static void main(String[] args) {
System.out.println("=== compareTo返回值含义 ===");
Integer a = 10;
Integer b = 20;
Integer c = 10;
System.out.println("a.compareTo(b): " + a.compareTo(b)); // 负数,a < b
System.out.println("b.compareTo(a): " + b.compareTo(a)); // 正数,b > a
System.out.println("a.compareTo(c): " + a.compareTo(c)); // 0,a == c
String s1 = "apple";
String s2 = "banana";
String s3 = "apple";
System.out.println("s1.compareTo(s2): " + s1.compareTo(s2)); // 负数
System.out.println("s2.compareTo(s1): " + s2.compareTo(s1)); // 正数
System.out.println("s1.compareTo(s3): " + s1.compareTo(s3)); // 0
// 总结:
// 返回值 < 0: 当前对象小于参数对象
// 返回值 = 0: 当前对象等于参数对象
// 返回值 > 0: 当前对象大于参数对象
}
}
Comparable接口要点:
-
实现
Comparable<T>接口 -
重写
compareTo(T o)方法 -
返回值规则:
-
负数:当前对象 < 参数对象
-
零:当前对象 = 参数对象
-
正数:当前对象 > 参数对象
-
-
应该与
equals()保持一致 -
需要考虑 null 值处理
2.3 使用Comparator接口
graph TD
A[Comparator接口] --> B[核心特点]
A --> C[实现方式]
A --> D[使用场景]
A --> E[优势]
B --> B1[外部比较器]
B --> B2[策略模式]
B --> B3[灵活性高]
B --> B4[多种比较器]
C --> C1[实现Comparator类]
C --> C2[匿名内部类]
C --> C3[Lambda表达式]
C --> C4[方法引用]
C --> C5[Comparator.comparing]
D --> D1[无法修改源代码]
D --> D2[多种排序方式]
D --> D3[临时比较]
D --> D4[复杂排序]
D --> D5[第三方库排序]
E --> E1[不修改原有类]
E --> E2[可定义多个比较器]
E --> E3[易于测试]
E --> E4[组合使用]
代码示例:
// 商品类
class Product {
private String name;
private double price;
private int stock;
private String category;
public Product(String name, double price, int stock, String category) {
this.name = name;
this.price = price;
this.stock = stock;
this.category = category;
}
// Getters
public String getName() { return name; }
public double getPrice() { return price; }
public int getStock() { return stock; }
public String getCategory() { return category; }
@Override
public String toString() {
return String.format("%s[¥%.2f, 库存:%d, 分类:%s]",
name, price, stock, category);
}
}
// 方式1:实现Comparator类
class PriceComparator implements java.util.Comparator<Product> {
@Override
public int compare(Product p1, Product p2) {
return Double.compare(p1.getPrice(), p2.getPrice()); // 按价格升序
}
}
class PriceDescComparator implements java.util.Comparator<Product> {
@Override
public int compare(Product p1, Product p2) {
return Double.compare(p2.getPrice(), p1.getPrice()); // 按价格降序
}
}
class StockComparator implements java.util.Comparator<Product> {
@Override
public int compare(Product p1, Product p2) {
return Integer.compare(p1.getStock(), p2.getStock()); // 按库存升序
}
}
// 方式2:复杂比较器
class MultiFieldComparator implements java.util.Comparator<Product> {
@Override
public int compare(Product p1, Product p2) {
// 先按分类排序
int categoryCompare = p1.getCategory().compareTo(p2.getCategory());
if (categoryCompare != 0) {
return categoryCompare;
}
// 分类相同按价格降序
int priceCompare = Double.compare(p2.getPrice(), p1.getPrice());
if (priceCompare != 0) {
return priceCompare;
}
// 价格相同按库存升序
return Integer.compare(p1.getStock(), p2.getStock());
}
}
// Comparator使用测试
class ComparatorTest {
public static void main(String[] args) {
System.out.println("=== Comparator接口测试 ===");
// 创建商品列表
java.util.List<Product> products = new java.util.ArrayList<>();
products.add(new Product("iPhone", 6999.0, 50, "手机"));
products.add(new Product("小米手机", 2999.0, 100, "手机"));
products.add(new Product("联想笔记本", 5999.0, 30, "电脑"));
products.add(new Product("华为平板", 3999.0, 80, "平板"));
products.add(new Product("MacBook", 12999.0, 20, "电脑"));
// 方式1:使用独立的Comparator类
System.out.println("\n1. 按价格升序:");
products.sort(new PriceComparator());
for (Product p : products) {
System.out.println(" " + p);
}
System.out.println("\n2. 按价格降序:");
products.sort(new PriceDescComparator());
for (Product p : products) {
System.out.println(" " + p);
}
System.out.println("\n3