You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Scala类继承Java类实现Writable并解决读取异常

解决Scala继承Java类实现Writable时的无参构造方法问题

问题分析

你遇到的NoSuchMethodException核心原因是:Hadoop的ReflectionUtils在读取SequenceFile时,必须通过无参构造方法实例化Writable对象,但你的代码存在两个关键问题:

  1. 从异常里的$iwC$$iwC$ClassBWritable可以看出,你是在Spark REPL(交互式Shell)里定义的类,这类类会被自动包装在Scala生成的内部作用域类中,Hadoop的反射机制无法正确访问嵌套内部类的无参构造。
  2. 即使你写了辅助无参构造,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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 07:22:28