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คลาสอย่างง่ายด้วยพารามิเตอร์ชื่อและอายุ แต่ไม่ใช่เมธอดเดียว แต่เนื่องจากทุกคลาสสืบทอด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()วิธีการ

ตอนนี้เรามาเขียนวิธีที่เราใช้เท่ากับใน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
}


เปรียบเทียบการอ้างอิง

สถานการณ์ที่ 2 : nullถูกส่งผ่านไปยังequalsเมธอด — เราไม่มีอะไรจะเปรียบเทียบ วัตถุที่equalsเรียกเมธอดนั้นไม่เป็นโมฆะอย่างแน่นอน ดังนั้น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คลาสไปยังวิธีการเท่ากับเราจะส่งคืนเสมอ 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


Typecasting

และคุณจะเปรียบเทียบสอง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


Typecasting

แต่นั่นไม่ใช่ทั้งหมด

ขั้นแรก ฟิลด์ชื่อคือ a Stringดังนั้นคุณต้องเปรียบเทียบฟิลด์ชื่อโดยเรียกequalsเมธอด

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

ถ้าthis.nameเท่ากับ ก็nullไม่มีประโยชน์ที่จะเปรียบเทียบด้วยequalsวิธี นี้ ที่นี่nameฟิลด์ที่สองเท่ากับnullหรือไม่ใช่

เปรียบเทียบฟิลด์ชื่อทั้งสองโดยใช้equalsเมธอด


5. hashCode()วิธีการ

นอกจากequalsวิธีการซึ่งมีจุดประสงค์เพื่อทำการเปรียบเทียบโดยละเอียดของฟิลด์ทั้งหมดของวัตถุทั้งสองแล้ว ยังมีวิธีการอื่นที่สามารถใช้สำหรับการเปรียบเทียบที่ไม่แม่นยำแต่รวดเร็วมากhashCode():

ลองนึกภาพว่าคุณกำลังจัดเรียงรายการคำนับพันตามตัวอักษร และคุณต้องเปรียบเทียบคู่คำซ้ำๆ และคำนั้นยาวประกอบด้วยตัวอักษรจำนวนมาก โดยทั่วไปแล้วการเปรียบเทียบดังกล่าวจะต้องใช้เวลานานมาก

แต่สามารถเร่งความเร็วได้ สมมติว่าเรามีคำที่ขึ้นต้นด้วยตัวอักษรต่างกัน จะเห็นได้ชัดเจนว่าต่างกัน แต่ถ้าขึ้นต้นด้วยตัวอักษรเดียวกัน เราก็ยังบอกไม่ได้ว่าผลลัพธ์จะเป็นอย่างไร คำอาจออกมาเท่ากันหรือต่างกันก็ได้

วิธี นี้hashCode()ใช้หลักการที่คล้ายกัน หากคุณเรียกมันบนวัตถุ มันจะส่งกลับ ตัวเลข บางส่วน — คล้ายกับตัวอักษรตัวแรกของคำ หมายเลขนี้มีคุณสมบัติดังต่อไปนี้:

  • วัตถุที่เหมือนกันจะมีรหัสแฮชเดียวกันเสมอ
  • วัตถุที่แตกต่างกันสามารถมีรหัสแฮชเดียวกัน หรือรหัสแฮชอาจแตกต่างกัน
  • หากวัตถุมีแฮชโค้ดต่างกัน แสดงว่าวัตถุนั้นแตกต่างกันอย่างแน่นอน

เพื่อให้ชัดเจนยิ่งขึ้น ลองเปลี่ยนคุณสมบัติเหล่านี้ในแง่ของคำ:

  • คำที่เหมือนกันจะมีอักษรตัวแรกเหมือนกันเสมอ
  • คำที่ต่างกันอาจมีอักษรตัวแรกเหมือนกัน หรืออักษรตัวแรกอาจต่างกันก็ได้
  • หากคำมีอักษรตัวแรกต่างกัน คำนั้นก็จะแตกต่างกันอย่างแน่นอน

คุณสมบัติสุดท้ายใช้เพื่อเร่งการเปรียบเทียบวัตถุ:

ขั้นแรก คำนวณรหัสแฮชของวัตถุทั้งสอง หากแฮชโค้ดเหล่านี้แตกต่างกัน แสดงว่าวัตถุนั้นแตกต่างกันอย่างแน่นอน และไม่จำเป็นต้องเปรียบเทียบเพิ่มเติมอีก

แต่ถ้ารหัสแฮชเหมือนกัน เราก็ยังคงต้องเปรียบเทียบวัตถุโดยใช้วิธีการเท่ากับ



6. สัญญาในรหัส

ลักษณะการทำงานที่อธิบายไว้ข้างต้นต้องนำไปใช้โดยทุกคลาสใน Java ระหว่างการคอมไพล์ ไม่มีวิธีตรวจสอบว่าอ็อบเจ็กต์ถูกเปรียบเทียบอย่างถูกต้องหรือไม่

โปรแกรมเมอร์ Java มีข้อตกลงสากลว่าหากพวกเขาเขียนการใช้งานวิธีการเท่ากับ () ของตนเองและแทนที่การใช้งานมาตรฐาน (ในคลาสObject) พวกเขาจะต้องเขียนการใช้งานเมธอดของตนเองhashCode()ในลักษณะที่กฎดังกล่าวข้างต้น พอใจ.

ข้อ ตกลงนี้เรียกว่าสัญญา

หากคุณใช้เฉพาะวิธีequals()หรือhashCode()วิธีเดียวในชั้นเรียนของคุณ แสดงว่าคุณละเมิดสัญญาอย่างร้ายแรง (คุณทำผิดข้อตกลง) อย่าทำเช่นนี้

ถ้าโปรแกรมเมอร์คนอื่นใช้โค้ดของคุณ โค้ดนั้นอาจทำงานไม่ถูกต้อง ยิ่งไปกว่านั้น คุณจะใช้รหัสที่อาศัยการปฏิบัติตามสัญญาข้างต้น

สำคัญ!

เมื่อค้นหาองค์ประกอบ คอลเล็กชัน Java ทั้งหมดจะเปรียบเทียบรหัสแฮชของอ็อบเจ็กต์ก่อน แล้วจึงทำการเปรียบเทียบโดยใช้เมธอดequalsเท่านั้น

นั่นหมายความว่า หากคุณให้equalsเมธอดแก่คลาสของคุณเอง แต่คุณไม่ได้เขียนhashCode()เมธอดของคุณเอง หรือคุณนำไปใช้อย่างไม่ถูกต้อง การรวบรวมอาจทำงานไม่ถูกต้องกับอ็อบเจกต์ของคุณ

ตัวอย่างเช่น คุณอาจเพิ่มวัตถุลงในรายการแล้วค้นหาโดยใช้contains()วิธีการ แต่คอลเลกชันอาจไม่พบวัตถุของคุณ