Scala内部样例类序列化失败:ScalaTest与REPL差异排查
ScalaTest中样例类序列化抛出
NotSerializableException的原因分析 问题场景
尝试对Scala中的极简样例类执行基础序列化操作,代码如下:
import org.scalatest.wordspec.AnyWordSpecLike import java.io.{ByteArrayOutputStream, ObjectOutputStream} class PersistenceSpec extends AnyWordSpecLike{ case class TestClass(name: String) extends Serializable def serializeSomething(): ByteArrayOutputStream = { val testItem = TestClass("My Thing") val bos: ByteArrayOutputStream = new ByteArrayOutputStream() val oos = new ObjectOutputStream(bos) oos.writeObject(testItem) bos } "serializeSomething" when { "executed" must { "successfully serialize" in { val outputStream = serializeSomething() println(outputStream.toString()) } } } }
运行ScalaTest测试时,调用oos.writeObject(testItem)抛出java.io.NotSerializableException,但存在以下矛盾现象:
- Scala样例类默认实现
Serializable接口; - 相同代码在REPL中执行可成功序列化;
- 将序列化对象替换为字符串"Hello"时,ScalaTest测试能正常运行。
原因解析
核心问题出在内部类的序列化规则上:
- 你将
TestClass定义为PersistenceSpec测试类的内部类,Java/Scala中,非静态内部类会隐式持有外部类实例的引用。当序列化内部类实例时,JVM会尝试同时序列化它所引用的外部类实例。 - ScalaTest的测试执行环境中,
PersistenceSpec的实例(即测试类本身)并未实现Serializable接口,因此序列化TestClass实例时,JVM尝试序列化外部的PersistenceSpec实例就会抛出NotSerializableException。 - REPL环境对内部类的处理逻辑不同:交互环境中定义的类会被当作顶级类处理,不会持有外部类引用,因此序列化时不会触发外部类的序列化检查,自然可以成功。
- 替换为字符串"Hello"能正常运行,是因为字符串本身是顶级类且实现了
Serializable,不存在外部类引用的问题。
解决办法
方案1:将样例类移为顶级类
把TestClass移到测试类外部,作为独立的顶级类定义:
import org.scalatest.wordspec.AnyWordSpecLike import java.io.{ByteArrayOutputStream, ObjectOutputStream} case class TestClass(name: String) class PersistenceSpec extends AnyWordSpecLike{ def serializeSomething(): ByteArrayOutputStream = { val testItem = TestClass("My Thing") val bos: ByteArrayOutputStream = new ByteArrayOutputStream() val oos = new ObjectOutputStream(bos) oos.writeObject(testItem) bos } "serializeSomething" when { "executed" must { "successfully serialize" in { val outputStream = serializeSomething() println(outputStream.toString()) } } } }
方案2:将样例类放在嵌套对象中
在测试类内部定义一个嵌套对象,把TestClass放在该对象中,这样它就不会持有外部测试类的引用:
import org.scalatest.wordspec.AnyWordSpecLike import java.io.{ByteArrayOutputStream, ObjectOutputStream} class PersistenceSpec extends AnyWordSpecLike{ object TestModels { case class TestClass(name: String) } def serializeSomething(): ByteArrayOutputStream = { val testItem = TestModels.TestClass("My Thing") val bos: ByteArrayOutputStream = new ByteArrayOutputStream() val oos = new ObjectOutputStream(bos) oos.writeObject(testItem) bos } "serializeSomething" when { "executed" must { "successfully serialize" in { val outputStream = serializeSomething() println(outputStream.toString()) } } } }
内容的提问来源于stack exchange,提问作者JoeMjr2
相关产品推荐
相关产品推荐

