รหัสแฮชของ ArrayList ที่มีตัวมันเองเป็นองค์ประกอบ


38

เราสามารถหาhashcodeของlistที่มีตัวมันเองเป็นelement?

ฉันรู้ว่านี่เป็นวิธีปฏิบัติที่ไม่ดี แต่นี่คือสิ่งที่ผู้สัมภาษณ์ถาม

เมื่อฉันรันรหัสต่อไปนี้มันจะพ่น a StackOverflowError:

public class Main {
    public static void main(String args[]) {
        ArrayList<ArrayList> a = new ArrayList();
        a.add(a);
        a.hashCode();
    }
}

ตอนนี้ที่นี่ฉันมีสองคำถาม:

  1. ทำไมถึงมีStackOverflowError?
  2. เป็นไปได้ไหมที่จะหารหัสแฮชด้วยวิธีนี้

7
เนื่องจากคุณเพิ่มรายการลงในตัวเอง ลอง a.hashCode () โดยไม่มีคำสั่งเพิ่ม
Jens

เมื่อคุณใส่วัตถุลงในรายการอาร์เรย์คุณกำลังจัดเก็บการอ้างอิงไปยังวัตถุในกรณีของคุณคุณจะใส่แม่มด ArrayList เป็นการอ้างอิงตัวเอง
Vishwa Ratna

ที่คล้ายกัน: stackoverflow.com/questions/42566559/…
Vishwa Ratna

ตกลงฉันได้เหตุผลว่าทำไมมี stackoverflow บางคนสามารถช่วยฉันอธิบายปัญหาหมายเลข 2- วิธีการค้นหานี้
Joker

9
ตามที่คนอื่น ๆ ตอบแล้วสิ่งนี้เป็นไปไม่ได้โดยนิยามของListอินเทอร์เฟซมากhashCodeรายการนั้นขึ้นอยู่กับสมาชิก เนื่องจากรายการนั้นเป็นสมาชิกของตัวเองรหัสแฮชจะขึ้นอยู่กับว่ามันhashCodeขึ้นอยู่กับhashCode... และอื่น ๆ ทำให้เกิดการเรียกซ้ำแบบไม่สิ้นสุดและStackOverflowErrorคุณกำลังทำงานอยู่ ตอนนี้คำถามคือ: ทำไมคุณต้องการรายการที่จะมีตัวเอง? ฉันรับประกันได้ว่าคุณจะประสบความสำเร็จในทุกสิ่งที่คุณพยายามทำในวิธีที่ดีกว่าโดยไม่ต้องเป็นสมาชิกแบบเรียกซ้ำแบบนี้
Alexander - Reinstate Monica

คำตอบ:


36

รหัสกัญชาให้สอดคล้องกับListการใช้งานที่ได้รับการระบุในอินเตอร์เฟซ :

ส่งคืนค่ารหัสแฮชสำหรับรายการนี้ รหัสแฮชของรายการถูกกำหนดให้เป็นผลลัพธ์ของการคำนวณต่อไปนี้:

 int hashCode = 1;
 for (E e : list)
     hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

เพื่อให้แน่ใจว่านี้list1.equals(list2)หมายความว่าlist1.hashCode()==list2.hashCode()สำหรับการใด ๆ สองรายการlist1และตามที่กำหนดไว้ในสัญญาโดยทั่วไปของlist2Object.hashCode()

สิ่งนี้ไม่ต้องการให้การใช้งานมีลักษณะเช่นนั้น (ดูวิธีการคำนวณรหัสแฮชสำหรับสตรีมในลักษณะเดียวกับ List.hashCode ()เพื่อเป็นทางเลือก) แต่รหัสแฮชที่ถูกต้องสำหรับรายการที่มีตัวเองเท่านั้น เป็นตัวเลขที่x == 31 + xต้องมีtrueในคำอื่น ๆ มันเป็นไปไม่ได้ที่จะคำนวณจำนวนที่สอดคล้องกัน


1
@Holger, Eirc ต้องการที่จะเปลี่ยนรหัสของฟังก์ชั่นทั้งการกลับมาhashCode() 0เทคนิคนี้แก้ได้x == 31 + xแต่ไม่สนใจข้อกำหนดที่ x ต้องมีอย่างน้อย 1
bxk21

4
@EricDuminil จุดของคำตอบของฉันคือสัญญาอธิบายถึงตรรกะที่ArrayListดำเนินการตามตัวอักษรอย่างแท้จริงนำไปสู่การสอบถามซ้ำ แต่ไม่มีการใช้งานทางเลือกที่สอดคล้องกัน โปรดทราบว่าฉันโพสต์คำตอบของฉันในเวลาที่ OP เข้าใจแล้วว่าทำไมการใช้งานเฉพาะนี้นำไปสู่ ​​a StackOverflowErrorซึ่งได้รับการแก้ไขในคำตอบอื่น ๆ ดังนั้นฉันจึงมุ่งเน้นไปที่ความเป็นไปไม่ได้ทั่วไปของการดำเนินการตามข้อกำหนดให้เสร็จภายในเวลาที่กำหนดด้วยค่า
Holger

