Java记录 StackOverflow RuntimeException

问题描述 投票:0回答:1

我正在学习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()

java stack-overflow tostring runtimeexception java-record
1个回答
2
投票

这是因为你试图打印一个学生注册的课程列表,而这些课程中的每一门课程都有同一个学生被列为注册课程,所以你有一个循环引用。所以你有一个循环参考。Course -> Student -> Course -> Student等等。

当你覆盖Course toString()时,你只打印了名字,所以这不再是一个问题。


2
投票

你的结构如下 Student-> List<Course>Course-> Student 构建了一个循环链和 toString 方法在循环中运行。

javadocs 的记录。

Object.toString() 方法是由所有的组件字段派生出来的。

当你做 System.out.println(john) toString 被称为 Student 约翰就把它委托给了所有的报名者。Course 的实例,其中 toString 被称为和作为 Course 实例有一个对 Student 实例再......整个链条运行到一个 StackOverflowException 被抛出。


2
投票

正如其他人指出的那样,问题在于你的记录中有一个循环引用。

你之所以能够创建这样的循环引用,是因为记录只是浅层次的不可变。在你的情况下。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);

这样就不可能有循环引用了。但看起来你的模型需要这些循环引用。所以你可能需要改变模型。


1
投票

这与 Record#toString 特别是。仔细看一下你的申报。

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

Course#toString 需要 Student#toStringStudent#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) {}
© www.soinside.com 2019 - 2024. All rights reserved.