第12章 注解
注解就是标签。
标签是用来标记某些代码需要特殊处理的。
处理的手段可以在代码运行时操作,也可以在编译期操作。
12.1 什么可以被注解
1) 可以为类,方法,字段局部变量,参数,表达式,类型参数以及各种类型定义添加注解
@Entity class Student @Test def play() {} @BeanProperty var username = _ def doSomething(@NotNull message: String) {} @BeanProperty @Id var username = _
2) 构造器注解,需要在主构造器之前,类名之后,且需要加括号,如果注解有参数,则写在注解括号里
class Student @Inject() (var username: String, var password: String)
3) 为表达式添加注解,在表达式后添加冒号
(map1.get(key): @unchecked) match {...}
4) 泛型添加注解
class Student[@specialized T]
5) 实际类型添加注解
String @cps[Unit]
12.2 注解参数
Java注解可以有带名参数:
@Test(timeout = 100, expected = classOf[IOException]) // 如果参数名为value,则该名称可以直接略去。 @Named("creds") var credentials: Credentials = _ // value参数的值为 “creds” // 注解不带参数,圆括号可以省去 @Entity class Credentials
Java 注解的参数类型只能是:
数值型的字面量,
字符串,
类字面量,
Java枚举,
其他注解。
上述类型的数组(但不能是数组的数组)
Scala注解可以是任何类型,但只有少数几个Scala注解利用了这个增加的灵活性。
12.3 注解实现
你可以实现自己的注解,但是更多的是使用Scala和Java提供的注解。
注解必须扩展Annotation特质:
class unchecked extends annotation.Annotation
12.4 针对 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]
2) 标记接口:Scala用注解@cloneable和@remote 而不是 Cloneable和Java.rmi.Remote“标记接口”来标记可被克隆的对象和远程的对象。
@cloneable class Employee
3) 受检异常:和Scala不同,Java编译器会跟踪受检异常。如果你从Java代码中调用Scala的方法,其签名应包含那些可能被抛出的受检异常。用@throws注解来生成正确的签名。
class Book { @throws (classOf[IOException]) def read(filename: String) { ... } ... } Java版本的方法签名: void read(String fileName) throws IOException // 如果没有@throws注解,Java代码将不能捕获该异常 try {//Java代码 book.read("war-and-peace.txt"); } catch (IOException ex) { ... }
即:Java编译期需要在编译时就知道read方法可以抛IOException异常,否则Java会拒绝捕获该异常。
12.5 用于优化的注解
尾递归的优化
啥玩是尾递归?
尾递归:
def story(): Unit = {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story()}
尖叫提示:进入下一个函数不再需要上一个函数的环境了,得出结果以后直接返回。
非尾递归:
def story(): Unit = {从前有座山,山上有座庙,庙里有个老和尚,一天老和尚对小和尚讲故事:story(),小和尚听了,找了块豆腐撞死了}
尖叫提示:下一个函数结束以后此函数还有后续,所以必须保存本身的环境以供处理返回值。
递归调用有时候能被转化成循环,这样能节约栈空间:
object Util { def sum(xs: Seq[Int]): BigInt = { if (xs.isEmpty) 0 else xs.head + sum(xs.tail) } ... }
上面的sum方法无法被优化,因为计算过程中最后一步是加法,而不是递归调用。调整后的代码:
def sum2(xs: Seq[Int], partial: BigInt): BigInt = { if (xs.isEmpty) partial else sum2(xs.tail, xs.head + partial) }
Scala编译器会自动对sum2应用“尾递归”优化。如果你调用sum(1 to 1000000) 将会发生一个栈溢出错误。不过sum2(1 to 1000000, 0) 将会得到正确的结果。
尽管Scala编译器会尝试使用尾递归优化,但有时候某些不太明显的原因会造成它无法这样做。如果你想编译器无法进行优化时报错,则应该给你的方法加上@tailrec注解。
尖叫提示:对于消除递归,一个更加通用的机制叫做“蹦床”。蹦床的实现会执行一个循环,不停的调用函数。每个函数都返回下一个将被调用的函数。尾递归在这里是一个特例,每个函数都返回它自己。Scala有一个名为TailCalls的工具对象,帮助我们轻松实现蹦床:
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