2
@pdem ไม่สำคัญว่าข้อมูลจำเพาะจะใช้คำอธิบายคร่าวๆของอัลกอริทึมสูตรรหัสหลอกหรือรหัสจริง ดังที่ได้กล่าวไว้ในคำตอบรหัสที่ให้ไว้ในข้อมูลจำเพาะไม่ได้ขัดขวางการใช้งานทางเลือกโดยทั่วไป รูปแบบของสเป็คไม่ได้พูดอะไรเกี่ยวกับการวิเคราะห์ที่เกิดขึ้นหรือไม่ ประโยคของเอกสารของอินเทอร์เฟซ“ ในขณะที่รายการอนุญาตให้มีองค์ประกอบเป็นของตัวเองได้ขอแนะนำให้ใช้ความระมัดระวังอย่างยิ่ง: วิธีการที่เท่าเทียมกันและ hashCode ไม่ได้ถูกนิยามไว้ในรายการดังกล่าวอีกต่อไป ” แสดงให้เห็นว่า
Holger

2
@pdem คุณกำลังย้อนกลับ ฉันไม่เคยบอกว่ารายการจะต้องเท่ากันเพราะรหัสแฮช รายการจะเท่ากันตามคำจำกัดความ Arrays.asList("foo")เท่ากับCollections.singletonList("foo")เท่ากับเท่ากับList.of("foo") new ArrayList<>(List.of("foo"))รายการทั้งหมดนี้มีค่าเท่ากัน จากนั้นเนื่องจากรายการเหล่านี้เท่ากันจึงต้องมีรหัสแฮชเดียวกัน ไม่ใช่วิธีอื่น ๆ เนื่องจากต้องมีรหัสแฮชเดียวกันจึงต้องมีการกำหนดอย่างดี ไม่ว่าพวกเขาจะกำหนดไว้อย่างไร (กลับมาใน Java 2) จะต้องมีการกำหนดไว้อย่างดีเพื่อให้ได้รับความเห็นชอบจากการใช้งานทั้งหมด
Holger

2
@pdem การใช้งานที่กำหนดเองซึ่งไม่ได้ติดตั้งListหรือมีสัญญาณเตือนขนาดใหญ่“ อย่าปะปนกับรายการธรรมดา” เปรียบเทียบกับIdentityHashMapคลาสนี้ไม่ใช่การใช้แผนที่ทั่วไป! " คำเตือน. ในกรณีก่อนหน้านี้คุณสามารถใช้งานได้แล้วCollectionแต่เพิ่มวิธีการเข้าถึงดัชนีตามสไตล์รายการด้วยเช่นกัน จากนั้นไม่มีข้อ จำกัด ด้านความเท่าเทียมกัน แต่ก็ยังทำงานได้อย่างราบรื่นกับคอลเล็กชันประเภทอื่น
Holger

23

ตรวจสอบการใช้โครงกระดูกของhashCodeวิธีการในAbstractListชั้นเรียน

public int hashCode() {
    int hashCode = 1;
    for (E e : this)
        hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());
    return hashCode;
}

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


ดังนั้นคำตอบคือไม่มีทางที่จะหารหัสแฮชด้วยวิธีนี้?
โจ๊กเกอร์

3
ใช่เพราะสภาพเวียนเกิด
สปริง

ไม่เพียงแค่นั้นมันถูกกำหนดด้วยวิธีนี้
chrylis -on strike-

14

คุณได้กำหนดรายการ (พยาธิวิทยา) ที่มีตัวมันเอง

ทำไมมีStackOverflowError?

ตามjavadocs (เช่นสเปค), hashcode ของ a Listถูกกำหนดให้กับฟังก์ชั่นของ hashcode ของแต่ละองค์ประกอบ มันบอกว่า:

"รหัสแฮชของรายการถูกกำหนดให้เป็นผลลัพธ์ของการคำนวณต่อไปนี้:"

int hashCode = 1;
    for (E e : list)
         hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

ดังนั้นในการคำนวณ hashcode ของaคุณต้องคำนวณ hashcode ของaก่อน นั่นคือเรียกซ้ำไม่สิ้นสุดและนำไปสู่การล้นสแต็กอย่างรวดเร็ว

เป็นไปได้ไหมที่จะหารหัสแฮชในลักษณะนี้

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


ฉันไม่รู้ว่าทำไมคำตอบนี้จึงต่ำกว่าอีกสองคำตอบเพราะจริง ๆ แล้วตอบคำถาม OP ด้วยคำอธิบาย
Neyt

1
@ ไม่อนุญาตให้ผู้ใช้บางคนอ่านคำตอบที่ด้านบนสุดเท่านั้น
Holger

8

ไม่เอกสารมีคำตอบ

เอกสารของโครงสร้างรายการอย่างชัดเจน:

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

