美文网首页
快学Scala第15章----注解

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

作者: 胡杨1015 | 来源:发表于2016-07-04 21:46 被阅读0次

    本章要点

    • 你可以为类、方法、字段、局部变量、参数、表达式、类型参数以及各种类型定义添加注解。
    • 对于表达式和类型,注解跟在被注解的条目之后
    • 注解的形式有: @Annotation、 @Annotation(value) 或 @Annotation(namel = value, ...)
    • @volatitle、 @transient、 @strictfp 和 @native 分别生成等效的Java修饰符。
    • 用@throws来生成与Java兼容的throws规格说明
    • @tailrec注解让你教研某个递归函数使用了尾递归优化
    • assert函数利用了@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注解是有区别的:
    Java注解并不影响编译器如何将源码翻译成字节码;它们仅仅是往字节码中添加数据,以便外部工具可以利用到它们。而在Scala中,注解可以影响编译过程。例如@BeanPropetry注解将触发getter和setter方法(如果为var的话)的生成。


    什么可以被注解

    在Scala中,你可以为类、方法、字段、局部变量和参数添加注解

    @Entity class Credentials
    @Test def testSomeFeature() {}
    @BeanProperty var username = _
    def doSomething(@NotNull message: String) {}
    // 同时添加多个注解
    @BeanProperty @Id var username = _
    
    // 给构造器添加注解,需要将注解放置在构造器之前,并加上一对圆括号(注解不带参数的话)
    class Credentials @Inject() (var username: String, var password: String)
    
    // 给表达式添加注解,需要在表达式后加上冒号,然后是注解本身
    (myMap.get(key): @unchecked) match { ... }
    
    // 为类型参数添加注解
    class MyContainer[@specialized T]
    
    // 为实际类型添加注解应放置在类型名称之后
    String @cps[Unit]
    

    注解参数

    Java注解可以有带名参数:

    @Test(timeout = 100, expected = classOf[IOException])
    
    // 如果参数名为value,则该名称可以直接略去。
    @Named("creds") var credentials: Credentials = _  // value参数的值为 “creds”
    
    // 注解不带参数,圆括号可以省去
    @Entity class Credentials
    

    Java 注解的参数类型只能是:

    • 数值型的字面量
    • 字符串
    • 类字面量
    • Java枚举
    • 其他注解
    • 上述类型的数组(但不能是数组的数组)

    Scala注解可以是任何类型,但只有少数几个Scala注解利用了这个增加的灵活性。


    注解实现

    你可以实现自己的注解,但是更多的是使用Scala和Java提供的注解。
    注解必须扩展Annotation特质:

    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代码中调用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. 变长参数
      @varargs注解让你可以从Java调用Scala的带有变长参数的方法。
    // 默认情况
    def process(agrs: String*)
    // Scala编译器会把变长参数翻译成序列:
    def process(args: Seq[String])   // 这样的方法签名在Java中使用很费劲
    
    // 加上 @varargs
    @varargs def process(args: String*)
    // 编译器将生成如下java方法
    void process(String... args)
    
    1. JavaBeans
      @BeanProperty注解将会生成JavaBean风格的getter和setter方法。

    用于优化的注解

    1. 尾递归
      递归调用有时候能被转化成循环,这样能节约栈空间。
    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注解。
    注意上面的方法是在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。

    **说明: **对于消除递归,一个更加通用的机制叫做“蹦床”。蹦床的实现会将执行一个循环,不停的调用函数。每个函数都返回下一个将被调用的函数。尾递归在这里是一个特例,每个函数都返回它自己。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
    

    2 跳转表生成与内联
    在C++或Java中,switch语句通常被编译成跳转表,用于多种情况的判断,比if/else方便高效。Scala也会尝试对匹配语句生成跳转表,使用@switch注解。

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

    另一个常见的优化方法是使用@inline来进行内联:用函数体替换函数调用,这与C++或Java的inline函数相同。 而@noinline来告诉编译器不要内联。

    1. 可省略方法
      @elidable注解给那些可以在生产代码中移除的方法打上标记。
    @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
    

    当你调用allDifferent(3,4,5) 则参数的类型为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注解用于某个隐士参数不存在的时候生成有意义的错误提示。
    @unchecked注解用于匹配不完整时取消警告信息:

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

    编译器不会报告说没有给出Nil选项。但是当lst是Nil的时候还是会抛出异常。

    相关文章

      网友评论

          本文标题:快学Scala第15章----注解

          本文链接:https://www.haomeiwen.com/subject/fagqjttx.html