我刚刚开始使用抽象类型。我遇到了一个我无法完全理解的错误。以下是我的一些背景
的代码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
inner类返回此类型的引用。
以下是这个抽象类的概念性实现:更多背景
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时,它扩展了抽象的Field映射。我在这样的单元测试中尝试这个:
问题出现的地方
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" )
}
在上一次测试中(无论是启用了断言还是打印出来),每当我尝试访问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书中,返回抽象类型的方法在子类型中被覆盖,但是我不在这里做,并且编译器在实例化子类型时并没有抱怨缺少实现。当方法的返回类型是抽象类型(在基类中定义)时,为什么子类方法中需要重写标记?
答案 0 :(得分:1)
我认为这是问题所在,最好用一个例子说明。您的父类定义了这个:
def fieldMappings:Map [Key,FieldMapping]
这必须与编译时调用的单一类型一致。例如,如果您在上面添加了第二个类,第二个“类型”并合法地开始将其添加到该地图。 (完全合法,都是FieldMapping的子类。)但接下来,Scala如何知道以后要做什么?
dbf.fieldMappings.head._2.getField.getType
dbf.fieldMappings.tail._2.getField.otherFunction
现在考虑一下,编译器在.getField之后不知道你在那里加载了哪两个类。也许头是你的第一个孩子班?也许他们都是你定义“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
....
}
总而言之,你不能拥有一个抽象类型的集合,它可以在某种程度上稍后调用子类型而不在运行时进行强制转换,即使你只有一个定义它仍然是同样的问题。