2010-12-23 66 views
4

我有一个名为ImmutableEntity的Java抽象类和几个包含称为@DBTable的类级注释的子类。我试图用尾递归方法斯卡拉搜索的注释的类层次结构:Scala tailrec注释错误

def getDbTableForClass[A <: ImmutableEntity](cls: Class[A]): String = { 
    @tailrec 
    def getDbTableAnnotation[B >: A](cls: Class[B]): DBTable = { 
     if (cls == null) { 
     null 
     } else { 
     val dbTable = cls.getAnnotation(classOf[DBTable]) 
     if (dbTable != null) { 
      dbTable 
     } else { 
      getDbTableAnnotation(cls.getSuperclass) 
     } 
     } 
    } 

    val dbTable = getDbTableAnnotation(cls) 
    if (dbTable == null) { 
     throw new 
       IllegalArgumentException("No DBTable annotation on class " + cls.getName) 
    } else { 
     val value = dbTable.value 
     if (value != null) { 
     value 
     } else { 
     throw new 
       IllegalArgumentException("No DBTable.value annotation on class " + cls.getName) 
     } 
    } 
    } 

当我编译这段代码,我得到了错误:“无法优化@tailrec注释的方法:它被称为递归地用不同的类型参数“。我内心的方法有什么问题?

谢谢。

回答

15

这是因为编译器通过循环实现尾递归的方式。这是从Scala到Java字节码的转换链中的一个步骤。每次转换都必须生成一个类型正确的程序。但是,它不能在中间循环执行中更改变量的类型,这就是编译器无法扩展为类型正确的循环的原因。

+1

感谢您的解释。顺便说一句,伟大的语言! – Ralph 2010-12-24 13:03:11

2

由于类型参数B与其绑定并不严格要求,你可以使用一个存在的类型,而不是,

@tailrec 
def getDbTableAnnotation(cls: Class[_]): DBTable = { 
    ... 
} 

斯卡拉接受这个定义尾递归调用。

+0

感谢。我会尝试的。你知道为什么第一个表格被拒绝吗? – Ralph 2010-12-23 18:14:20

+0

@Ralph:不,我不确定。 @tailrec优化将递归函数转换为一个循环并进行擦除,我不明白这会带来什么麻烦。这可能是编译器实现的限制,也可能是因为目标平台可能会擦除类似JVM的类型,因此规范不允许使用它。 – 2010-12-23 18:31:47

3

我可以建议一个更简洁的代码版本吗?

def getDbTableForClass[A <: ImmutableEntity](cls: Class[A]): String = { 
@tailrec 
def getDbTableAnnotation[B >: A](cls: Class[B]): DBTable = cls match { 
    case null => null 
    case c if c.isAnnotationPresent(classOf[DBTable]) => c.getAnnotation(classOf[DBTable]) 
    case other => getDbTableAnnotation(other.getSuperclass) 
} 

getDbTableAnnotation(cls) match { 
    case null => throw new IllegalArgumentException("No DBTable annotation on class " + cls.getName) 
    case dbTable if dbTable.value ne null => dbTable.value 
    case other => throw new IllegalArgumentException("No DBTable.value annotation on class " + cls.getName) 
} 

}