"ตอนนี้ฉันจะบอกคุณเกี่ยวกับวิธีการบางอย่างที่มีประโยชน์พอๆ กัน:  equals(Object o) & hashCode() "

"คุณอาจจำได้แล้วว่าใน Java เมื่อเปรียบเทียบตัวแปรอ้างอิง ตัววัตถุเองจะไม่ถูกเปรียบเทียบ แต่จะเปรียบเทียบการอ้างอิงถึงวัตถุ"

รหัส คำอธิบาย
Integer i = new Integer(1);
Integer j = new Integer(1);
System.out.println(i==j);
ฉันไม่เท่ากับ j
ตัวแปรชี้ไปที่วัตถุต่างๆ
แม้ว่าวัตถุจะมีข้อมูลเดียวกัน
Integer i = new Integer(1);
Integer j = i;
System.out.println(i==j);
ฉันเท่ากับ j ตัวแปรมีการอ้างอิงไปยังวัตถุเดียวกัน

"ครับ ผมจำได้"

ที่  เท่ากัน .

"วิธีเท่ากับเป็นวิธีการแก้ปัญหามาตรฐานที่นี่ จุดประสงค์ของวิธีเท่ากับคือการระบุว่าวัตถุภายในเหมือนกันหรือไม่ โดยการเปรียบเทียบสิ่งที่เก็บไว้ภายในวัตถุนั้น"

"แล้วมันทำอย่างนั้นได้อย่างไร"

"ทุกอย่างคล้ายกับเมธอด toString()"

คลาสอ็อบเจกต์มีการใช้งานวิธีการเท่ากับซึ่งเปรียบเทียบการอ้างอิง:

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

"เยี่ยม... กลับไปเป็นแบบนั้นอีกแล้วใช่ไหม"

"เชิดหน้าขึ้น! มันเป็นเรื่องยุ่งยากจริงๆ"

"เมธอดนี้สร้างขึ้นเพื่อให้นักพัฒนาสามารถเขียนทับในคลาสของตนเองได้ อย่างไรก็ตาม มีเพียงนักพัฒนาของคลาสเท่านั้นที่รู้ว่าข้อมูลใดเกี่ยวข้องและสิ่งใดไม่เกี่ยวข้องเมื่อทำการเปรียบเทียบ"

"คุณยกตัวอย่างได้ไหม"

"แน่นอน สมมติว่าเรามีชั้นเรียนที่แสดงเศษส่วนทางคณิตศาสตร์ มันจะมีลักษณะดังนี้:"

ตัวอย่าง:
class Fraction
{
private int numerator;
private int denominator;
Fraction(int numerator, int denominator)
{
this.numerator  = numerator;
this.denominator = denominator;
}public boolean equals(Object obj)
{
if (obj==null)
return false;

if (obj.getClass() != this.getClass() )
return false;

Fraction other = (Fraction) obj;
return this.numerator* other.denominator == this.denominator * other.numerator;
}
}
ตัวอย่างการเรียกใช้เมธอด:
Fraction one = new Fraction(2,3);
Fraction two = new Fraction(4,6);
System.out.println(one.equals(two));
การเรียกเมธอดจะคืนค่าจริง
เศษส่วน 2/3 เท่ากับเศษส่วน 4/6

"ตอนนี้มาวิเคราะห์ตัวอย่างนี้กัน"

"เราลบล้าง วิธี การเท่ากับดังนั้น วัตถุ เศษส่วนจะมีการใช้งานของตัวเอง

"มีวิธีการตรวจสอบหลายวิธี:"

" 1)  ถ้าอ็อบเจกต์ที่ผ่านการเปรียบเทียบมีค่าเป็นnullแสดงว่าอ็อบเจกต์นั้นไม่เท่ากัน หากคุณสามารถเรียกใช้ เมธอดการ เท่ากับบนอ็อบเจกต์ได้ แสดงว่าไม่เป็นnull อย่างแน่นอน "

" 2)  การเปรียบเทียบคลาส ถ้าอ็อบเจกต์เป็นอินสแตนซ์ของคลาสต่างๆ เราจะไม่พยายามเปรียบเทียบ เราจะใช้return false ทันที เพื่อระบุว่าสิ่งเหล่านี้เป็นอ็อบเจ็กต์ที่แตกต่างกัน"

" 3)  ทุกคนจำได้ตั้งแต่ป.2 ว่า 2/3 เท่ากับ 4/6 แต่จะเช็คยังไงล่ะ"

2/3 == 4/6
เราคูณทั้งสองข้างด้วยตัวหารทั้งสอง (6 และ 3) และเราได้:
6 * 2 == 4 * 3
12 == 12
กฎทั่วไป:
ถ้า
a / b == c / d
แล้ว
a * d == c * b

