2010-12-23 61 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) 
} 

}