12.Scala- 注解

第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
 
 
 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

posted @ 2019-07-04 15:32  LXL_1  阅读(313)  评论(0编辑  收藏  举报