【问题标题】:Errors using Abstract types in Scala在 Scala 中使用抽象类型的错误
【发布时间】:2013-07-10 09:12:27
【问题描述】:

我刚刚开始使用抽象类型。我遇到了一个我无法完全理解的错误。这是我的一些 BACKGROUND

代码
abstract class DbfReader( fileName: String )
{
    type DBFDataType <:Any
    type Key <:Any
    type Value <:Any

    abstract class FieldMapping
    {
        type FieldType
        def acronym: Key
        def longName: Key 
        def fieldNum: Int
        def getField: FieldType
        def getFieldLength: Int
    }

    def fieldMappings: Map[ Key, FieldMapping ]
    def getFieldCount: Int
    def hasRecord(): Boolean
    def getRecord(): DBFDataType
    def getFieldVal( fieldName: Key )( rowData: DBFDataType ): Value
    protected def createFieldMapping( fieldAcro: Key, 
                                      fieldLongName: Key, 
                                      fieldPosition: Int ): FieldMapping
....
}

抽象类 DbfReader 旨在成为我正在尝试的不同 DBF(数据库文件)读取库的抽象包装器。抽象类有一个 FieldMapping 内部类(表元数据有一个抽象类型FieldType,它是数据库字段的底层库表示的占位符。内部类中的例程getField 返回一个这种类型的引用。

以下是这个抽象类的具体实现:更多背景

class MyDBFReader( fileName: String, fmap: List[( String, String, Int )]  ) extends DbfReader( fileName )
{
    type DBFDataType = Array[Object]
    type Key = String
    type Value = String

    val dbReader = new jdbf.DBFReader( new java.io.FileInputStream( theFile ) )

    val fieldMappings = addFieldMappings(fmap)(Map())
    case class InnerFieldMapping( acronym: Key, longName: Key, fieldNum: Int) extends FieldMapping
    {
        type FieldType = jdbf.JDBField
        override def getField: jdbf.JDBField = dbReader.getField( fieldNum )
        def getFieldLength = getField.getLength
    }

    def getFieldCount = dbReader.getFieldCount
    def hasRecord = dbReader.hasNextRecord
    def getRecord = dbReader.nextRecord
    def createFieldMapping( fieldAcro: String, fieldLongName: String, fieldPosition: Int ) = InnerFieldMapping( fieldAcro, fieldLongName, fieldPosition )

    def getFieldVal( fieldName: Key )( rowData: DBFDataType ) = {
        if( fieldMappings.keySet.contains( fieldName ) ) stringer( rowData( fieldMappings( fieldName ).fieldNum - 1 ) )
        else
            throw new NoSuchElementException( "Key " + fieldName + " not Found" )
    }

    private def stringer( r: Object ) = r.asInstanceOf[String].trim
}

我遇到的麻烦是当我尝试从扩展抽象字段映射的 InnerFieldMapping 调用 getField 时。我正在这样的单元测试中尝试这个:

问题出在哪里

class MyDBFSuite extends FunSuite {
    val fileName = "/Users/Me/api11bdb.dbf"
    val dbf = new MyDBFReader( fileName, DbfReader.SchoolFieldMapping )

    test( "Dbf should have 150 fields" )
    {
        assert( dbf.getFieldCount === 150 )
    }

    test( "Should read a record" )
    {
        assert( dbf.hasRecord === true )
        assert( dbf.getRecord.size === 150 )
    }

    test( "Should Get a Field" )
    {
        println( dbf.fieldMappings.head._2.getField.getType )
        //assert( dbf.fieldMappings.head._2.getField.getType === "S" )
    }

在最后一次测试中(启用断言或在 println 中),每当我尝试访问 getType 时,它​​是 DBFField 中的例程,这是我对内部类 InnerFieldMapping 例程 getField 的期望。在抽象玻璃中,例程指定返回类型为FieldType,我在具体类中将其实现为 jdbf.JDBFField

但是编译器说:问题

src/test/scala/ChinaDBFTestSuite.scala:23: value getType is not a member of MyDBFSuite.this.dbf.FieldMapping#FieldType
[error]         println( dbf.fieldMappings.head._2.getField.getType )
[error]                                                     ^

在另一个测试中,我调用外部类例程 getRecord,它在其抽象类中返回抽象类型。编译器在那里没有问题。查看错误消息,它似乎期望 FieldType 符合内部抽象类定义。我的意思是我希望它寻找 MyDBFSuite.this.dbg.InnerFieldMapping.FieldType。我在这里做错了什么吗?

编辑: 非常感谢您的回答。作为后续,我还有一个关于覆盖的问题?我注意到在 scala book 中返回抽象类型的方法在子类型中被覆盖,但是我在这里不这样做,编译器在实例化子类型时不会抱怨缺少实现。当方法的返回类型是抽象类型(在基类中定义)时,为什么子类方法中需要override标签?

【问题讨论】:

  • 也许你需要打电话给getClass

标签: scala types


【解决方案1】:

我认为这就是问题所在,最好用一个例子来说明。你的父类定义了这个:

def fieldMappings: Map[ Key, FieldMapping ]

而且 this 必须同质地绑定到它在编译器期间调用的单一类型。例如,如果您在上面添加了第二个类,第二个“类型”并合法地开始将其添加到该地图中。 (完全合法,两者都是 FieldMapping 的子类。)但是,接下来,Scala 怎么知道以后要做什么呢?

    dbf.fieldMappings.head._2.getField.getType
    dbf.fieldMappings.tail._2.getField.otherFunction

现在考虑一下,编译器在 .getField 之后不知道您在其中加载了 2 个类中的哪一个。也许头是你的第一个孩子班?也许它们都是您定义的第二个定义“otherFunction”的类?谁知道?

解决方案:

你可以在事后施放它:

dbf.fieldMappings.head._2.getField match {
  case answer:jdbf.JDBField => answer
  case _ => throw new ClassCastException
}

或者,使用类型参数从上到下绑定所有内容 - 这可能是最易读和最安全的方式,因为它可以在编译期间被捕获。

abstract class DbfReader[T]( fileName: String )
{
    type DBFDataType <:Any
    type Key <:Any
    type Value <:Any

    abstract class FieldMapping
    {

        def acronym: Key
        def longName: Key 
        def fieldNum: Int
        def getField: T
        def getFieldLength: Int
    }

    def fieldMappings: Map[ Key, FieldMapping ]
    def getFieldCount: Int
    def hasRecord(): Boolean
    def getRecord(): DBFDataType
    def getFieldVal( fieldName: Key )( rowData: DBFDataType ): Value
    protected def createFieldMapping( fieldAcro: Key, 
                                      fieldLongName: Key, 
                                      fieldPosition: Int ): FieldMapping
....
}

总而言之,您不能拥有一个抽象类型的集合,该集合以某种方式稍后调用子类型而不在运行时进行强制转换,即使您只定义了一个,这仍然是同样的问题。

【讨论】:

  • 非常感谢,很抱歉我没有尽快回复......这是有道理的,我不能持有一个抽象对象(包含抽象类型)并期望能够访问混凝土类型的内部结构。因此,如果我想坚持上述方法,我需要确保调用者不需要与底层类型交互(考虑到它们只能处理抽象版本)。
  • 没问题很高兴我能提供帮助,我也在学习 Scala 的类型系统,有时真的很沮丧!仅供参考,我推荐一本好书 - “不耐烦的 Scala”.. 可能是目前最好的书,而且在亚马逊上很便宜。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-05-22
  • 1970-01-01
  • 1970-01-01
  • 2018-08-01
  • 1970-01-01
  • 2011-12-27
相关资源
最近更新 更多