1. 比较Java中的对象

在 Java 中,对象可以通过引用和值进行比较。

比较参考

如果两个变量在内存中指向同一个对象,那么这些变量中存储的引用是相等的。如果使用相等运算符 ( ==) 比较这些变量,结果为真,并且该结果有意义。这里的一切都很简单。

代码 控制台输出
Integer a = 5;
Integer b = a;
System.out.println(a == b);


true

按价值比较

但是您经常会遇到两个变量引用两个相同的不同对象的情况。例如,包含相同文本的两个不同字符串对象。

要确定不同的对象是否相同,请使用equals()方法。例如:

代码 控制台输出
String a = new String("Hello");
String b = new String("Hello");
System.out.println(a == b);
System.out.println(a.equals(b));


false
true

equals方法不限于类String每个班级都有。

甚至是您自己编写的类,这就是原因。



2.Object

Java 中的所有类都继承该类Object。Java 的创建者想出了这种方法。

而如果一个类继承了该类Object,那么它就获得了该类的所有方法Object。这是继承的主要结果。

换句话说,每个类都有Object类的方法,即使他们的代码没有提到它们。

这些继承的方法包括与对象比较相关的方法。这些是equals()hashCode()方法。

代码 实际上,这就是我们将拥有的:
class Person
{
   String name;
   int age;
}
class Person extends Object
{
   String name;
   int age;

   public boolean equals(Object obj)
   {
      return this == obj;
   }

   public int hashCode()
   {
      return address_of_object_in_memory; // This is the default implementation, but there may be a different implementation
   }
}

在上面的示例中,我们创建了一个Person带有 name 和 age 参数的简单类,但没有一个方法。但是因为所有的类都继承了这个Object类,所以这个Person类自动有两个方法:

方法 描述
boolean equals(Object obj)
比较当前对象和传递的对象
int hashCode()
返回当前对象的哈希码

事实证明,绝对每个对象都有equals方法,不同类型的对象可以相互比较。这样的代码将编译并完美运行。

代码 控制台输出
Integer a = 5;
String s = "Hello";
System.out.println(a.equals(s));
System.out.println(s.equals(a));


false
false
Object a = new Integer(5);
Object b = new Integer(5);
System.out.println(a.equals(b)) ;


true

3.equals()方法

equals()方法继承自该类Object,实现了比较当前对象与传递的对象的最简单算法:它只是比较对对象的引用。

Person如果您只是比较变量而不是调用方法,您会得到相同的结果equals()。例子:

代码 控制台输出
Person a = new Person();
a.name = "Steve";

Person b = new Person();
b.name = "Steve";

System.out.println(a == b);
System.out.println(a.equals(b));






false
false

equals调用该方法时a,它只是将存储在a变量中的引用与存储在b变量中的引用进行比较。

但是,类的比较工作不同String。为什么?

因为创建该类的人String编写了他们自己的方法实现equals()

equals()方法的实现

现在让我们在类中编写我们自己的 equals 方法的实现Person。我们将考虑 4 个主要案例。

重要的:
无论哪个类重写equals方法,它总是以一个Object对象作为参数

场景 1:调用该方法的同一对象equals也传递给该equals方法。如果当前对象和传递对象的引用相等,则该方法必须返回true。一个对象等于它自己。

在代码中它看起来像这样:

代码 描述
public boolean equals(Object obj)
{
   if (this == obj)
    return true;

   // The rest of the code of the equals method
}


比较参考资料

场景 2null传递给equals方法——我们没有什么可比较的。调用该方法的对象equals肯定不为null,所以我们需要false在这种情况下返回。

在代码中它看起来像这样:

代码 描述
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   // The rest of the code of the equals method
}


比较引用


是传递的对象null吗?

场景 3:将对不是 a 的对象的引用传递Person给该equals方法。对象是否Person等于非Person对象?这是一个由课程开发者Person决定的问题,无论他或她想要什么。

但通常对象必须属于同一类才能被视为相等。因此,如果类的对象以外的东西Person被传递给我们的 equals 方法,那么我们将始终返回false。如何检查对象的类型?没错——通过使用instanceof运算符。

这是我们的新代码的样子:

