1.

概要

この記事では、Java言語のコア機能、つまりJDKで使用可能なデフォルトの注釈について説明します。

** 2注釈とは

簡単に言うと、アノテーションは「@」記号が前に付いた** Java型です。

Javaは1.5リリース以来ずっと注釈を持っています。それ以来、それらは私達が私達のアプリケーションを設計した方法を形作ってきました。

SpringとHibernateは、さまざまな設計手法を可能にするためにアノテーションに大きく依存しているフレームワークの素晴らしい例です。

基本的に、

アノテーションは

にバインドされているソースコードに追加のメタデータを割り当てます。メソッド、インターフェース、クラス、またはフィールドに注釈を追加することで、次のことが可能になります。

  1. 警告とエラーについてコンパイラに通知する

  2. コンパイル時にソースコードを操作する

  3. 実行時に動作を変更または確認する


3 Java組み込みアノテーション

基本を見直しましたので、次にコアJavaに付属するアノテーションを見てみましょう。まず、コンパイルを知らせるものがいくつかあります。


  1. @オーバーライド


  2. @ SuppressWarnings


  3. @非推奨


  4. @ SafeVarargs


  5. @ FunctionalInterface

これらの注釈は、コンパイラの警告とエラーを生成または抑制します。

それらを追加することは将来のプログラマのエラーを防ぐことができるので、それらを一貫して適用することはしばしば良い習慣です。


3.1.

@オーバーライド


サブクラスでは、/java-classes-initialization-questions[オーバーライドまたはオーバーロード]インスタンスメソッドをリンクできます。オーバーライドは、サブクラスが継承された動作を置き換えていることを示します。オーバーロードは、サブクラスが新しい動作を追加しているときです。

実際にオーバーライドしようとしたときに、偶然に過負荷になることがあります。 Javaでこの間違いを犯すのは簡単です:

public class Machine {
    public boolean equals(Machine obj) {
        return true;
    }

    @Test
    public void whenTwoDifferentMachines__thenReturnTrue() {
        Object first = new Machine();
        Object second = new Machine();
        assertTrue(first.equals(second));
    }
}

驚くべきことに、上記のテストは失敗します。これは、この

equals

メソッドがオーバーライドしているのではなく、

Object#equals

をオーバーロードしているためです。

この間違いから私たちを守るために、継承されたメソッドに

@ Override

アノテーションを使うことができます。

この例では、

equals

メソッドの上に

@ Override

アノテーションを追加できます。

@Override
public boolean equals(Machine obj) {
    return true;
}

この時点で、コンパイラはエラーを発生させ、私たちが考えているように

equals

をオーバーライドしていないことを知らせます。

それから、私達は私達の間違いを訂正することができます:

@Override
public boolean equals(Object obj) {
    return true;
}

誤ってオーバーロードするのは簡単なので、すべての継承メソッドに

@ Override

アノテーションを使用することをお勧めします。


3.2.

@ SuppressWarnings


コンパイラの警告メッセージは通常役に立ちます。時には警告がうるさいことがあります。

特に私たちがそれらに対処することができない、または望まない場合は:

public class Machine {
    private List versions;

    public void addVersion(String version) {
        versions.add(version);
    }
}

コンパイラはこのメソッドについて警告を出します。生タイプのコレクションを使用していることを警告します。警告を修正したくない場合は、

@ SuppressWarnings

アノテーションを使って

抑制することができます

この注釈により、どの種類の警告を無視するかを指定できます。

警告の種類はhttps://stackoverflow.com/questions/1205995/what-is-the-list-of-valid-suppresswarnings-warning-names-in-java[compiler vendor]によって異なりますが、

2つの最も一般的なものは

deprecation



unchecked



deprecation

は、非推奨のメソッドや型を使用しているときには無視するようにコンパイラに指示します。


unchecked

は、生の型を使用しているときは無視するようにコンパイラーに指示します。

したがって、上記の例では、** 生の型の使用法に関連する警告を抑制できます。

public class Machine {
    private List versions;

    @SuppressWarnings("unchecked")
   //or
    @SuppressWarnings({"unchecked"})
    public void addVersion(String version) {
        versions.add(version);
    }
}

複数の警告のリストを抑制するために、対応する警告リストを含む

String

配列を設定します。

@SuppressWarnings({"unchecked", "deprecated"})


3.3.

@非推奨


プロジェクトが進化するにつれて、そのAPIの変更が行われます。時間が経つにつれて、私たちが人々にもう使いたくないような特定のコンストラクタ、フィールド、タイプ、またはメソッドがあります。

プロジェクトのAPIの下位互換性を破る代わりに、これらの要素に

@ Deprecated

アノテーション

__.

__というタグを付けることができます。


@ Deprecated

は他の開発者に


印の付いた要素はもう


使用されるべきではないことを伝えます

。 Javadocに

@ Deprecated

