Java中的比较方法(equals,hashcode,Comparble,Comparator,注解@Data和@Equalsandhashcode(参数使用等等)(这个是项目中最常用的比较注解))

其实呢比较总体分为四个部分

1.基本类型的比较String,int等这些基本类型.

2.equals和hashcode (注解@Data和@equalsandhashcode)

3..Comparble中compareTo方法

4.Comparator中compare方法

基本类型的比较

Java 中,基本类型的对象可以直接比较大小。
public class TestCompare {
public static void main(String[] args) {
int a = 10;
int b = 20;
System.out.println(a > b);
System.out.println(a < b);
System.out.println(a == b);
char c1 = 'A';
char c2 = 'B';
System.out.println(c1 > c2);
System.out.println(c1 < c2);
System.out.println(c1 == c2);
boolean b1 = true;
boolean b2 = false;
System.out.println(b1 == b2);
System.out.println(b1 != b2);
}
}

注意:"=" 是赋值的意思, 大家不要忘记了.

对象比较的问题(引出后面的比较方法)(这个是过度)

class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
public class TestPriorityQueue {
public static void main(String[] args) {
Card c1 = new Card(1, "♠");
Card c2 = new Card(2, "♠");
Card c3 = c1;
//System.out.println(c1 > c2); // 编译报错
System.out.println(c1 == c2); // 编译成功 ----> 打印false,因为c1和c2指向的是不同对象
//System.out.println(c1 < c2); // 编译报错
System.out.println(c1 == c3); // 编译成功 ----> 打印true,因为c1和c3指向的是同一个对象
}
}
c1 c2 c3 分别是 Card 类型的引用变量,上述代码在比较编译时:
c1 > c2 编译失败
c1== c2 编译成功
c1 < c2 编译失败
从编译结果可以看出, Java 中引用类型的变量不能直接按照 > 或者 < 方式进行比较 。 那为什么 == 可以比较?
因为: 对于用户实现自定义类型,都默认继承自 Object 类,而 Object 类中提供了 equal 方法,而 == 默认情况下调 用的就是 equal 方法 ,但是该方法的比较规则是: 没有比较引用变量引用对象的内容,而是直接比较引用变量的地 ,但有些情况下该种比较就不符合题意。

// Object equal 的实现,可以看到:直接比较的是两个引用变量的地址
public boolean equals ( Object obj ) {
return ( this == obj );
}

 

 equals比较

public class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
@Override
public boolean equals(Object o) {
// 自己和自己比较
if (this == o) {
return true;
}
// o如果是null对象,或者o不是Card的子类
if (o == null || !(o instanceof Card)) {
return false;
}
// 注意基本类型可以直接比较,但引用类型最好调用其equal方法
Card c = (Card)o;
return rank == c.rank && suit.equals(c.suit);
}
}
注意: 一般覆写 equals 的套路就是上面演示的
1. 如果指向同一个对象,返回 true
2. 如果传入的为 null ,返回 false
3. 如果传入的对象类型不是 Card ,返回 false
4. 按照类的实现目标完成比较,例如这里只要花色和数值一样,就认为是相同的牌
5. 注意下调用其他引用类型的比较也需要 equals ,例如这里的 suit 的比较
覆写基类 equal 的方式虽然可以比较,但缺陷是: equal 只能按照相等进行比较,不能按照大于、小于的方式进行 比较

equals和hashcode

外面有些说hashcode也可以比较大小是错误的说法,可能有些例子只是恰好而已.

  • 若两个对象通过 equals() 方法比较结果为 true,那么它们的 hashCode() 返回值必定相同:这是 Java 语言的强制要求。在使用哈希集合(像 HashMapHashSet 这类)时,哈希集合会先借助 hashCode() 方法得到对象的哈希码,从而快速定位对象存储的桶位置,接着再用 equals() 方法来精确判断对象是否相等。若两个对象相等但哈希码不同,就可能在哈希集合里出现错误的存储与查找情况。
  • 若两个对象的 hashCode() 返回值相同,它们通过 equals() 方法比较的结果不一定为 true:也就是哈希码相同的对象不一定相等。这是由于哈希码的取值范围是有限的,不同对象可能会产生相同的哈希码,这种情况被叫做哈希冲突。

在实际开发中,当你重写 equals() 方法时,必须同时重写 hashCode() 方法,以此保证上述规则的正确性。这样做能够避免在使用哈希集合时出现意外的行为。