代码 描述
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   // The rest of the code of the equals method
}


比较引用


是传递的对象null吗?


如果传递的对象不是Person

4.比较两个Person对象

我们最终得到了什么?如果我们已经到达方法的末尾,那么我们有一个Person不是的对象引用null。所以我们将其转换为a Person,并比较两个对象的相关内部数据。这是我们的第四个场景

代码 描述
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   // The rest of the code of the equals method
}


比较引用


是传递的对象null吗?


如果传递的对象不是Person


类型转换

你如何比较两个Person对象?如果他们具有相同的名字 ( name) 和年龄 ( age),则它们是相等的。最终代码将如下所示:

代码 描述
public boolean equals(Object obj)
{
   if (this == obj)
      return true;

   if (obj == null)
      return false;

   if (!(obj instanceof Person))
      return false;

   Person person = (Person) obj;

   return this.name == person.name && this.age == person.age;
}


比较引用


是传递的对象null吗?


如果传递的对象不是Person


类型转换

但这还不是全部。

首先,name字段是a String,所以需要通过调用equals方法来比较name字段。

this.name.equals(person.name)

其次,该name字段可能是null:在这种情况下,您不能调用equals它。您需要额外检查null

this.name != null && this.name.equals(person.name)

也就是说,如果名称字段null在两个Person对象中,那么名称仍然相同。

第四种情况的代码可能如下所示:

Person person = (Person) obj;

if (this.age != person.age)
   return false;

if (this.name == null)
   return person.name == null;

return this.name.equals(person.name);


如果年龄不相等,
则立即return false

Ifthis.name等于null,使用该方法进行比较没有意义equals。这里要么第二个name字段等于null,要么不等于。

使用 方法比较两个名称字段equals


5.hashCode()方法

除了equals旨在对两个对象的所有字段执行详细比较的方法之外,还有另一种方法可用于不精确但非常快速的比较:hashCode().

想象一下,您正在按字母顺序对包含数千个单词的列表进行排序,并且您需要反复比较单词对。单词很长,由很多字母组成。一般来说,这样的比较会耗费很长时间。

但它可以加速。假设我们有以不同字母开头的单词——很明显它们是不同的。但如果它们以相同的字母开头,那么我们还不能说结果会是什么:这些词可能相同或不同。

hashCode()方法使用类似的原理工作。如果你在一个对象上调用它,它会返回一些数字——类似于单词中的第一个字母。该数字具有以下属性:

  • 相同的对象总是具有相同的哈希码
  • 不同的对象可以有相同的哈希码,或者它们的哈希码可以不同
  • 如果对象有不同的哈希码,那么对象肯定是不同的

为了更清楚地说明这一点,让我们用文字重新定义这些属性:

  • 相同的单词总是有相同的首字母。
  • 不同单词的首字母可以相同,也可以不同
  • 如果单词的首字母不同,则单词肯定不同

最后一个属性用于加速对象比较:

首先,计算两个对象的哈希码。如果这些hashcode不一样,那么对象肯定是不一样的,没必要再去比较了。

但是如果哈希码相同,那么我们仍然需要使用 equals 方法来比较对象。



6. 代码契约

上述行为必须由 Java 中的所有类实现。在编译期间,无法检查对象是否被正确比较。

Java 程序员有一个普遍的共识,如果他们自己编写 equals() 方法的实现并因此覆盖标准实现(在类中),他们也必须按照上述规则Object编写自己的方法实现hashCode()使满意。

这种安排称为合同

如果您只在您的班级中实施该方法equals()或仅​​实施该hashCode()方法,那么您就严重违反了合同(您违反了协议)。不要这样做。

如果其他程序员使用您的代码,它可能无法正常工作。此外,您将使用依赖于遵守上述合同的代码。

重要的!

所有Java集合在查找元素时,都是先比较对象的hashcode,然后才使用方法进行比较equals

这意味着,如果您为自己的类提供了一个equals方法,但您没有编写自己的hashCode()方法或未正确实现它,那么集合可能无法与您的对象一起正常工作。

例如,您可能将一个对象添加到列表中,然后使用该方法搜索它contains(),但集合可能找不到您的对象。