如何用Scala类继承Java类实现Writable并解决读取异常
解决Scala继承Java类实现Writable时的无参构造方法问题
问题分析
你遇到的NoSuchMethodException核心原因是:Hadoop的ReflectionUtils在读取SequenceFile时,必须通过无参构造方法实例化Writable对象,但你的代码存在两个关键问题:
- 从异常里的
$iwC$$iwC$ClassBWritable可以看出,你是在Spark REPL(交互式Shell)里定义的类,这类类会被自动包装在Scala生成的内部作用域类中,Hadoop的反射机制无法正确访问嵌套内部类的无参构造。 - 即使你写了辅助无参构造,REPL的类加载方式也会导致反射无法识别这个构造方法。
解决方案
方案1:将类定义为顶级类(推荐生产环境使用)
把ClassBWritable放到单独的Scala源文件中编译,避免REPL的内部类包装问题,让Hadoop反射能正常访问无参构造。
修正后的ClassBWritable.scala代码:
import your.package.ClassA // 替换成ClassA实际的包路径 import java.io.{DataOutput, DataInput} import org.apache.hadoop.io.Writable class ClassBWritable(field1: Byte, field2: Float) extends ClassA(field1, field2) with Writable { // 无参辅助构造,调用主构造并传入默认值 def this() = this(0.toByte, 0.0F) override def write(out: DataOutput): Unit = { out.writeByte(getField1) out.writeFloat(getField2) } override def readFields(in: DataInput): Unit = { setField1(in.readByte()) setField2(in.readFloat()) } }
编译成jar包后,通过spark-shell --jars your.jar引入,或者在Spark项目中添加依赖,此时读取SequenceFile就能正常实例化对象了。
方案2:用包装类替代继承(灵活规避构造问题)
如果不想处理继承带来的构造限制,可以创建一个包装类持有ClassA实例,实现Writable接口,这种方式更灵活:
import your.package.ClassA import java.io.{DataOutput, DataInput} import org.apache.hadoop.io.Writable import org.apache.hadoop.io.LongWritable class ClassAWritableWrapper(var classA: ClassA) extends Writable { // 必须的无参构造 def this() = this(new ClassA(0.toByte, 0.0F)) override def write(out: DataOutput): Unit = { out.writeByte(classA.getField1) out.writeFloat(classA.getField2) } override def readFields(in: DataInput): Unit = { val field1 = in.readByte() val field2 = in.readFloat() classA = new ClassA(field1, field2) } }
对应的读写代码调整为:
// 保存 myrdd.map(pair => (new LongWritable(pair.longNumber), new ClassAWritableWrapper(new ClassA(pair.my_byte_value, pair.my_float_value)))) .saveAsSequenceFile(mypath) // 读取 val df = sc.sequenceFile(mypath, classOf[LongWritable], classOf[ClassAWritableWrapper]) .map(row => (row._1.get(), row._2.classA.getField1(), row._2.classA.getField2())) .take(1)
方案3:REPL临时测试用静态内部类
如果必须在REPL中快速测试,可以把类包裹在单例对象里,作为静态内部类使用:
object WritableHelpers { class ClassBWritable(field1: Byte, field2: Float) extends ClassA(field1, field2) with Writable { def this() = this(0.toByte, 0.0F) override def write(out: DataOutput): Unit = { out.writeByte(getField1) out.writeFloat(getField2) } override def readFields(in: DataInput): Unit = { setField1(in.readByte()) setField2(in.readFloat()) } } }
读取时指定完整类路径:
val df = sc.sequenceFile(mypath, classOf[LongWritable], classOf[WritableHelpers.ClassBWritable]) .map(row => (row._1.get(), row._2.getField1(), row._2.getField2())) .take(1)
验证注意事项
- 确保无参构造方法是public的(Scala辅助构造默认是public,无需额外声明)
- 检查父类
ClassA的构造方法参数类型是否和你传入的默认值匹配 - 生产环境尽量避免在REPL中定义需要被Hadoop反射实例化的类
内容的提问来源于stack exchange,提问作者Marat Saitkulov
相关产品推荐
相关产品推荐

