Java Records StackOverflow RuntimeException

Я изучаю записи Java, функцию предварительного просмотра и получаю исключение Stackru, когда запускаю приведенный ниже фрагмент кода.

    import java.util.ArrayList;
    import java.util.List;
    import java.util.stream.Collectors;
    public class Example {
        public record Course(String name, Student topper) { }
        public record Student(String name, List<Course> enrolled) {}

        public static void main(String[] args) {
            Student john = new Student("John", new ArrayList<>());
            john.enrolled().add(new Course("Enrolled for Math", john));
            john.enrolled().add(new Course("Enrolled for History", john));
            System.out.println(john);
        }
    }


Below is the exception trace :


 java --enable-preview Example
 Exception in thread "main" java.lang.StackruError
    at Example$Course.toString(Example.java:6)
    at java.base/java.lang.String.valueOf(String.java:3388)
    at java.base/java.lang.StringBuilder.append(StringBuilder.java:167)
    at java.base/java.util.AbstractCollection.toString(AbstractCollection.java:457)

Из исключения я понимаю, что это связано с toString(), и когда я переопределяю toString() в записях, как показано ниже, я не вижу исключения.

// code with implementation of toString()
public class Example {

    public record Course(String name, Student topper) {
    public String toString()
        {
            return name;
        }
   }
   public record Student(String name, List<Course> enrolled) {
   public String toString()
        {
             return this.name+" : "+enrolled.stream().map(s->s.toString()).collect(Collectors.joining(","));
        }
   }
   public static void main(String... args) {
       Student john = new Student("John", new ArrayList<>());
       john.enrolled().add(new Course("Enrolled for Math", john));
       john.enrolled().add(new Course("Enrolled for History", john));
       System.out.println(john);
   }
}

Этот код печатает Джон: зачислен на математику, зачислен на историю. Может кто-нибудь объяснить, почему, если я не переопределяю toString(), я получаю Stackru? Также я вижу Stackru при печатиjohn.hashCode()

4 ответа

Это не имеет ничего общего с Record#toStringособенно. Присмотритесь к своей декларации:

public record Course(String name, Student topper) {}
public record Student(String name, List<Course> enrolled) {}

Course#toString потребности Student#toString а также Student#toString потребности Course#toString. Итак, если вы попытаетесь распечататьStudent тогда все записались Courseбудут напечатаны вместе. ТеCourseпотребности Student так они напечатают Studentвнутри. Этот цикл будет продолжаться, пока вы не получитеStackru исключение.

Чтобы избежать такого сценария, вам следует изменить код, чтобы они не зависели друг от друга. Вы можете создать идентификатор для каждого экземпляра. Примером может быть:

public record Course(String name, String studentId) {}
public record Student(String name, String studentId, List<Course> courses) {}

Это потому, что вы пытаетесь распечатать список курсов, на которые зачислен студент, и на каждом из этих курсов тот же самый студент указан как зачисленный. Итак, у вас есть круговая ссылка. Курс -> Студент -> Курс -> Студент и т. Д.

Когда вы переопределяли Course toString(), вы печатали только имя, поэтому это больше не было проблемой.

Ваша структура следующая Student-> List<Course> а также Course-> Student который строит круговую цепочку и toString метод работает в цикле.

Из javadocs для записей:

Методы Object.toString() являются производными от всех полей компонента.

Когда ты делаешь System.out.println(john) toString называется для Student Джон, тогда это делегируется всем зарегистрированным Course случаи, для которых toString называется и как Course экземпляр имеет ссылку на Student снова... вся цепочка работает до тех пор, пока StackruException брошен.

Как указывали другие, проблема в том, что у вас есть циклическая ссылка в вашей записи.

Вы можете создать такую ​​круговую ссылку, потому что записи являются неизменяемыми только поверхностно.
В твоем случае,enrolled это List<Course>, который вы инициализируете пустым ArrayList<>.

Чтобы этого избежать, вы должны сделать защитную копию при создании записи:

public record Student(String name, List<Course> enrolled) {
    public Student {
        enrolled = List.copyOf(enrolled);
    }
}

Затем вы должны предоставить список курсов при создании студента:

List<Course> courses = List.of(
        new Course("Enrolled for Math", null),
        new Course("Enrolled for History", null)
);
Student john = new Student("John", courses);

Это делает невозможным создание круговой ссылки.
Но похоже, что вашей модели требуются эти круговые ссылки. Так что вам может потребоваться сменить модель.

Другие вопросы по тегам