我正在学习Java记录,预览功能和我得到一个StackOverflow异常,当我运行下面的一段代码。
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.StackOverflowError
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(),我得到StackOverflow?另外,当我打印时,我看到StackOverflow john.hashCode()
这是因为你试图打印一个学生注册的课程列表,而这些课程中的每一门课程都有同一个学生被列为注册课程,所以你有一个循环引用。所以你有一个循环参考。Course -> Student -> Course -> Student等等。
当你覆盖Course toString()时,你只打印了名字,所以这不再是一个问题。
你的结构如下 Student
-> List<Course>
和 Course
-> Student
构建了一个循环链和 toString
方法在循环中运行。
从 javadocs 的记录。
Object.toString() 方法是由所有的组件字段派生出来的。
当你做 System.out.println(john)
toString
被称为 Student
约翰就把它委托给了所有的报名者。Course
的实例,其中 toString
被称为和作为 Course
实例有一个对 Student
实例再......整个链条运行到一个 StackOverflowException
被抛出。
正如其他人指出的那样,问题在于你的记录中有一个循环引用。
你之所以能够创建这样的循环引用,是因为记录只是浅层次的不可变。在你的情况下。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);
这样就不可能有循环引用了。但看起来你的模型需要这些循环引用。所以你可能需要改变模型。
这与 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
里面。这个循环将继续,直到你得到 StackOverflow
异常。
为了避免这种情况,你应该重新安排你的代码,使它们不相互依赖。你可以为每个实例创建一个id。一个例子是
public record Course(String name, String studentId) {}
public record Student(String name, String studentId, List<Course> courses) {}