วิธีการ HashCode () และ Equals () สามารถกล่าวได้ว่าเป็นคุณสมบัติสำคัญของวัตถุที่มุ่งเน้นอย่างสมบูรณ์ของ Java มันอำนวยความสะดวกในการเขียนโปรแกรมของเราและยังนำมาซึ่งอันตรายมากมาย ในบทความนี้เราจะหารือเกี่ยวกับวิธีการทำความเข้าใจและใช้วิธีการทั้งสองนี้อย่างถูกต้อง
หากคุณตัดสินใจที่จะเขียนวิธี Equals () อีกครั้งคุณต้องชัดเจนเกี่ยวกับความเสี่ยงที่เกิดจากการทำเช่นนั้นและตรวจสอบให้แน่ใจว่าคุณสามารถเขียนวิธีการที่มีประสิทธิภาพเท่ากับ () สิ่งหนึ่งที่คุณต้องทราบคือหลังจากการเขียนใหม่เท่ากับ () คุณต้องเขียนวิธี HashCode () ใหม่ เหตุผลเฉพาะจะอธิบายในภายหลัง
ก่อนอื่นมาดูคำอธิบายของวิธี Equals () ในข้อกำหนด Javase 7:
・ มันเป็นการสะท้อนกลับ: สำหรับค่าอ้างอิงที่ไม่ใช่ Null ใด ๆ x, x.equals(x) ควรส่งคืน true
・ มันมีความสมมาตร: สำหรับค่าอ้างอิงที่ไม่ใช่ NULL ใด ๆ x และ y, x.equals(y) ควรส่งคืน true ถ้า y.equals(x) ส่งคืน true
・ มันเป็นสกรรมกริยา: สำหรับค่าอ้างอิงที่ไม่ใช่ NULL ใด ๆ x, y, และ z ถ้า x.equals(y) ส่งคืน true และ y.equals(z) ส่งคืน true จากนั้น x.equals(z) ควรส่งคืน true.
・ มีความสอดคล้อง: สำหรับค่าอ้างอิงที่ไม่ใช่ค่าใด ๆ x และ y , การเรียกร้องหลายครั้งของ x.equals(y) ส่งคืน true หรือส่งคืน false อย่างสม่ำเสมอโดยไม่มีข้อมูลที่ใช้ในการเปรียบเทียบเท่ากับในวัตถุได้รับการแก้ไข
・ สำหรับค่าอ้างอิงที่ไม่ใช่ NULL ใด ๆ x, x.equals(null) ควรส่งคืน false
ข้อความนี้ใช้ตัวเลขจำนวนมากในคณิตศาสตร์ที่ไม่ต่อเนื่อง ให้ฉันอธิบายสั้น ๆ :
1. การสะท้อนกลับ: A.Equals (a) ต้องกลับมาเป็นจริง
2. สมมาตร: ถ้า A.Equals (b) ส่งคืนจริงดังนั้น b.equals (a) ก็ต้องกลับมาจริง
3. การส่ง: ถ้า A.Equals (b) เป็นจริงและ b.equals (c) เป็นจริงดังนั้น A.equals (c) จะต้องเป็นจริงเช่นกัน เพื่อให้มันตรงไปตรงมา a = b, b = c, จากนั้น A = C
4. ความสอดคล้อง: ตราบใดที่สถานะของวัตถุ A และ B ไม่เปลี่ยนแปลง A.Equals (b) จะต้องกลับมาเป็นจริงเสมอ
5. A.Equals (NULL) เพื่อส่งคืน FALSE
ฉันเชื่อว่าตราบใดที่คนที่ไม่ได้เป็นมืออาชีพในวิชาคณิตศาสตร์จะไม่เรียกสิ่งต่าง ๆ ข้างต้น ในแอปพลิเคชันจริงเราจำเป็นต้องเขียนวิธี Equals () ใหม่ตามขั้นตอนบางอย่าง เพื่อความสะดวกในการอธิบายก่อนอื่นเรากำหนดคลาสโปรแกรมเมอร์ (coder):
coder คลาส {ชื่อสตริงส่วนตัว; อายุ int ส่วนตัว; // getters และ setters}สิ่งที่เราต้องการคือถ้าชื่อและอายุของวัตถุโปรแกรมเมอร์ทั้งสองเหมือนกันเราคิดว่าโปรแกรมเมอร์สองโปรแกรมนี้เหมือนกัน ในเวลานี้เราต้องเขียนวิธีการเท่ากับ () ใหม่ เนื่องจากค่าเริ่มต้นเท่ากับ () จริง ๆ แล้วจะกำหนดว่าการอ้างอิงสองรายการชี้ไปที่วัตถุเดียวกันนั้นจึงเทียบเท่ากับ == เมื่อเขียนใหม่ให้ทำตามสามขั้นตอนต่อไปนี้:
1. พิจารณาว่ามันเท่ากับตัวคุณเองหรือไม่
ถ้า (อื่น ๆ == สิ่งนี้) ส่งคืนจริง;
2. ใช้ตัวดำเนินการอินสแตนซ์ของตัวดำเนินการเพื่อตรวจสอบว่าอื่น ๆ เป็นวัตถุของประเภท coder หรือไม่
if (! (อินสแตนซ์อื่น ๆ ของ coder)) ส่งคืน false;
3. เปรียบเทียบโดเมนข้อมูลชื่อและอายุที่คุณปรับแต่งในคลาส Coder และคุณจะต้องไม่พลาด
coder o = (coder) อื่น ๆ ; return o.name.equals (ชื่อ) && o.age == อายุ;
เมื่อเห็นสิ่งนี้ใครบางคนอาจถามว่ามีนักแสดงในขั้นตอนที่ 3 ถ้ามีคนผ่านวัตถุประสงค์ของชั้นเรียนจำนวนเต็มเข้าสู่ความเท่าเทียมกันนี้เขาจะโยน ClassCastException หรือไม่? ความกังวลนี้ซ้ำซ้อนจริง ๆ เนื่องจากเราได้ตัดสินอินสแตนซ์ของขั้นตอนที่สองหากอื่น ๆ เป็นวัตถุที่ไม่ใช่ตัวพิมพ์หรือแม้แต่อื่น ๆ ก็เป็นโมฆะเท็จจะถูกส่งกลับโดยตรงในขั้นตอนนี้เพื่อให้รหัสที่ตามมาจะไม่ได้รับโอกาสที่จะดำเนินการ
สามขั้นตอนข้างต้นยังเป็นขั้นตอนที่แนะนำใน <java ที่มีประสิทธิภาพ> ซึ่งโดยทั่วไปสามารถตรวจสอบให้แน่ใจว่าไม่มีข้อผิดพลาด
ในสเปค Javase 7
"โปรดทราบว่าโดยทั่วไปจำเป็นต้องแทนที่วิธี HashCode เมื่อวิธีการนี้ (เท่ากับ) ถูกแทนที่เพื่อรักษาสัญญาทั่วไปสำหรับวิธีการ HashCode ซึ่งระบุว่าวัตถุที่เท่ากันจะต้องมีรหัสแฮชเท่ากัน"
หากคุณเขียนเมธอด Equals () ใหม่ให้อย่าลืมเขียนวิธี HashCode () ใหม่ เราได้เรียนรู้ตารางแฮชในหลักสูตรโครงสร้างข้อมูลคอมพิวเตอร์ของมหาวิทยาลัย วิธีการ HashCode () ให้บริการตารางแฮช
เมื่อเราใช้คลาสคอลเลกชันที่เริ่มต้นด้วยแฮชเช่นแฮชเช่น HashMap และ Hashset, HashCode () จะถูกเรียกโดยปริยายเพื่อสร้างความสัมพันธ์การแมปแฮช เราจะอธิบายสิ่งนี้ในภายหลัง ที่นี่เราจะมุ่งเน้นไปที่การเขียนวิธี HashCode () ก่อน
<java ที่มีประสิทธิภาพ> ให้วิธีการเขียนที่สามารถหลีกเลี่ยงความขัดแย้งแฮชในระดับที่ยิ่งใหญ่ที่สุด แต่โดยส่วนตัวแล้วฉันคิดว่าไม่จำเป็นต้องทำปัญหามากมายสำหรับแอปพลิเคชันทั่วไป หากคุณต้องการจัดเก็บวัตถุหลายหมื่นหรือหลายล้านชิ้นในแอปพลิเคชันของคุณคุณควรทำตามวิธีการที่ได้รับในหนังสืออย่างเคร่งครัด หากคุณกำลังเขียนแอปพลิเคชันขนาดเล็กและขนาดกลางหลักการต่อไปนี้ก็เพียงพอแล้ว:
มีความจำเป็นเพื่อให้แน่ใจว่าสมาชิกทั้งหมดของวัตถุ Coder สามารถสะท้อนได้ใน HashCode
สำหรับตัวอย่างนี้เราสามารถเขียนสิ่งนี้:
@Override สาธารณะ int hashCode () {int result = 17; ผลลัพธ์ = ผลลัพธ์ * 31 + name.hashCode (); ผลลัพธ์ = ผลลัพธ์ * 31 + อายุ; ผลการกลับมา; -ในกรณีที่ int result = 17 คุณสามารถเปลี่ยนเป็น 20, 50 ฯลฯ เมื่อเห็นสิ่งนี้ฉันอยากรู้อยากเห็นและต้องการดูว่าวิธีการ HashCode () ในคลาสสตริงถูกนำไปใช้อย่างไร ตรวจสอบเอกสารและรู้:
"ส่งคืนรหัสแฮชสำหรับสตริงนี้รหัสแฮชสำหรับวัตถุสตริงจะถูกคำนวณเป็น
S [0]*31^(N-1) + S [1]*31^(N-2) + ... + S [N-1]
การใช้เลขคณิต int โดยที่ s [i] เป็นอักขระ ith ของสตริง n คือความยาวของสตริงและ ^ หมายถึงการขยาย (ค่าแฮชของสตริงว่างเป็นศูนย์) "
คำนวณรหัส ASCII ของแต่ละอักขระไปยังพลังงาน N - 1 จากนั้นเพิ่ม จะเห็นได้ว่าซันมีความเข้มงวดมากในการใช้ HashCode สิ่งนี้สามารถหลีกเลี่ยง hashcode เดียวกันในสองสายที่แตกต่างกันในระดับที่ยิ่งใหญ่ที่สุด
แนวคิดของถังถูกอ้างอิงในการใช้งานตารางแฮชของ Oracle ดังที่แสดงในรูปด้านล่าง:
ดังที่เห็นได้จากรูปด้านบนตารางแฮชที่มีถังจะเทียบเท่ากับการรวมกันของตารางแฮชและรายการที่เชื่อมโยง นั่นคือรายการที่เชื่อมโยงจะถูกแขวนไว้ในแต่ละถังและแต่ละโหนดของรายการที่เชื่อมโยงจะถูกใช้เพื่อจัดเก็บวัตถุ Java ใช้วิธีการ HashCode () เพื่อพิจารณาว่าควรตั้งค่าวัตถุใดจากวัตถุและจากนั้นค้นหาในรายการที่เชื่อมโยงที่สอดคล้องกัน เป็นการดีถ้าวิธีการ HashCode () ของคุณมีความแข็งแกร่งเพียงพอแล้วแต่ละถังจะมีเพียงโหนดเดียวซึ่งจะบรรลุความซับซ้อนของเวลาระดับคงที่ของการดำเนินการค้นหา นั่นคือไม่ว่าวัตถุของคุณจะอยู่ในหน่วยความจำใดฉันสามารถค้นหาพื้นที่ผ่าน HashCode () ได้ทันทีโดยไม่ต้องข้ามและค้นหาตั้งแต่ต้นจนจบ นี่เป็นแอปพลิเคชั่นหลักของตารางแฮช
ชอบ:
เมื่อเราเรียกวิธีการใส่ (วัตถุ O) ของ HashSet เราจะค้นหาในถังที่สอดคล้องกันก่อนตามค่าส่งคืนของ O.HashCode () หากไม่มีโหนดในถังให้ใส่ o ที่นี่ หากมีโหนดอยู่แล้วให้แขวน o ไปที่ส่วนท้ายของรายการที่เชื่อมโยง ในทำนองเดียวกันเมื่อการโทรมี (Object O), Java จะค้นหาถังที่เกี่ยวข้องผ่านค่าส่งคืนของ HashCode () จากนั้นเรียกใช้วิธี Equals () ในการหมุนที่โหนดในรายการที่เชื่อมโยงที่สอดคล้องกันเพื่อพิจารณาว่าวัตถุในโหนดเป็นวัตถุที่คุณต้องการหรือไม่
มาใช้ตัวอย่างเพื่อสัมผัสกับกระบวนการนี้:
มาสร้างวัตถุ coder ใหม่สองรายการก่อน:
coder c1 = coder ใหม่ ("Bruce", 10); coder c2 = coder ใหม่ ("Bruce", 10);สมมติว่าเราได้เขียนวิธี Equals () ของ coder ใหม่โดยไม่ต้องเขียนวิธี HashCode () ใหม่:
@Override บูลีนสาธารณะเท่ากับ (วัตถุอื่น ๆ ) {system.out.println ("วิธีการเรียกใช้วิธีการเรียกใช้!"); ถ้า (อื่น ๆ == สิ่งนี้) ส่งคืนจริง; if (! (อินสแตนซ์อื่น ๆ ของ coder)) ส่งคืน false; coder o = (coder) อื่น ๆ ; return o.name.equals (ชื่อ) && o.age == อายุ; -จากนั้นเราสร้าง Hashset และใส่วัตถุ C1 ลงในชุด:
ตั้งค่า <coder> set = new hashset <coder> (); set.add (c1);
ดำเนินการอีกครั้ง:
System.out.println (set.contains (c2));
เราคาดหวังว่าวิธีการบรรจุ (C2) จะส่งคืนจริง แต่ในความเป็นจริงมันกลับมาเป็นเท็จ
ชื่อและอายุของ C1 และ C2 เหมือนกัน ทำไมฉันถึงโทรมี (C2) และส่งคืนเท็จหลังจากใส่ C1 ลงใน Hashset? นี่คือ hashcode () ที่ก่อให้เกิดปัญหา เนื่องจากคุณยังไม่ได้เขียนวิธี HashCode () ใหม่เมื่อ Hashset มองหา C2 จึงจะมองหามันในถังต่าง ๆ ตัวอย่างเช่นหาก C1 ถูกใส่ลงในถัง 05 มันจะถูกค้นหาใน Bucket 06 เมื่อค้นหา C2 ดังนั้นจึงไม่สามารถพบได้ ดังนั้นจุดประสงค์ของการเขียนใหม่ของเรา HashCode () คือเมื่อ A.Equals (b) ส่งคืนจริง HashCode () ของ A และ B ควรส่งคืนค่าเดียวกัน
ฉันถาม HashCode () เพื่อส่งคืนหมายเลขหมายเลขคงที่ทุกครั้ง
บางคนอาจเขียนใหม่เช่นนี้:
@Override สาธารณะ int hashCode () {return 10; -หากเป็นกรณีนี้ HashMap, Hashset และคลาสคอลเลกชันอื่น ๆ จะสูญเสีย "ความหมายแฮช" ของพวกเขา ในคำพูดของ <java ที่มีประสิทธิภาพ> ตารางแฮชจะลดลงในรายการที่เชื่อมโยง หาก HashCode () ส่งคืนหมายเลขเดียวกันทุกครั้งวัตถุทั้งหมดจะถูกวางไว้ในถังเดียวกันและทุกครั้งที่คุณทำการค้นหามันจะสำรวจรายการที่เชื่อมโยงซึ่งจะสูญเสียฟังก์ชั่นของการแฮชอย่างสมบูรณ์ ดังนั้นจึงเป็นการดีกว่าที่จะให้ HashCode ที่แข็งแกร่ง () เป็นความคิดที่ดี
ข้างต้นคือการแนะนำรายละเอียดทั้งหมดของบทความนี้เกี่ยวกับวิธีการเขียนใหม่ hashCode () และ Equals () ฉันหวังว่ามันจะเป็นประโยชน์กับทุกคน เพื่อนที่สนใจสามารถอ้างถึงหัวข้ออื่น ๆ ที่เกี่ยวข้องในเว็บไซต์นี้ต่อไป หากมีข้อบกพร่องใด ๆ โปรดฝากข้อความไว้เพื่อชี้ให้เห็น ขอบคุณเพื่อนที่ให้การสนับสนุนเว็บไซต์นี้!