"ดังนั้น ในส่วนที่ 3 ของ วิธี เท่ากับเราจึงแปลงวัตถุที่ส่งผ่านไปยังเศษส่วนและเปรียบเทียบเศษส่วน"

"เข้าใจแล้ว ถ้าเราแค่เปรียบเทียบตัวเศษกับตัวเศษและตัวส่วนกับตัวส่วน 2/3 จะไม่เท่ากับ 4/6"

"ตอนนี้ฉันเข้าใจสิ่งที่คุณหมายถึงเมื่อคุณกล่าวว่ามีเพียงนักพัฒนาของชั้นเรียนเท่านั้นที่รู้วิธีเปรียบเทียบอย่างถูกต้อง"

"ใช่ แต่นั่นเป็นเพียงครึ่งเรื่อง  ยังมีวิธีอื่น: hashCode() "

"ตอนนี้ทุกอย่างเกี่ยวกับวิธีการเท่ากับมีเหตุผล แต่ทำไมเราต้องใช้  hashCode () "

" ต้องใช้เมธอดhashCode สำหรับการเปรียบเทียบอย่างรวดเร็ว"

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

"ฉันสามารถวนรอบองค์ประกอบทั้งหมดโดยใช้ลูปและเปรียบเทียบวัตถุกับแต่ละวัตถุในชุด จนกว่าฉันจะหาคู่ที่ตรงกัน"

“แล้วถ้าไม่มีล่ะ เราจะทำการเปรียบเทียบเป็นล้านครั้งเพื่อดูว่าวัตถุนั้นไม่มีอยู่จริงเหรอ?

“ใช่ ฉันรู้ว่ามันเป็นการเปรียบเทียบที่มากเกินไป มีวิธีอื่นอีกไหม”

"ใช่ คุณสามารถใช้hashCode () สำหรับสิ่งนี้

เมธอดhashCode () ส่งคืนจำนวนเฉพาะสำหรับแต่ละวัตถุ นักพัฒนาของคลาสจะตัดสินใจว่าจะส่งคืนตัวเลขใด เช่นเดียวกับที่เขาหรือเธอทำกับวิธีการเท่ากับ

"ลองดูตัวอย่าง:"

"ลองนึกภาพว่าคุณมีตัวเลข 10 หลักล้านตัว จากนั้นคุณสามารถทำให้ hashCode ของตัวเลขแต่ละตัวเป็นเศษเหลือหลังจากหารตัวเลขด้วย 100"

นี่คือตัวอย่าง:

ตัวเลข แฮชโค้ดของเรา
1234567890 90
9876554321 21
9876554221 21
9886554121 21

"ใช่ มันสมเหตุสมผลแล้ว เราจะทำอย่างไรกับ hashCode นี้"

"แทนที่จะเปรียบเทียบตัวเลข เราเปรียบเทียบhashCodes ของพวกเขา วิธีนี้เร็วกว่า"

"และเราเรียกว่าเท่ากับก็ต่อเมื่อhashCodes ของพวกเขา เท่ากัน"

"ใช่ มันเร็วกว่า แต่เรายังต้องทำการเปรียบเทียบเป็นล้านๆ ครั้ง เราแค่เปรียบเทียบจำนวนที่น้อยกว่า และเรายังต้องเรียกค่าเท่ากันสำหรับตัวเลขใดๆ ที่มีรหัสแฮชที่ตรงกัน"

"ไม่ คุณสามารถหลีกเลี่ยงได้ด้วยจำนวนการเปรียบเทียบที่น้อยกว่ามาก"

"ลองนึกภาพว่า Set ของเราจัดเก็บตัวเลขที่จัดกลุ่มหรือจัดเรียงตามhashCode (การจัดเรียงด้วยวิธีนี้ถือเป็นการจัดกลุ่มเป็นหลัก เนื่องจากตัวเลขที่มี hashCode เดียวกันจะอยู่ติดกัน) จากนั้นคุณสามารถละทิ้งกลุ่มที่ไม่เกี่ยวข้องได้อย่างรวดเร็วและง่ายดาย เท่านี้ก็เพียงพอแล้ว เพื่อตรวจสอบหนึ่งครั้งต่อกลุ่มเพื่อดูว่า hashCode ตรงกับ hashCode ของวัตถุหรือไม่"

"ลองนึกภาพว่าคุณเป็นนักศึกษาที่กำลังมองหาเพื่อนที่คุณจำได้ด้วยตาเปล่า และเป็นคนที่เรารู้จักอาศัยอยู่ในหอพัก 17 จากนั้นคุณก็แค่ไปที่หอพักทุกแห่งในมหาวิทยาลัยแล้วถามว่า 'นี่คือหอพัก 17 หรือไม่' ถ้าไม่ใช่ คุณก็ไม่สนใจทุกคนในหอพักและไปที่ถัดไป ถ้าคำตอบคือ 'ใช่' คุณก็เริ่มเดินผ่านแต่ละห้องแล้วมองหาเพื่อนของคุณ"

