快學Scala第15章----注解

本章要點

  • 你可以為類、方法、字段、局部變量、參數(shù)、表達式、類型參數(shù)以及各種類型定義添加注解。
  • 對于表達式和類型,注解跟在被注解的條目之后
  • 注解的形式有: @Annotation、 @Annotation(value) 或 @Annotation(namel = value, ...)
  • @volatitle、 @transient、 @strictfp 和 @native 分別生成等效的Java修飾符。
  • 用@throws來生成與Java兼容的throws規(guī)格說明
  • @tailrec注解讓你教研某個遞歸函數(shù)使用了尾遞歸優(yōu)化
  • assert函數(shù)利用了@elidable注解。你可以選擇從Scala程序中移除所有斷言。
  • 用@deprecated注解來標記已過時的特性。

什么是注解

注解是那些你插入到代碼中,以便有工具可以對它們進行處理的標簽。工具可以在代碼級別運作,也可以處理被編譯器加入了注解信息的類文件。
注解的語法和Java一樣:

@Test(timeout = 100) def testSomeFeature() { ... }

@Entity class Credentials {
  @Id @BeanProperty var username: String = _
  @BeanProperty var password: String = _
}

你可以對Scala類使用Java注解。上述示例中的注解除了@BeanProperty外,其他的都來自JUnit和JPA,而這兩個Java框架并不知道我們用的是Scala。
Scala特有的注解通常是由Scala編譯器或編譯器插件處理。Scala注解和Java注解是有區(qū)別的:
Java注解并不影響編譯器如何將源碼翻譯成字節(jié)碼;它們僅僅是往字節(jié)碼中添加數(shù)據(jù),以便外部工具可以利用到它們。而在Scala中,注解可以影響編譯過程。例如@BeanPropetry注解將觸發(fā)getter和setter方法(如果為var的話)的生成。


什么可以被注解

在Scala中,你可以為類、方法、字段、局部變量和參數(shù)添加注解

@Entity class Credentials
@Test def testSomeFeature() {}
@BeanProperty var username = _
def doSomething(@NotNull message: String) {}
// 同時添加多個注解
@BeanProperty @Id var username = _

// 給構造器添加注解,需要將注解放置在構造器之前,并加上一對圓括號(注解不帶參數(shù)的話)
class Credentials @Inject() (var username: String, var password: String)

// 給表達式添加注解,需要在表達式后加上冒號,然后是注解本身
(myMap.get(key): @unchecked) match { ... }

// 為類型參數(shù)添加注解
class MyContainer[@specialized T]

// 為實際類型添加注解應放置在類型名稱之后
String @cps[Unit]

注解參數(shù)

Java注解可以有帶名參數(shù):

@Test(timeout = 100, expected = classOf[IOException])

// 如果參數(shù)名為value,則該名稱可以直接略去。
@Named("creds") var credentials: Credentials = _  // value參數(shù)的值為 “creds”

// 注解不帶參數(shù),圓括號可以省去
@Entity class Credentials

Java 注解的參數(shù)類型只能是:

  • 數(shù)值型的字面量
  • 字符串
  • 類字面量
  • Java枚舉
  • 其他注解
  • 上述類型的數(shù)組(但不能是數(shù)組的數(shù)組)

Scala注解可以是任何類型,但只有少數(shù)幾個Scala注解利用了這個增加的靈活性。


注解實現(xiàn)

你可以實現(xiàn)自己的注解,但是更多的是使用Scala和Java提供的注解。
注解必須擴展Annotation特質(zhì):

class unchecked extends annotation.Annotation

針對Java特性的注解

  1. Java修飾符
    對于那些不是很常用的Java特性,Scala使用注解,而不是修飾符關鍵字:
@volatile var done = false  // JVM中將成為volatile的字段
@transient var recentLookups = new HashMap[String, String]  // 在JVM中將成為transient字段,該字段不會被序列化。
@strictfp def calculate(x: Double) = ...
@native def win32RegKeys(root: Int, path: String): Array[String]
  1. 標記接口
    Scala用注解@cloneable和@remote 而不是 Cloneable和Java.rmi.Remote標記接口來標記可被克隆的和遠程的對象。
@cloneable class Employee

對于可序列化的類,你可以用@SerialVersionUID注解來指定序列化版本

@SerialVersionUID(6157032470129070425L)
class Employee extends Person with Serializable
  1. 受檢異常
    和Scala不同,Java編譯器會跟蹤受檢異常。如果你從Java代碼中調(diào)用Scala的方法,其簽名應包含那些可能被拋出的受檢異常。用@throws注解來生成正確的簽名。
class Book {
  @throws (classOf[IOException]) def read(filename: String) { ... }
  // Java版本為
  // void read(String filename) throws IOException
  ...
}

// 如果沒有@throws注解,Java代碼將不能捕獲該異常
try {
  book.read("war-and-peace.txt");
} catch (IOException ex) {
  ...
}

Java 編譯器需要知道read方法可以拋出IOException,否則會拒絕捕獲該異常。

  1. 變長參數(shù)
    @varargs注解讓你可以從Java調(diào)用Scala的帶有變長參數(shù)的方法。
