其实呢比较总体分为四个部分
1.基本类型的比较String,int等这些基本类型.
2.equals和hashcode (注解@Data和@equalsandhashcode)
3..Comparble中compareTo方法
4.Comparator中compare方法
基本类型的比较
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指向的是同一个对象
}
}
// 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和hashcode
外面有些说hashcode也可以比较大小是错误的说法,可能有些例子只是恰好而已.
- 若两个对象通过
equals()
方法比较结果为true
,那么它们的hashCode()
返回值必定相同:这是 Java 语言的强制要求。在使用哈希集合(像HashMap
、HashSet
这类)时,哈希集合会先借助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 方法,以及toString
、equals
、hashCode
方法和一个无参构造函数。@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
方法,而不需要getter
、setter
等其他方法,可以单独使用@EqualsAndHashCode
注解。
接口类的比较Comparble
public interface Comparable < E > {// 返回值 :// < 0: 表示 this 指向的对象小于 o 指向的对象// == 0: 表示 this 指向的对象等于 o 指向的对象// > 0: 表示 this 指向的对象大于 o 指向的对象int compareTo ( E o );}
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 比较大
}
}
比较器比较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 比较大
}
}
覆写的方法 说明Object.equals 因为所有类都是继承自 Object 的,所以直接覆写即可,不过 只能比较相等与否.Comparable.compareTo 需要手动实现接口,侵入性比较强,但一旦实现,每次用该 类都有顺序,属于内部顺序.Comparator.compare 需要实现一个比较器对象,对待比较类的侵入性弱,但对算 法代码实现侵入性强.