有些人问为什么需要重写呢?

不重写 hashCode 方法可能导致的问题

如果重写了 equals 方法但没有重写 hashCode 方法,就可能违反上述契约。例如,创建两个属性相同的对象,使用重写的 equals 方法比较它们会返回 true,但由于没有重写 hashCode 方法,它们的 hashCode 值可能不同。在使用哈希集合时,就会出现问题。

import java.util.HashSet;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 重写 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && name.equals(person.name);
    }

    // 未重写 hashCode 方法
}

public class Main {
    public static void main(String[] args) {
        Person p1 = new Person("Alice", 25);
        Person p2 = new Person("Alice", 25);

        HashSet<Person> set = new HashSet<>();
        set.add(p1);
        set.add(p2);

        System.out.println("集合大小: " + set.size());
    }
}

在上述代码中,Person 类重写了 equals 方法,但没有重写 hashCode 方法。虽然 p1 和 p2 通过 equals 方法比较是相等的,但由于它们的 hashCode 值不同,HashSet 会将它们视为不同的元素,从而都添加到集合中,这与我们的预期不符。

为了避免上述问题,当重写 equals 方法时,应该同时重写 hashCode 方法,确保相等的对象具有相同的哈希码。

import java.util.Objects;

class Person {
    private String name;
    private int age;

    public Person(String name, int age) {
        this.name = name;
        this.age = age;
    }

    // 重写 equals 方法
    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        Person person = (Person) o;
        return age == person.age && name.equals(person.name);
    }

    // 重写 hashCode 方法
    @Override
    public int hashCode() {
        return Objects.hash(name, age);
    }
}

在这个改进后的代码中,Person 类同时重写了 equals 和 hashCode 方法。hashCode 方法使用 Objects.hash 方法根据 name 和 age 属性生成哈希码,确保了相等的对象具有相同的哈希码,从而保证了哈希集合的正常工作。

综上所述,在开发环境中重写 equals 方法时,重写 hashCode 方法是为了遵守 equals 和 hashCode 方法之间的契约,保证哈希集合的正确性和性能。

@Data和@Equalsandhashcode

由于开发项目的时候,经常使用到这两个注解来比较是否相等,这两个注解内置了equals和hashcode.

@Data 注解

功能

@Data 是一个复合注解,它整合了 @Getter@Setter@ToString@EqualsAndHashCode 和 @RequiredArgsConstructor 这几个注解的功能。使用 @Data 注解后,Lombok 会自动为类生成以下内容:

  • 所有字段的 getter 和 setter 方法。
  • 一个 toString 方法,用于返回包含类名和所有字段值的字符串。
  • equals 和 hashCode 方法,用于比较对象是否相等。
  • 一个包含所有必需字段(即被 final 修饰或者被 @NonNull 注解标记的字段)的构造函数。
  • import lombok.Data;
    
    @Data
    class Person {
        private String name;
        private int age;
    }
    
    public class Main {
        public static void main(String[] args) {
            Person person = new Person();
            person.setName("Alice");
            person.setAge(25);
            System.out.println(person);
        }
    }

    在上述代码中,@Data 注解为 Person 类自动生成了 name 和 age 字段的 getter 和 setter 方法,以及 toStringequalshashCode 方法和一个无参构造函数。

    @EqualsAndHashCode 注解(包含有疑问的参数讲解)

    功能

    @EqualsAndHashCode 注解用于自动生成 equals 和 hashCode 方法。默认情况下,它会考虑类中的所有非静态和非瞬态(transient)字段。你可以通过 exclude 或 of 参数来指定要排除或包含的字段。

    示例代码

  • import lombok.EqualsAndHashCode;
    
    @EqualsAndHashCode(exclude = "age")
    class Employee {
        private String name;
        private int age;
    }
    
    public class Main {
        public static void main(String[] args) {
            Employee emp1 = new Employee();
            emp1.setName("Bob");
            emp1.setAge(30);
    
            Employee emp2 = new Employee();
            emp2.setName("Bob");
            emp2.setAge(35);
    
            System.out.println(emp1.equals(emp2)); 
        }
    }

    在这个例子中,@EqualsAndHashCode(exclude = "age") 表示在生成 equals 和 hashCode 方法时排除 age 字段。因此,emp1 和 emp2 虽然 age 不同,但由于 name 相同,equals 方法会返回 true

其中:

equalsandhashcode里面的大家有点遗憾的参数@EqualsAndHashCode(callSuper = true)

