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);
Это делает невозможным создание круговой ссылки.
Но похоже, что вашей модели требуются эти круговые ссылки. Так что вам может потребоваться сменить модель.