22Java对象的比较

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
        
        // 问题:我们通常想要比较的是内容,而不是地址
    }
}

注意易错点

  1. 对象不能使用 ><等比较运算符

  2. ==比较的是对象地址,不是内容

  3. 默认的 equals()方法比较的也是地址

  4. 需要根据实际需求实现自定义比较

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方法规范

  1. 自反性:x.equals(x) 必须返回 true

  2. 对称性:如果 x.equals(y) 返回 true,那么 y.equals(x) 也必须返回 true

  3. 传递性:如果 x.equals(y) 返回 true,y.equals(z) 返回 true,那么 x.equals(z) 必须返回 true

  4. 一致性:只要对象没有被修改,多次调用 equals 应该返回相同结果

  5. 非空性:x.equals(null) 必须返回 false

  6. 必须覆写 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接口要点

  1. 实现 Comparable<T>接口

  2. 重写 compareTo(T o)方法

  3. 返回值规则:

    • 负数:当前对象 < 参数对象

    • 零:当前对象 = 参数对象

    • 正数:当前对象 > 参数对象

  4. 应该与 equals()保持一致

  5. 需要考虑 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
相关推荐
张小凡vip1 小时前
Python异步编程实战:基于async/await的高并发实现
开发语言·python
常利兵2 小时前
吃透Java操作符高阶:位操作符+赋值操作符全解析(Java&C区别+实战技巧+面试考点)
java·c语言·面试
不用89k2 小时前
SpringBoot学习新手项初识请求
java·spring boot·学习
zcbk01682 小时前
不踩坑!手把手教你在 Mac 上安装 Windows(含分区/虚拟机/驱动解决方案)
python
码农阿豪2 小时前
SpringBoot实现公正有趣好玩的年会抽奖系统
java·spring boot·后端
Java爱好狂.2 小时前
RDB&AOF持久化原理解析
java·数据库·redis·后端开发·java编程·java程序员·java八股文
Dev7z2 小时前
滚压表面强化过程中变形诱导位错演化与梯度晶粒细化机理的数值模拟研究
人工智能·python·算法
吴秋霖2 小时前
apple游客下单逆向分析
python·算法·逆向分析
hashiqimiya3 小时前
gradle.properties使用系统代理
java