15. Annotations - RobertMakyla/scalaWiki GitHub Wiki
KEY POINTS:
You can annotate classes, methods, fields, local variables, parameters, expressions, type parameters, and types. With expressions and types, the annotation follows the annotated item. Annotations have the form @Annotation, @Annotation(value), or @Annotation(name1 = value1, ...). @volatile, @transient, @strictfp, and @native generate the equivalent Java modifiers. Use @throws to generate Java-compatible throws specifications. The @tailrec annotation lets you verify that a recursive function uses tail call optimization. The assert function takes advantage of the @elidable annotation. You can optionally remove assertions from your Scala programs. Use the @deprecated annotation to mark deprecated features.
What Can Be Annotated
Classes, methods, fields, local variables, and parameters, just like in Java. Also:
Expressions:
( myMap.get(key): @unchecked ) match { ... } // The expression myMap.get(key) is annotated
Type parameters:
class MyContainer[@specialized T]
Types
String @cps[Unit] // String type is annotated
Annotation Arguments
@Test(timeout = 100, expected = classOf[IOException])
@Named(value = "creds")
@Named("creds") // I can omit the name if the name is 'value'
@Test
def testSomeFeature() { ... }
Is equivalent to:
@Test(timeout = 0, expected = classOf[org.junit.Test.None])
def testSomeFeature() { ... }
Java Annotations
class Credentials( @NotNull @BeanProperty var username: String )
This gives us:
- The constructor parameter
- The private instance field
- The accessor method username
- The mutator method username_=
- The bean accessor getUsername
- The bean mutator setUsername
@volatile var done = false // may change between different accesses, by different threads
@transient var recentLookups = new HashMap[String, String] // not serialized
@strictfp def calculate(x: Double) = ... // floating-point calculations with IEEE
@native def win32RegKeys(path: String):Int // method implemented in other language than Java,
// through Java Native Interface (JNI)
@cloneable class Employee // instead of Cloneable interface
@remote class Employee // instead of java.rmi.Remote interface
The @serializable annotation is deprecated. Extend the scala.Serializable trait instead.
@SerialVersionUID(6157032470129070425L)
class Employee extends Person with Serializable
Checked Exceptions
In scala I don't declare checked exceptions that can be thrown.
So when I call scala code from java, the scala code should have:
class Book {
@throws( classOf[IOException] ) def read(filename: String) { ... }
...
}
Variable Arguments
When I call scala code (with var args) from java, the scala code should have:
@varargs def process(args: String*)
Optimizations - Tail Recursion
Recursion occupies stack memory. And in Functional programming we use it often.
def sum(xs: Seq[Int]): Int = if (xs.isEmpty) 0 else xs.head + sum(xs.tail)
sum(1 to 10) // OK
sum(1 to 100000) // --> StackOverflowException
This method cannot be optimized because the last step of the computation is addition, not the recursive call.
But a slight transformation can be optimized:
def sum2(xs: Seq[Int], total: Int): Int = if (xs.isEmpty) total else sum2(xs.tail, xs.head + total)
The 'total' sum is passed as a parameter.
Since the last step of the computation is a recursive call to the same method,
it can be transformed into a loop to the top of the method.
The Scala compiler automatically applies the 'tail recursion' optimization to the second method.
sum2(1 to 100000, 0) // OK
Sometimes it is blocked with no reason. To force optimization:
@tailrec def sum2 ...
Then if compiler cannot apply optimization, there will be error. e.g.:
// could not optimize @tailrec annotated method sum2: it is neither private nor final so can be overridden
Tail Recursion - recursive sum example:
def sum( n:Int): Int = if (n==0) 0 else n+sum(n-1) // bad performance
transformed into tail recursion:
def sum( n:Int, total:Int): Int = if (n==0) total else sum(n-1, total + n)
sum(4,0) // --> 1+2+3+4 = 10 tail-recursive call
explanation of 'total + n' :
'n' is integer that starts at 'n' and changes (decrements) each step
'total' is current result - at the beginning is 0
so each step we need to '+' decrementing 'n' to 'total' current result
(to get sum)
Tail Recursion - factorial (silnia) example:
def factorial( n:Int): Int = if (n==0) 1 else n * factorial(n-1) // bad performance
transformed into tail recursion:
def factorial( n:Int, total:Int): Int = if (n==0) total else factorial(n-1, total*n)
factorial(3,1) // --> 1*2*3=6 tail-recursive call
explanation of 'total * n' :
'n' is integer that starts at 'n' and changes (decrements) each step
'total' is current result - at the beginning is 1
so each step we need to 'multiply' decrementing 'n' with 'total' current result
(to get factorial)
Optimizations - Jump Table Generation (tableswitch)
Scala attempts to transform case clause into tableswitch (better for perf).
The '@switch' provides a warning at compile time if the switch can't be compiled to a tableswitch.
(n: @switch) match {
case 0 => "Zero"
case 1 => "One"
case _ => "?"
}
Optimizations - Eliding Methods
The @elidable annotation flags methods that can be removed in production code. e.g.:
@elidable(500) def dump(arg:Int) { ... }
Compiling that removes this method:
scalac -Xelide-below 800 myprog.scala
Optimizations - Specialization for Primitive Types
Generic types:
def allDifferent[T](x: T, y: T, z: T) = x != y && x != z && y != z
allDifferent(1,2,3) //wraps arguments to java.lang.Integer
We use @specialized to mark that we want to accept only scala types:
def allDifferent[@specialized T](x: T, y: T, z: T) = x != y && x != z && y != z
// acceptable types: Unit, Boolean, Byte, Short, Char, Int, Long, Float, Double
def allDifferent[@specialized(Long, Double) T](x: T, y: T, z: T) = x != y && x != z && y != z
// acceptable types: Long, Double
Annotations for Errors and Warnings
@deprecated(message = "Use factorial(n: BigInt) instead") // generates warning
def factorial(n: Int): Int = ...
The @unchecked annotation suppresses a warning that a match is not exhaustive.
So compiler won't complain that there is no Nil option. But if lst is Nil --> Exception
(lst: @unchecked) match {
case head :: tail => ...
}