Хеш-код ArrayList, який містить себе як елемент


38

Можемо чи ми знайти hashcodeв , listякий містить себе element?

Я знаю, що це погана практика, але це запитав інтерв'юер.

Коли я запустив наступний код, він видає 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, може хтось допоможе мені пояснити проблему № 2 - Як знайти це
Джокер

9
Як відповіли інші, це неможливо, за самим визначенням Listінтерфейсу, hashCodeсписок залежить від його членів. Зважаючи на те, що список є його власним членом, хеш-код залежить від його hashCode, який залежить від його hashCode... і так далі, викликаючи нескінченну рекурсію і StackOverflowErrorви натрапляєте на неї. Тепер питання: чому вам потрібен список, щоб він містив себе? Я можу вам гарантувати, що ви можете досягти того, що ви намагаєтеся зробити, кращим чином, не вимагаючи такого рекурсивного членства.
Олександр - Відновіть Моніку

Відповіді:


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і list2, як того вимагає загальний договір від Object.hashCode().

Для цього не потрібно, щоб реалізація виглядала саме так (див. Як обчислити хеш-код для потоку так само, як List.hashCode () для альтернативи), але правильний хеш-код для списку, що містить сам себе, бути числом, яке x == 31 + xповинно бути true, інакше кажучи, неможливо обчислити відповідне число.


1
@Holger, Eirc хоче замінити код усієї функції, hashCode()щоб повернутися 0. Це технічно вирішує, x == 31 + xале ігнорує вимогу, що х має бути принаймні 1.
bxk21

4
@EricDuminil Суть моєї відповіді полягає в тому, що контракт описує логіку, яка ArrayListреалізується буквально, що призводить до рекурсії, але не існує відповідної альтернативної реалізації. Зауважте, що я опублікував свою відповідь у той час, коли ОП вже зрозуміла, чому саме ця реалізація призводить до "" StackOverflowError, що було розглянуто в інших відповідях. Тому я зосередив увагу на загальній неможливості того, щоб відповідна реалізація завершилась у кінцевий час із значенням.
Хольгер

2
@pdem не має значення, чи використовує специфікація багатослівний опис алгоритму, формули, псевдокоду чи фактичного коду. Як сказано у відповіді, код, наведений у специфікації, не виключає альтернативних реалізацій загалом. Форма специфікації нічого не говорить про те, чи відбувся аналіз чи ні. Речення документації на інтерфейс " Хоча списки дозволяють містити себе як елементи, рекомендується вкрай обережно: методи рівняння та хеш-коди більше не визначені в такому списку " говорить про те, що такий аналіз відбувся.
Холгер

2
@pdem ви його реверсуєте. Я ніколи не говорив, що список повинен бути рівним через хеш-код. Переліки є рівними, за визначенням. Arrays.asList("foo")дорівнює Collections.singletonList("foo"), дорівнює List.of("foo"), дорівнює new ArrayList<>(List.of("foo")). Усі ці списки рівні, крапка. Потім, оскільки ці списки рівні, вони повинні мати однаковий хеш-код. Не навпаки. Оскільки вони повинні мати однаковий хеш-код, він повинен бути чітко визначений. Незалежно від того, як вони його визначили (ще в Java 2), він повинен бути чітко визначений, погоджуватися всіма реалізаціями.
Холгер

2
@pdem точно, власна реалізація, яка або не реалізує, Listабо має великий попереджувальний знак "не змішувати зі звичайними списками", порівняйте IdentityHashMapта його " Цей клас не є реалізацією карти загального призначення! " увага. У першому випадку ви вже чудово реалізуєте, Collectionале додаєте також методи доступу до індексу стилю списку. Тоді ніякого обмеження рівності не існує, але воно все одно працює безперебійно з іншими типами колекцій.
Холгер

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
Так, через рекурсивний стан
весна

Мало того, це визначено таким чином.
хриліс

14

Ви визначили (патологічний) список, який містить сам себе.

Чому існує StackOverflowError?

Згідно з javadocs (тобто специфікацією), хеш-код a Listвизначається функцією хеш-коду кожного з його елементів. Він говорить:

"Хеш-код списку визначений як результат такого обчислення:"

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

