"ตอนนี้ฉันจะบอกคุณเกี่ยวกับวิธีการบางอย่างที่มีประโยชน์พอๆ กัน: equals(Object o) & hashCode() "
"คุณอาจจำได้แล้วว่าใน Java เมื่อเปรียบเทียบตัวแปรอ้างอิง ตัววัตถุเองจะไม่ถูกเปรียบเทียบ แต่จะเปรียบเทียบการอ้างอิงถึงวัตถุ"
รหัส | คำอธิบาย |
---|---|
|
ฉันไม่เท่ากับ 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 |
"นั่นคือทั้งหมดที่สำหรับตอนนี้."
"ขอบคุณ เอลลี่ มันน่าสนใจจริงๆ"
GO TO FULL VERSION