// 默認情況
def process(agrs: String*)
// Scala編譯器會把變長參數(shù)翻譯成序列:
def process(args: Seq[String])   // 這樣的方法簽名在Java中使用很費勁

// 加上 @varargs
@varargs def process(args: String*)
// 編譯器將生成如下java方法
void process(String... args)
  1. JavaBeans
    @BeanProperty注解將會生成JavaBean風格的getter和setter方法。

用于優(yōu)化的注解

  1. 尾遞歸
    遞歸調(diào)用有時候能被轉(zhuǎn)化成循環(huán),這樣能節(jié)約??臻g。
object Util {
  def sum(xs: Seq[Int]): BigInt = {
    if (xs.isEmpty) 0 else xs.head + sum(xs.tail)
  }
  ...
}

上面的sum方法無法被優(yōu)化,因為計算過程中最后一步是加法,而不是遞歸調(diào)用。調(diào)整后的代碼:

def sum2(xs: Seq[Int], partial: BigInt): BigInt = {
  if (xs.isEmpty) partial else sum2(xs.tail, xs.head + partial)
}

Scala編譯器會自動對sum2應用“尾遞歸”優(yōu)化。如果你調(diào)用sum(1 to 1000000) 將會發(fā)生一個棧溢出錯誤。不過sum2(1 to 1000000, 0) 將會得到正確的結果。
盡管Scala編譯器會嘗試使用尾遞歸優(yōu)化,但有時候某些不太明顯的原因會造成它無法這樣做。如果你想編譯器無法進行優(yōu)化時報錯,則應該給你的方法加上@tailrec注解。
注意上面的方法是在Object中定義的,如果是在class中呢:

class Util {
import scala.annotation._
  @tailrec def sum2(xs: Seq[Int], partial: BigInt): BigInt = {
    if (xs.isEmpty) partial else sum2(xs.tail, xs.head + partial)
  }
  ...
}
tailrec.png

這種情況下,你可以將方法挪到對象中,或者將它聲明為private或final。

**說明: **對于消除遞歸,一個更加通用的機制叫做“蹦床”。蹦床的實現(xiàn)會將執(zhí)行一個循環(huán),不停的調(diào)用函數(shù)。每個函數(shù)都返回下一個將被調(diào)用的函數(shù)。尾遞歸在這里是一個特例,每個函數(shù)都返回它自己。Scala有一個名為TailCalls的工具對象,幫助我們輕松實現(xiàn)蹦床。

import scala.util.control.TailCalls._
def evenLength(xs: Seq[Int]): TailRec[Boolean] = {
  if(xs.isEmpty) done(true) else tailcall(oddLength(xs.tail))
}

def oddLength(xs: Seq[Int]): TailRec[Boolean] = {
  if(xs.isEmpty) done(false) else tailcall(evenLength(xs.tail))
}

// 獲得TailRec對象獲取最終結果,可以用result方法
evenLength(1 to 1000000).result

2 跳轉(zhuǎn)表生成與內(nèi)聯(lián)
在C++或Java中,switch語句通常被編譯成跳轉(zhuǎn)表,用于多種情況的判斷,比if/else方便高效。Scala也會嘗試對匹配語句生成跳轉(zhuǎn)表,使用@switch注解。

(n @switch) match {
  case 0 => "Zero"
  case 1 => "One"
  case _ => "?"
}

另一個常見的優(yōu)化方法是使用@inline來進行內(nèi)聯(lián):用函數(shù)體替換函數(shù)調(diào)用,這與C++或Java的inline函數(shù)相同。 而@noinline來告訴編譯器不要內(nèi)聯(lián)。

  1. 可省略方法
    @elidable注解給那些可以在生產(chǎn)代碼中移除的方法打上標記。
@elidable(500) deg dump(props: Map[String, String]) = { ... }

使用 scalac -Xelide-below 800 myprog.scala 則上述方法代碼不會被生成。

  1. 基本類型的特殊化
    打包和解包基本類型的值是不高效的,但是在泛型代碼中很常見。
def allDifferent[T](x: T, y: T, z: T) = x != y && x != x && y != z

當你調(diào)用allDifferent(3,4,5) 則參數(shù)的類型為java.lang.Integer。你可以重載該版本,指定具體的類型,你也可以讓編譯器自動生成這些方法,使用@specialized注解:

def allDifferent[@specialized T](x: T, y: T, z: T) = ...
// 你也可以將特化限定在某幾個可選類型的子集
def allDifferent[@specialized(Long, Double) T](x: T, y: T, z: T) = ...

用于錯誤和警告的注解

如果給特性加上@deprecated注解,則每當編譯器遇到這個特性的使用時都會生成一個警告信息。

@deprecated(message = "Use factorial(n: BigInt) instead")
def factorial(n: Int): Int = ...

@implicitNotFound注解用于某個隱士參數(shù)不存在的時候生成有意義的錯誤提示。
@unchecked注解用于匹配不完整時取消警告信息:

(lst: @unchecked) match {
  case head :: tail => ...
}

編譯器不會報告說沒有給出Nil選項。但是當lst是Nil的時候還是會拋出異常。

最后編輯于
?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容