Отже, щоб обчислити хеш-код a, спершу обчисліть хеш-код a. Це нескінченно рекурсивно і швидко призводить до переповнення стека.

Чи можливо знайти хеш-код таким чином?

Ні. Якщо розглядати алгоритмічну специфікацію вище в математичному відношенні, хеш-код вмісту, Listякий містить себе, є не обчислюваною функцією . Неможливо обчислити його таким чином (використовуючи вищевказаний алгоритм) чи будь-яким іншим способом .


Я не знаю, чому ця відповідь нижча, ніж дві інші, оскільки вона насправді відповідає на питання ОП поясненнями.
Нейт

1
@Neyt деякі користувачі читають відповіді лише вгорі.
Холгер

8

Ні, документація має відповідь

У документації структури Списку прямо зазначено:

Примітка. Хоча списки дозволяють містити себе як елементи, рекомендується вкрай обережно: методи такого рівня, як і хеш-коди, більше не визначені в такому списку.

Не можна сказати більше того - згідно зі специфікацією Java, ви не зможете обчислити хеш-код для списку, який містить сам себе; інші відповіді детально пояснюють, чому це так, але справа в тому, що це відомо і навмисно.


1
Ви сказали, чому це поза специфікацією, і це пояснює, що це не помилка. У цій частині бракувало інших відповідей.
pdem

3

Відповідь Равіндри дає хороше пояснення для пункту 1. Прокоментувати питання 2:

Чи можливо знайти хеш-код таким чином?

Тут щось кругле. Принаймні один з цих 2 повинен бути помилковим у контексті цієї помилки переповнення стека:

  • що хеш-код списку повинен враховувати ті його елементи
  • що це нормально, щоб список був його власним елементом

Тепер, оскільки ми маємо справу з ArrayList, перша точка виправлена. Іншими словами, можливо, вам потрібна інша реалізація, щоб мати змогу осмислено обчислити хеш-код рекурсивного списку ... Можна розширити ArrayListі пропустити додавання хеш-кодів елементів, щось подібне

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

Використовуючи такий клас замість ArrayList, ви могли.

З ArrayList, другий пункт неправильний. Отже, якщо інтерв'юер мав на увазі "Чи можна знайти хеш-код таким чином (зі списком масиву)?" , то відповідь ні, тому що це абсурд.


1
Розрахунок хеш - код уповноважена в Listконтракті . Жодна дійсна реалізація не може пропустити себе. З специфікації ви можете зрозуміти, що якщо ви знайдете intчисло, яке x == 31 + xє true, то ви можете застосувати дійсну скорочену версію ...
Holger

Я не зовсім зрозумів, що говорить @Holger. Але є 2 проблеми з рішенням: По-перше: це вирішує проблему лише тоді, коли цей список є предметом сам по собі, а не тоді, коли список, де сам елемент елемента (більш глибокі шари рекурсії) Другий: Якщо Список пропустив сам його може дорівнювати порожньому списку.
Йонас Мішель

1
@JonasMichel Я не дуже пропонував рішення. Я просто по-філософськи дискутую навколо безглуздості питання 2. Якщо ми повинні обчислити хеш-код для такого списку, він не спрацює, якщо ми не усунемо обмеження списку масивів (і Холгер підкреслює це, кажучи, що це Listформально диктує логіка коду хеш-коду, яку слід виконувати впровадженням)
ernest_k

Моє (обмежене) розуміння полягає в тому, що Listзабезпечує обчислення хеш-коду, але ми можемо його перекрити. Єдиною реальною вимогою є загальні хеш-коди: якщо об’єкти рівні, то хеш-коди повинні бути рівними. Ця реалізація відповідає цій вимозі.
Teepeemm

1
@Teepeemm List- це інтерфейс. Він визначає договір , він не забезпечує виконання. Звичайно, договір охоплює і те, і інше, і рівність, і хеш-код. Оскільки загальним договором рівності є те, що він повинен бути симетричним, якщо ви зміните одну реалізацію списку, вам доведеться змінити всі існуючі реалізації списку, інакше ви навіть порушите основний контракт java.lang.Object.
Хольгер

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);
    }

}   
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.