標註(Annotation)


標註(Annotation)是撰寫在原始碼中的資訊,可以提供程式碼以外的額外資訊,與註解不同的是,標註本身有其結構,你可以使用工具提取這些結構化訊息。

Scala本身提供一些標註,可提供編譯器或運行平台(JVM)使用所加註的訊息,如果平台是JVM,在Java中所撰寫的標註,也可以使用在Scala的語法之中。

以下簡介Scala本身所提供的幾個標註。首先是@deprecated,也許你原先撰寫了某個類別或函式,別人也使用了你撰寫的類別或函式,但之後你發現該類別或函式有些不適當的地方(也許是效能、安全等問題),為了向前相容性,你不可以直接移除該類別或函式,以免已使用你所撰寫的類別或函式的客戶端,突然發現他們找不到該類別或函式了。

你可以在類別或函式上標註@deprecated,例如:
@deprecated class Some {
    ...
}

@deprecated def someFunc = {
    ....
}

class Other {
    @deprecated def otherFunc = {
        ...
    }
}

如果客戶端再度使用了你所提供的這些類別或函式,則編譯時會出現以下的訊息,提醒他們使用了不再建議使用的API:
warning: there were deprecation warnings; re-run with -deprecation for details one warning found

如果運行的平台是在JVM,則Scala的編譯器會將@deprectated轉換為Java的@Deprecated標註,如此若你在Java中使用了Scala所編譯出來的類別,使用Java編譯器時也會出現警示訊息。

Scala的標註也可以用於運算式(Expression),例如在 密封類別(Sealed class) 中看過的例子,當編譯器好意提出一些你未注意到的檢查狀況時的警示訊息,而你不想再看到這些囉嗦的訊息時,可以使用 @unchecked 標註(Annotation)來告訴編譯器住嘴:
...
def what(d: Drawing) = (d: @unchecked) match {
    case Point(_, _)    => "點"
    case Cylinder(_, _) => "柱"
}

當你要在多執行緒環境下共用存取某個類別的可變狀態資料成員(Field)時,可將該成員標註為@volatile,這告訴編譯器,該成員會在多執行緒環境下被共用存取,如果運行的環境為JVM,編譯器會將@volatile轉為Java語法的volatile修飾字。

Scala本身不提供序列化機制,然而提供了三個與序列化有關的標註:@serializable@SerialVersionUID以及@transient。 這三個標註告知底層平台哪些類別可以進行序列化、其序列化版本識別為何以及哪些成員(Field)不用序列化。如果是在JVM平台上, @serializable將對應至實作java.io.Serializable介面,@SerialVersionUID將對應至final static的serialVersionUID,而@transient將對應至transient關鍵字。

如果是在JVM上,@clonable將對應至實作java.lang.Clonable介面,@remote將對應至實作java.rmi.Remote介面。

Scala本身並不需要JavaBean形式的設值方法(Setter)與取值方法(Getter),在JVM平台上,如果你想要Scala的類別編譯出來後,其可變資料成員自動生成設值與取值方法,則可以使用@scala.reflect.BeanProperty,例如:
import scala.reflect.BeanProperty
class Some {
@BeanProperty private var x = 10
}

這個類別在使用scalac編譯之後,若使用Java的javap指令,可以看到以下的資訊:
public class Some extends java.lang.Object implements scala.ScalaObject{
    public Some();
    public void setX(int);
    public int getX();
    public int \$tag()       throws java.rmi.RemoteException;
}

在Scala中並不區分受檢例外(Checked exception)與非受檢例外(Unchecked exception),因此你可以撰寫類似以下的程式碼:
import java.io._

class Some {
def writeTo(file: String) {
val writer = new BufferedWriter(new FileWriter(file));
writer.write("...");
writer.close();
}
}

若平台是JVM,編譯這個類別後,使用Java的javap指令,可以看到writeTo()方法並沒有throws宣告:
public class Some extends java.lang.Object implements scala.ScalaObject{
    public Some();
    public void writeTo(java.lang.String);
    public int \$tag()       throws java.rmi.RemoteException;
}

在Java 的程式碼中仍然可以使用這個編譯好的類別(因為受檢例外是編譯器檢查,而非JVM的驗證器),Scala沒有throws語句,若你想要編譯出來的類別, 可以在writeTo()方法上宣告throws IOException,以便在Java程式中使用到writeTo()方法時,編譯器可以檢查是否處理IOException,則你可以使用@throws標註,例如:
import java.io._

class Some {
@throws(classOf[IOException])
def writeTo(file: String) {
val writer = new BufferedWriter(new FileWriter(file));
writer.write("...");
writer.close();
}
}

編譯這個類別後,使用Java的javap指令,可以看到writeTo()方法已加上throws宣告:
public class Some extends java.lang.Object implements scala.ScalaObject{
    public Some();
    public void writeTo(java.lang.String)       throws java.io.IOException;
    public int \$tag()       throws java.rmi.RemoteException;
}

Scala本身並不提供撰寫標註的機制,其所提供的標註僅提供底層平台轉換時的一些資訊,如果你的平台是JVM,那麼你可以使用Java的標註撰寫語法,將撰寫好的標註使用在Scala之中。