这个参数默认是false;意思是重写的equals和hashcode不比较父类的属性.如果设置成true就是子类和父类的属性一起都要比较.还有很多别的参数这里就不多一一介绍,有疑问的可以一起讨论或者查阅相关资料,这里只是提一下大家疑惑比较多的问题.

import lombok.EqualsAndHashCode;

class Animal {
    private String species;

    public Animal(String species) {
        this.species = species;
    }
}

@EqualsAndHashCode(callSuper = true)
class Dog extends Animal {
    private String name;

    public Dog(String species, String name) {
        super(species);
        this.name = name;
    }
}

使用这两个注解可以显著减少样板代码,让代码更加简洁易读。但在使用时要注意,自动生成的方法可能并不总是符合你的业务需求,必要时可以手动重写这些方法。

  • 两者关系

  • @Data 注解包含了 @EqualsAndHashCode 的功能。如果你使用了 @Data 注解,就无需再单独使用 @EqualsAndHashCode 注解,除非你想要对 equals 和 hashCode 方法的生成进行更细致的控制。
  • 如果你只需要 equals 和 hashCode 方法,而不需要 gettersetter 等其他方法,可以单独使用 @EqualsAndHashCode 注解。

接口类的比较Comparble

Comparble JDK 提供的泛型的比较接口类,源码实现具体如下:
public interface Comparable < E > {
// 返回值 :
// < 0: 表示 this 指向的对象小于 o 指向的对象
// == 0: 表示 this 指向的对象等于 o 指向的对象
// > 0: 表示 this 指向的对象大于 o 指向的对象
int compareTo ( E o );
}

 

对用用户自定义类型,如果要想按照大小与方式进行比较时: 在定义类时,实现 Comparble 接口即可,然后在类 中重写 compareTo 方法。
public class Card implements Comparable<Card> {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
// 根据数值比较,不管花色
// 这里我们认为 null 是最小的
@Override
public int compareTo(Card o) {
if (o == null) {
return 1;
}
return rank - o.rank;
}
public static void main(String[] args){
Card p = new Card(1, "♠");
Card q = new Card(2, "♠");
Card o = new Card(1, "♠");
System.out.println(p.compareTo(o)); // == 0,表示牌相等
System.out.println(p.compareTo(q)); // < 0,表示 p 比较小
System.out.println(q.compareTo(p)); // > 0,表示 q 比较大
}
}
注意:Compareble java.lang 中的接口类,可以直接使用。

比较器比较Comparator

用户自定义比较器类,实现Comparator接口

public interface Comparator < T > {
// 返回值 :
// < 0: 表示 o1 指向的对象小于 o2 指向的对象
// == 0: 表示 o1 指向的对象等于 o2 指向的对象
// > 0: 表示 o1 指向的对象等于 o2 指向的对象
int compare ( T o1 , T o2 );
}

 覆写Comparator中的compare方法

 

import java.util.Comparator;
class Card {
public int rank; // 数值
public String suit; // 花色
public Card(int rank, String suit) {
this.rank = rank;
this.suit = suit;
}
}
class CardComparator implements Comparator<Card> {
// 根据数值比较,不管花色
// 这里我们认为 null 是最小的
@Override
public int compare(Card o1, Card o2) {
if (o1 == o2) {
return 0;
}
if (o1 == null) {
return -1;
}if (o2 == null) {
return 1;
}
return o1.rank - o2.rank;
}
public static void main(String[] args){
Card p = new Card(1, "♠");
Card q = new Card(2, "♠");
Card o = new Card(1, "♠");
// 定义比较器对象
CardComparator cmptor = new CardComparator();
// 使用比较器对象进行比较
System.out.println(cmptor.compare(p, o)); // == 0,表示牌相等
System.out.println(cmptor.compare(p, q)); // < 0,表示 p 比较小
System.out.println(cmptor.compare(q, p)); // > 0,表示 q 比较大
}
}
注意: Comparator java.util 包中的泛型接口类,使用时必须导入对应的包.
覆写的方法                                    说明
Object.equals                           因为所有类都是继承自 Object 的,所以直接覆写即可,不过                                                   只能比较相等与否.
Comparable.compareTo           需要手动实现接口,侵入性比较强,但一旦实现,每次用该                                                     类都有顺序,属于内部顺序.
Comparator.compare               需要实现一个比较器对象,对待比较类的侵入性弱,但对算                                                     法代码实现侵入性强.

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值