"ในตัวอย่างนี้ หมายเลขหอพัก (17) คือ hashCode"

"นักพัฒนาที่ใช้ฟังก์ชัน hashCode ต้องทราบสิ่งต่อไปนี้:"

A)  สองวัตถุที่แตกต่างกันสามารถมี hashCode เดียวกันได้  (ต่างคนต่างอยู่หอพักเดียวกันได้)

B)  วัตถุที่เหมือนกัน  ( ตามวิธีการเท่ากับต้องมีรหัสแฮชเหมือนกัน .

C)  ต้องเลือกรหัสแฮชเพื่อไม่ให้มีวัตถุที่แตกต่างกันจำนวนมากที่มีรหัสแฮชเดียวกัน  หากมี ข้อดีที่เป็นไปได้ของแฮชโค้ดจะหายไป (คุณไปที่หอพัก 17 และพบว่ามหาวิทยาลัยครึ่งหนึ่งอาศัยอยู่ที่นั่น คนเกียจคร้าน!)

"และตอนนี้สิ่งที่สำคัญที่สุด หากคุณแทนที่ เมธอด เท่ากับคุณต้องแทนที่เมธอดhashCode () และปฏิบัติตามกฎสามข้อที่อธิบายไว้ข้างต้น

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

"ในตัวอย่างเศษส่วนของเรา ถ้าเราทำให้ hashCode เท่ากับตัวเศษ เศษส่วน 2/3 และ 4/6 จะมี hashCodes ต่างกัน เศษส่วนเหมือนกันและวิธีเท่ากับบอกว่าเหมือนกัน แต่ hashCodes บอกว่า พวกมันต่างกัน และถ้าเราเปรียบเทียบโดยใช้ hashCode ก่อนที่เราจะเปรียบเทียบโดยใช้เท่ากับ เราจะสรุปได้ว่าวัตถุนั้นแตกต่างกันและเราไม่เคยใช้เมธอดเท่ากับด้วยซ้ำ"

นี่คือตัวอย่าง:

HashSet<Fraction>set = new HashSet<Fraction>();
set.add(new Fraction(2,3));System.out.println( set.contains(new Fraction(4,6)) );
หาก เมธอด hashCode()  ส่งกลับตัวเศษของเศษส่วน ผลลัพธ์จะ  เป็นเท็จ
และวัตถุ « เศษส่วนใหม่(4,6) » จะไม่พบในชุดสะสม

"วิธีที่ถูกต้องในการใช้ hashCode สำหรับเศษส่วนคืออะไร"

"ที่นี่คุณต้องจำไว้ว่าเศษส่วนที่เท่ากันต้องมีรหัสแฮชเหมือนกัน"

" เวอร์ชัน 1 : hashCode เท่ากับผลลัพธ์ของการหารจำนวนเต็ม"

"สำหรับ 7/5 และ 6/5 นี่จะเป็น 1"

"สำหรับ 4/5 และ 3/5 นี่จะเป็น 0"

"แต่ตัวเลือกนี้ไม่เหมาะสำหรับการเปรียบเทียบเศษส่วนที่จงใจให้น้อยกว่า 1 แฮชโค้ด (ผลลัพธ์ของการหารจำนวนเต็ม) จะเป็น 0 เสมอ"

" เวอร์ชัน 2 : hashCode เท่ากับผลลัพธ์ของการหารจำนวนเต็มของตัวส่วนด้วยตัวเศษ"

"ตัวเลือกนี้เหมาะสำหรับกรณีที่เศษส่วนน้อยกว่า 1 ถ้าเศษส่วนน้อยกว่า 1 ค่าผกผันของมันจะมากกว่า 1 และถ้าเรากลับเศษส่วนทั้งหมด การเปรียบเทียบจะไม่ได้รับผลกระทบแต่อย่างใด"

"เวอร์ชันสุดท้ายของเราผสมผสานโซลูชันทั้งสองเข้าด้วยกัน:"

public int hashCode()
{
return numerator/denominator + denominator/numerator;
}

ลองทดสอบโดยใช้ 2/3 และ 4/6 ควรมีรหัสแฮชที่เหมือนกัน:

เศษ 2/3 เศษ 4/6
ตัวเศษ / ตัวส่วน 2/3 == 0 4 / 6 == 0
ตัวส่วน / ตัวเศษ 3 / 2 == 1 6 / 4 == 1
ตัวเศษ / ตัวส่วน
+
ตัวส่วน / ตัวเศษ
0 + 1 == 1 0 + 1 == 1

"นั่นคือทั้งหมดที่สำหรับตอนนี้."

"ขอบคุณ เอลลี่ มันน่าสนใจจริงๆ"