ไม่มีอะไรจะพูดมากไปกว่านั้น - ตามข้อกำหนดของจาวาคุณจะไม่สามารถคำนวณ hashCode สำหรับรายการที่มีตัวมันเอง คำตอบอื่น ๆ ให้รายละเอียดว่าทำไมถึงเป็นเช่นนั้น แต่ประเด็นก็คือมันเป็นที่รู้จักและจงใจ


1
คุณบอกว่าทำไมมันอยู่นอกข้อกำหนดดังนั้นจึงอธิบายว่าไม่ใช่ข้อผิดพลาด นั่นคือส่วนที่ขาดไปจากคำตอบอื่น ๆ
pdem

3

คำตอบของ Ravindra ให้คำอธิบายที่ดีสำหรับประเด็นที่ 1 หากต้องการแสดงความคิดเห็นในคำถาม 2:

เป็นไปได้ไหมที่จะหารหัสแฮชในลักษณะนี้

มีบางอย่างที่นี่เป็นวงกลม อย่างน้อยหนึ่งใน 2 เหล่านี้ต้องผิดในบริบทของข้อผิดพลาดล้นสแต็กนี้:

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

ตอนนี้เนื่องจากเรากำลังจัดการกับArrayListจุดแรกได้รับการแก้ไข ในคำอื่น ๆ บางทีคุณอาจจำเป็นต้องมีการดำเนินการที่แตกต่างกันเพื่อให้สามารถมีความหมายคำนวณรหัสแฮชของรายการ recursive ... หนึ่งสามารถขยายArrayListและข้ามการเพิ่มรหัสกัญชาขององค์ประกอบบางอย่างเช่น

for (E e : this)
  if(e == this) continue; //contrived rules
  hashCode = 31*hashCode + (e==null ? 0 : e.hashCode());

คุณสามารถใช้คลาสนี้แทนArrayListได้

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


1
การคำนวณรหัสกัญชาเป็นไปตามข้อบังคับสัญญา ไม่มีการใช้งานที่ถูกต้องสามารถข้ามได้ จากสเปคที่คุณสามารถได้รับมาว่าถ้าคุณพบจำนวนที่เป็นแล้วคุณสามารถดำเนินการที่ถูกต้องตัดสั้น ...Listintx == 31 + xtrue
โฮล

ฉันไม่เข้าใจสิ่งที่ @Holger พูด แต่มีปัญหาเกี่ยวกับการแก้ปัญหา 2 ข้อแรก: สิ่งนี้จะแก้ไขปัญหาเฉพาะเมื่อรายการนี้เป็นรายการของตัวเองและไม่ใช่ถ้ารายการที่รายการของรายการของตัวเอง (เรียกซ้ำชั้นลึก) ที่สอง: หากรายการข้ามตัวเอง อาจเท่ากับรายการว่างเปล่า
Jonas Michel

1
@JonasMichel ฉันไม่ได้เสนอวิธีแก้ปัญหา ฉันแค่ปรัชญาการโต้วาทีรอบ ๆ ไร้สาระของคำถาม 2. ถ้าเราจะต้องคำนวณรหัสกัญชาสำหรับรายการดังกล่าวแล้วก็จะไม่ทำงานจนกว่าเราจะลบข้อ จำกัด ของรายการอาร์เรย์ (และตอกย้ำ Holger ที่ว่าด้วยการบอกว่าListคำสั่งอย่างเป็นทางการ ตรรกะรหัสกัญชาที่จะปฏิบัติตามโดยการใช้งาน)
ernest_k

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

1
@Teepeemm Listเป็นอินเทอร์เฟซ มันกำหนดสัญญามันไม่ได้ให้การดำเนินการ แน่นอนสัญญาครอบคลุมทั้งความเท่าเทียมกันและรหัสแฮช java.lang.Objectเนื่องจากสัญญาทั่วไปของความเท่าเทียมกันก็คือว่ามันจะต้องมีความสมมาตรถ้าคุณเปลี่ยนการดำเนินรายการหนึ่งคุณจะต้องเปลี่ยนทุกการใช้งานรายการที่มีอยู่มิฉะนั้นคุณก็จะทำลายสัญญาพื้นฐานของ
Holger

1

เพราะเมื่อคุณเรียกฟังก์ชั่นเดียวกันกับจากฟังก์ชั่นเดียวกันมันจะสร้างเงื่อนไขการเรียกซ้ำซึ่งไม่สิ้นสุด และเพื่อป้องกันการดำเนินการนี้ JAVA จะกลับมาjava.lang.StackOverflowError

ด้านล่างเป็นตัวอย่างโค้ดที่อธิบายสถานการณ์ที่คล้ายกัน:

public class RefTest {

    public static void main(String... strings) {
        RefTest rf = new RefTest();
        rf.message(message("test"));
    }

    public static String message2(String s){
        return message(s);
    }

    public static String message(String s){
        return message2(s);
    }

}   
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.