アノテーションを付けて、正しい振る舞いに役立つ

より良い代替案となるものを説明するのが一般的です。

public class Worker {
   /** **
     **  Calculate period between versions
     **  @deprecated
     **  This method is no longer acceptable to compute time between versions.
     **  <p> Use {@link Utils#calculatePeriod(Machine)} instead.
     **
     **  @param machine instance
     **  @return computed time
     ** /    @Deprecated
    public int calculate(Machine machine) {
        return machine.exportVersions().size() **  10;
    }
}

指定されたJava要素がコード内のどこかで使用されている場合、コンパイラは廃止予定のAPI警告のみを表示します。したがって、この場合は、

calculate

メソッドを呼び出すコードがあるかどうかだけを示します。

また、メソッドの説明でJavadoc

@ deprecated

タグを使用して、ドキュメント内で廃止予定のステータスを伝えることもできます。


3.4.

@ SafeVarargs


Java 5では、可変引数の概念、つまり可変長のメソッドパラメータ、およびパラメータ化された型が導入されました。

これらを組み合わせると問題が発生する可能性があります。

public static <T> T[]unsafe(T... elements) {
    return elements;//unsafe! don't ever return a parameterized varargs array
}

public static <T> T[]broken(T seed) {
    T[]plant = unsafe(seed, seed, seed);//broken! This will be an Object[]no matter what T is
    return plant;
}

public static void plant() {
   String[]plants = broken("seed");//ClassCastException
}

これらの問題はコンパイラが確認するのが難しいので、__unsafeの場合のように、2つが結合されるたびに警告を出します。

warning:[unchecked]Possible heap pollution from parameterized vararg type T
  public static <T> T[]unsafe(T... elements) {

このメソッドは、

broken

のように誤って使用された場合、

は意図した型

b


ではなく

Object[]

配列をヒープに汚染します。

この警告を回避するために、最終メソッドまたは静的メソッドおよびコンストラクタに

@ SafeVarargs

アノテーションを追加することができます。


@ SafeVarargs

は、@ SupressWarnings__と似ていますが、特定のコンパイラ警告が誤検出であることを宣言できるという意味です。

アクションが安全であることを確認したら

、このアノテーションを追加できます。

public class Machine<T> {
    private List<T> versions = new ArrayList<>();

    @SafeVarargs
    public final List<T> safe(T... toAdd) {
        for (T version : toAdd) {
            versions.add(version);
        }
    }
}

可変引数を安全に使用することは、それ自体が難しい概念です。詳細については、Josh Blochの著書、https://books.google.com/books?id=BIpDDwAAQBAJで優れた説明があります


3.5.

@ FunctionalInterface


Java 8では、より機能的な方法でコードを書くことができます。


単一抽象メソッドのインターフェース

は、この大部分を占めます。

ラムダで使用されるSAMインタフェースを意図しているなら、

@ FunctionalInterface


でそのようにそれをマークすることができます。

@FunctionalInterface
public interface Adder {
    int add(int a, int b);
}


@ Override

がメソッドであるように、

@ FunctionalInterface

は意図を

Adder

で宣言します。

これで、

@ FunctionalInterface

を使用するかどうかにかかわらず、

Adder

を同じ方法で使用できます。

Adder adder = (a,b) -> a + b;
int result = adder.add(4,5);

しかし、__Adderに2番目のメソッドを追加すると、コンパイラは次のように文句を言います。

@FunctionalInterface
public interface Adder {
   //compiler complains that the interface is not a SAM

    int add(int a, int b);
    int div(int a, int b);
}

これで、これは

@ FunctionalInterface

アノテーションなしでコンパイルされました。それで、それは私たちに何を与えるのでしょうか?


@ Override

と同様に、このアノテーションは将来のプログラマのエラーから私たちを守ります。

1つのインタフェースに複数のメソッドを含めることは合法ですが、そのインタフェースがラムダターゲットとして使用されているときではありません。ラムダ。さて、

それは

Adder

自体に割り込むだけです。**


4メタアノテーション

次に、メタアノテーションは他のアノテーションに適用できるアノテーションです。

たとえば、これらのメタアノテーションはアノテーション設定に使用されます。


  1. @ターゲット


  2. @保持


  3. @継承されました


  4. @文書化された


  5. @繰り返し可能


4.1.

@ターゲット


注釈の範囲は要件によって異なります。注釈はメソッドでのみ使用されますが、別の注釈はコンストラクターとフィールドの宣言で使用できます。

  • カスタムアノテーションのターゲット要素を決定するためには、それに

    @ Target

    アノテーションを付ける必要があります。**


@ Target

はhttps://docs.oracle.com/javase/tutorial/java/annotations/predefined.html[8種類の要素タイプ]で動作します。 @

SafeVarargs

のソースコードを見ると、それはコンストラクタまたはメソッドにのみ添付されなければならないことがわかります。

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.CONSTRUCTOR, ElementType.METHOD})
public @interface SafeVarargs {
}


4.2.

@保持


注釈の中には、コンパイラのヒントとして使用されるものもあれば、実行時に使用されるものもあります。

  • 私たちは

    @ Retention

    アノテーションを使って私たちのプログラムのライフサイクルのどこに私たちのアノテーションが適用されるかを示します

これを行うには、次の3つの保存ポリシーのいずれかを使用して

@ Retention

を構成する必要があります。


  1. RetentionPolicy.SOURCE –

    コンパイラからも見えないし

ランタイム


RetentionPolicy.CLASS

– コンパイラから見える


  1. RetentionPolicy.RUNTIME –

    コンパイラおよびランタイムから見える

    • @

      Retention

      のデフォルトは

      RetentionPolicy.SOURCE

      です。

実行時にアクセス可能なはずの注釈があるとします。

@Retention(RetentionPolicy.RUNTIME)
@Target(TYPE)
public @interface RetentionAnnotation {
}

それでは、クラスにアノテーションを追加すると、

@RetentionAnnotation
@Deprecated
public class AnnotatedClass {
}

保持されている注釈の数を確認するには、

AnnotatedClass

を参照してください。

@Test
public void whenAnnotationRetentionPolicyRuntime__shouldAccess() {
    AnnotatedClass anAnnotatedClass = new AnnotatedClass();
    Annotation[]annotations = anAnnotatedClass.getClass().getAnnotations();
    assertThat(annotations.length, is(1));
}


  • @ @ RetentionAnnotation

    の保持ポリシーは

    RUNTIME

    で、

    @ Deprecated__はそうではないため、値は1です。


4.3.

@継承されました


状況によっては、アノテーションを親クラスにバインドするためにサブクラスが必要になることがあります。

注釈を注釈付きクラスからそのサブクラスに伝播させるために

@ Inherited

注釈を使用することができます。

カスタムアノテーションに

@ Inherited

を適用してから

BaseClass

に適用すると、

@Inherited
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
public @interface InheritedAnnotation {
}

@InheritedAnnotation
public class BaseClass {
}

public class DerivedClass extends BaseClass {
}

それから、BaseClassを拡張した後、

DerivedClass

は実行時に同じアノテーションを持っているように見えるでしょう:

@Test
public void whenAnnotationInherited__thenShouldExist() {
    DerivedClass derivedClass = new DerivedClass();
    InheritedAnnotation annotation = derivedClass.getClass()
      .getAnnotation(InheritedAnnotation.class);

    assertThat(annotation, instanceOf(InheritedAnnotation.class));
}


@ Inherited

アノテーションがないと、上記のテストは失敗します。


4.4.

@文書化された


デフォルトでは、JavaはJavadocでのアノテーションの使い方を文書化していません。

  • しかし、私たちは

    @ Documented

    アノテーションを使ってJavaのデフォルトの振る舞いを変えることができます** 。


@ Documented

を使用するカスタムアノテーションを作成すると、

@Documented
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ExcelCell {
    int value();
}

そして、それを適切なJava要素に適用します。

public class Employee {
    @ExcelCell(0)
    public String name;
}

それから

Employee

Javadocはアノテーションの使い方を明らかにします:

画像:/uploads/Screen-Shot-2018-02-08-at-1.38.45-PM-1.png%20868w[画像]


4.5. __ @繰り返し可能

特定のJava要素に同じ注釈を複数回指定すると便利なことがあります。

Java 7より前は、アノテーションを単一のコンテナアノテーションにまとめる必要がありました。

@Schedules({
    @Schedule(time = "15:05"),
    @Schedule(time = "23:00")
})
void scheduledAlarm() {
}

しかし、Java 7はよりクリーンなアプローチをもたらしました。



@ Repeatable

アノテーションを使用すると、アノテーションを繰り返し可能にすることができます。

@Repeatable(Schedules.class)
public @interface Schedule {
    String time() default "09:00";
}


@ Repeatable

を使用するには、コンテナアノテーションも必要です

__.


この場合、

@ Schedules__を再利用します。

public @interface Schedules {
    Schedule[]value();
}

もちろん、これはJava 7以前のものと非常によく似ています。しかし、今の価値は、

@ Schedule

を繰り返す必要があるときにラッパー

@ Schedules

が指定されなくなったことです。

@Schedule
@Schedule(time = "15:05")
@Schedule(time = "23:00")
void scheduledAlarm() {
}

Javaにはラッパーアノテーションが必要なので、Java 7より前のアノテーションリストから繰り返し可能なアノテーションに移行するのは簡単でした。


5結論

この記事では、すべてのJava開発者が精通しているべきJava組み込みアノテーションについて説明しました。

いつものように、この記事のすべての例はhttps://github.com/eugenp/tutorials/tree/master/core-java-8[GitHubに掲載]で見つけることができます。