【问题标题】:Java Records StackOverflow RuntimeExceptionJava 记录 StackOverflow RuntimeException
【发布时间】:2020-09-20 15:42:21
【问题描述】:

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

此代码打印 John : Enrolled for Math,Enrolled for History 。有人可以解释为什么如果我不覆盖 toString() 我会得到 StackOverflow?当我打印 john.hashCode()

时,我也看到了 StackOverflow

【问题讨论】:

  • hashCode()equals() 相同。
  • 请注意,没有记录很容易构造相同的问题。 var x = new ArrayList(); var y = new ArrayList(); x.add(y); y.add(x),然后在 xy 上调用 toString()。在存在周期的情况下,几乎没有“不错的”toString() 方法可以免受 SOE 的影响。

标签: java stack-overflow tostring java-14 java-record


【解决方案1】:

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

您可以创建这样的循环引用,因为 Records 只是浅不可变的。
在您的情况下,enrolledList&lt;Course&gt;,您使用空的 ArrayList&lt;&gt; 进行初始化。

为避免这种情况,您应该在创建记录时制作一份防御性副本:

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

这使得循环引用变得不可能。
但看起来您的模型需要那些循环引用。所以你可能需要改变模型。

【讨论】:

  • 谢谢,我喜欢这个主意。
  • @DebapriyaBiswas 这与john.enrolled().add(new Course("Enrolled for Math", null)); 基本相同。因此,如果您甚至没有制作副本,您的代码将与john.enrolled().add(new Course("Enrolled for Math", null)); 一起正常工作。不太确定你喜欢这个解决方案的哪一点。
  • @DebapriyaBiswas 尝试编译此代码:pastiebin.com/5ed7242b1283a 它实际上与此答案相同。
  • 不,我的回答改变了设计 - 通过强制不变性。
  • @AniketSahrawat 我同意你的看法。我喜欢约翰的想法,但没有解决手头的问题。我正在寻找一种好的设计模式实践来避免将来出现循环引用。您是否建议在这种情况下覆盖 hashCode()、toString()、equals()。
【解决方案2】:

这与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 都将被打印出来。那些Courses 需要Student,所以他们会在里面打印Student。这个循环会一直持续到你得到StackOverflow 异常。

为避免这种情况,您应该重新排列代码,使它们不会相互依赖。您可以为每个实例创建一个 id。一个例子是:

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

【讨论】:

    【解决方案3】:

    您的结构如下Student-> List&lt;Course&gt;Course-> Student 构建一个循环链,toString 方法循环运行。

    来自javadocs的记录:

    Object.toString() 方法派生自所有组件 字段。

    当您为Student john 调用System.out.println(john) toString 时,它被委派给所有已注册的Course 实例,其中toString 被调用,并且作为Course 实例引用又是一个Student 实例...整个链运行直到抛出StackOverflowException

    【讨论】:

      【解决方案4】:

      这是因为您正在尝试打印学生已注册的课程列表,而这些课程中的每门课程都将同一名学生列为已注册。所以你有一个循环引用。课程 -> 学生 -> 课程 -> 学生等

      当您覆盖 Course toString() 时,您只打印了名称,因此这不再是问题。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-08-21
        • 1970-01-01
        • 1970-01-01
        • 2022-08-08
        • 1970-01-01
        相关资源
        最近更新 更多