にんにんにん

エンジニアな日々を書いていきます

アノテーション

@Overrideとか、@Deprecatedとか、日ごろJavaを書いていると出てくるこいつら

こいつらを付けるとコンパイル時にエラーを検出できる、などふわっとしたくらいにしか思っていなかったが、
その理解でいるのも危険だなと感じたので、まとめてみることにしました。
(個人的に、アノテーションのことを解説している入門書ってあまりない気がしている)

こいつらのことをアノテーションと呼び、JDK1.5より導入された機能です。
アノテーションをクラスやメソッドなどに付与することで、その名の通り「注釈」することができます。

アノテーションの種類

アノテーションは、持つデータの数によって次の3種類に分類されます。

また、次のアノテーションもあります。

マーカー

例えば、@Overrideの場合、以下のinterfaceがあるとします。

public interface Clickable {
    void click();
}

これを無名クラスとして実装するとき、

public class Main {
    public static void main(String[] args) {
        Clickable clickable = new Clickable() {
            @Override
            public void click() {
                System.out.print("click");
            }
        };
        clickable.click();
    }
}

となるかと思いますが、この時click()がオーバーライドされていることを明示しているのが@Overrideです。 試しに、@Overrideを取り外してみると、

public class Main {
    public static void main(String[] args) {
        Clickable clickable = new Clickable() {
            public void click() {
                System.out.print("click");
            }
        };
        clickable.click();
    }
}

実行

click
Process finished with exit code 0

コンパイルエラーはなく、実行できてしまいました。

文法的に間違いではなく、コンパイルには問題がないことがわかります。
マーカーは、人間のためのアノテーションで、このclickメソッドがコードの読み手に「clickはClickableの抽象メソッドを実装したものですよ」
と伝えています。
もっとも、interfaceであれば、実装したメソッドであることは一目でわかるのですが、classを継承した場合、それが親クラスのメソッドなのか子クラスのメソッドであるのか一目ではわからないため、こうしてアノテーションをつけることで可読性が上がります。
ただ、継承はよほどのことがない限り使うものではないとは思っています。

ちなみに、その@Overrideの中身をのぞいてみると

package java.lang;

import java.lang.annotation.*;

/**
 * Indicates that a method declaration is intended to override a
 * method declaration in a supertype. If a method is annotated with
 * this annotation type compilers are required to generate an error
 * message unless at least one of the following conditions hold:
 *
 * <ul><li>
 * The method does override or implement a method declared in a
 * supertype.
 * </li><li>
 * The method has a signature that is override-equivalent to that of
 * any public method declared in {@linkplain Object}.
 * </li></ul>
 *
 * @author  Peter von der Ah&eacute;
 * @author  Joshua Bloch
 * @jls 9.6.1.4 @Override
 * @since 1.5
 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

となっています。

アノテーションの定義は

<publicなどの修飾詞> @interface アノテーション名 { }

で、@Overrideの中身は空であることがわかります。

単一アノテーション

先ほどのClickableにdoubleClickという抽象メソッドを生やしたとします。

public interface Clickable {
    void click();
    void doubleClick();
}

これを実装して、clickメソッドしか呼び出さなかったとします。

public class Main {
    public static void main(String[] args) {
        Clickable clickable = new Clickable() {
            @Override
            public void click() {
                System.out.print("click");
            }

            @Override
            public void doubleClick() {
                System.out.print("doubleClick");
            }
        };
        clickable.click();
    }

}

この時、ClickableをIDE上で見てみると、

f:id:moroku0519:20180211123750p:plain

lintにdoubleClickが使われていないと怒られています。
ここで、doubleClickメソッドに@SupressWarning("unused")を付けてみます。

f:id:moroku0519:20180211124307p:plain

doubleClickメソッドの警告が消えました。(別の警告が出ているのはご愛敬...)

このように、本来ならばlintエラーが出てしまう個所に@SuppressWarningを付けることで、警告をなくすことができます。
ただし、警告をやみくもに消すのではなく、きちんとした理由があるときのみ使用します。

例えば、Androidで参照されていないメソッドがあるが、EventBusのSubscriberであり、警告されなくてもいいもの、など。

ちなみに、@SuppressWarningの実装は

package java.lang;

import java.lang.annotation.*;
import static java.lang.annotation.ElementType.*;

/**
 * Indicates that the named compiler warnings should be suppressed in the
 * annotated element (and in all program elements contained in the annotated
 * element).  Note that the set of warnings suppressed in a given element is
 * a superset of the warnings suppressed in all containing elements.  For
 * example, if you annotate a class to suppress one warning and annotate a
 * method to suppress another, both warnings will be suppressed in the method.
 *
 * <p>As a matter of style, programmers should always use this annotation
 * on the most deeply nested element where it is effective.  If you want to
 * suppress a warning in a particular method, you should annotate that
 * method rather than its class.
 *
 * @author Josh Bloch
 * @since 1.5
 * @jls 4.8 Raw Types
 * @jls 4.12.2 Variables of Reference Type
 * @jls 5.1.9 Unchecked Conversion
 * @jls 5.5.2 Checked Casts and Unchecked Casts
 * @jls 9.6.3.5 @SuppressWarnings
 */
@Target({TYPE, FIELD, METHOD, PARAMETER, CONSTRUCTOR, LOCAL_VARIABLE})
@Retention(RetentionPolicy.SOURCE)
public @interface SuppressWarnings {
    /**
     * The set of warnings that are to be suppressed by the compiler in the
     * annotated element.  Duplicate names are permitted.  The second and
     * successive occurrences of a name are ignored.  The presence of
     * unrecognized warning names is <i>not</i> an error: Compilers must
     * ignore any warning names they do not recognize.  They are, however,
     * free to emit a warning if an annotation contains an unrecognized
     * warning name.
     *
     * <p> The string {@code "unchecked"} is used to suppress
     * unchecked warnings. Compiler vendors should document the
     * additional warning names they support in conjunction with this
     * annotation type. They are encouraged to cooperate to ensure
     * that the same names work across multiple compilers.
     * @return the set of warnings to be suppressed
     */
    String[] value();
}

となっていおり、

String[] value();

というデータをひとつだけ持っています。

本来アノテーション

@アノテーション名(キー名=値)

という形で指定しますが、単一アノテーションは一つだけしかキーを持たないため、キー名を省略できます。そのため、

@SuppressWarning("unused")

というように、書くことができました。

少々長くなってしまったので、フルアノテーションとメタアノテーションについては次回まとめます。