LoginSignup
3
7

More than 3 years have passed since last update.

【Java】アノテーション

Posted at

アノテーション

  • クラス・インターフェースなどの型、メンバーに付与できるマーク
  • アプリ本来のロジックとは直接関係しない付随情報をコードに追加できる
  • 対象となる宣言の先頭に記述

@Override

  • 標準ライブラリで提供されるアノテーション
  • 対象メソッドが基底クラスのメソッドをオーバーライドしていることを宣言
  • 引数のミスでオーバーライドできてない時もコンパイラが警告してくれる
public class Person {
  private String firstName;
  private String lastName;

  public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  @Override
  public String toString() {
    return String.format("名前は、%s %s です。",
        this.lastName, this.firstName);
  }

  public String getLastName() {
    return this.lastName;
  }

  public String getFirstName() {
    return this.firstName;
  }
}

@WebServlet

  • サーブレットとはサーバー環境でJavaアプリを実行するための基本技術の一種
  • サーブレットの基本的な構成情報
    • urlPatterns:サーブレットを呼ぶためのURL
    • initParams:サーブレットで利用できるパラメータ情報

アノテーションのポイント

  • アノテーション入れ子が可能
  • 要素1個なら配列の{...}省略可能
    • urlPatterns = "/init-param"
  • 属性名valuesは省略可能
  • 属性がない場合、()省略可能
    • マーカーアノテーション(@Overrideなど)

標準アノテーション

  • @Override:基底クラスのメソッドを上書きしていることを宣言
  • @Deprecated:クラスやメンバーなどが非推奨であることを宣言
  • @SuppressWarnings:コンパイラー警告を抑制
  • @SafeVarargs:可変長引数の型安全を宣言
  • @FunctionalInterface:インターフェースを関数型インターフェースとして定義することを宣言

@Deprecated

  • クラス、メソッドなどが非推奨であることを宣言
  • IntegerコンストラクタはJava9以降で非推奨(since="9"
    • ドキュメンテーションでも記述
  • コンパイルは通るが警告がでる
Integer.java
/**
 * 中略
 * @deprecated
 * It is rarely appropriate to use this constructor. The static factory
 * {@link #valueOf(int)} is generally a better choice, as it is
 * likely to yield significantly better space and time performance.
 */

@Deprecated(since="9")
public Integer(int value) {
    this.value = value;
}

@SuppressWarnings

  • 特定範囲/種類のコンパイラー警告を非表示
    • 他の本来確認すべき警告の見落としを防げる
    • 意図して警告対象のコードを利用しているという意思表示
public class AnnotationBasic {

  public static Integer process() {
   @SuppressWarnings("deprecation")
    var i = new Integer(108);
   return i;
  }

  public static void main(String[] args) {
    System.out.println(process());
  }
}

@SafeVarargs

  • 可変長引数の型安全を宣言し、警告が表示されない
    • ジェネリック型を可変長引数とすると警告が出る
  • あくまでも型安全であることを開発者が認識してるという宣言

自作アノテーション

  • アノテーションは修飾子と違って自作できる
  • アノテーションは@interface命令で定義
  • @interface配下でアノテーションで利用できる属性を宣言
    • 以下の@ClassInfoではバージョン属性をversion属性、value属性で指定する
      • @ClassInfo(version="2.1", description="アノテーションテスト")
      • @ClassInfo(version="2.1")
  • アノテーション構成情報宣言するメタアノテーションを記述
    • @Documented:javadocにアノテーション情報反映
    • @Target:アノテーションをどの要素に適用するか決める
    • @Inherited:アノテーションが派生クラスにも継承
    • @Retention:アノテーション情報をどのタイミングまで保持するか
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

//構成情報宣言
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
//@interface命令でアノテーション定義
public @interface ClassInfo {
  //アノテーションで利用できる属性を宣言
  String value() default "";
  String version() default "";
  String description() default "";
}

リフレクションでアノテーション利用

  • リフレクション(Reflection):コードの実行中に型情報取得、操作する
  • 以下ではClass.forNameメソッドで指定した型情報をClassクラスで取得
  • マーカーアノテーション:アノテーションが保持する情報(属性)ではなくアノテーション存在そのものに関心がある
    • isAnnotationPresentメソッドでアノテーション有無確認
package mypackage;

//ClassInfoアノテーション指定
@ClassInfo(version = "2.1", description = "アノテーションの動作テスト")
// @ClassInfo("2.1")
public class AnnotationClient {
  public static void main(String[] args) throws ClassNotFoundException {
    //Classオブジェクトを経由して配下のメンバー取得
    var clazz = Class.forName("mypackage.AnnotationClient");
    //getAnnotationメソッドでクラスに付与されたアノテーション取得
    var info = (ClassInfo)
 clazz.getAnnotation(ClassInfo.class);
    System.out.println("バージョン:" +
        (info.value().equals("") ? info.version() : info.value()));
    System.out.println("説明:" + info.description());
  }
}

リフレクションのメソッド

オブジェクト生成

  • コンストラクタ経由でインスタンス生成
    • 呼び出すコンストラクタをClassクラスのgetConstructorメソッドで生成
    • newInstanceメソッドで呼び出すことでオブジェクト生成
    • Array.newInstanceメソッドで配列生成
    • setメソッドで配列要素設定
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;

public class ReflectInstance {

  public static void main(String[] args) {
    try {
      //Fileクラス取得
      var clazz = File.class;

      //コンストラクタ経由でFileオブジェクト生成
      var c = clazz.getConstructor(String.class);
      var fl = c.newInstance("./data/data.txt");
      System.out.println(fl);

      //サイズ2の配列生成
      var list = (File[]) Array.newInstance(File.class, 2);
      Array.set(list, 0, fl);
      System.out.println(Arrays.toString(list));
    } catch (InstantiationException | IllegalAccessException |
        IllegalArgumentException | InvocationTargetException
        | NoSuchMethodException | SecurityException e) {
      e.printStackTrace();
    }
  }
}

全メソッド取得

  • getMethodsメソッドでFileクラスのPublicメソッド列挙
    • publicメソッドをMethodオブジェクトの配列として返す
  • public以外も取りたいときはgetDeclaredMethodsメソッドで全メソッド取得
import java.io.File;

public class ReflectMethods {

  public static void main(String[] args) {
    //Fileクラス取得
    var str = File.class;
    //Fileクラス配下のpublicメソッドをgetNameでメソッド名列挙
    for (var m : str.getMethods()) {
      System.out.println(m.getName());
    }
  }
}

メソッド実行

  • Methodオブジェクトを介して実行
  • getMethodメソッドで個別のメソッドを取得し、invokeメソッドで実行
import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;

public class ReflectInvoke {

  public static void main(String[] args) {
    try {
      //Fileクラス取得
      var clazz = File.class;
      //Fileオブジェクト生成
      var f1 = clazz.getConstructor(String.class).newInstance("./data/data.txt");
      var f2 = clazz.getConstructor(String.class).newInstance("./data/sample.txt");
      //renameToメソッド取得、実行
      Method m = clazz.getMethod("renameTo", File.class);
      System.out.println(m.invoke(f1, f2));
    } catch (NoSuchMethodException | SecurityException |
        InstantiationException | IllegalAccessException
        | IllegalArgumentException | InvocationTargetException e) {
      e.printStackTrace();
    }
  }
}

フィールド取得、設定

  • publicフィールドはgetFieldメソッドで取得
  • その他フィールドはgetDeclaredFiledメソッドで取得
  • 引数は取得したいフィールドの名前を指定
  • Fieldオブジェクト取得したらget/setメソッドでフィールド値を取得/設定
  • privateフィールドはそのままアクセスできないので、setAccessibleメソッドでアクセスを明示的に許可
  • 注意:リフレクションは低速
//PersonクラスのlastNameフィールド取得

import java.lang.reflect.InvocationTargetException;
import example.Person;

public class ReflectField {

  public static void main(String[] args) {
    try {
      var clazz = Person.class;
      var con = clazz.getConstructor(String.class, String.class);
      var p = con.newInstance("太郎", "山田");
      //フィールド取得
      var last = clazz.getDeclaredField("lastName");
      //privateフィールドへのアクセスを明示的に許可
      last.setAccessible(true);
      //get/setメソッドでフィールド値取得/設定
      last.set(p, "鈴木");
      System.out.println(last.get(p)); //鈴木
    } catch (NoSuchMethodException | SecurityException |
        InstantiationException | IllegalAccessException
        | IllegalArgumentException | InvocationTargetException |
        NoSuchFieldException e) {
      e.printStackTrace();
    }
  }
}

package example;
public class Person {
  private String firstName;
  private String lastName;
  public Person(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  @Override
  public String toString() {
    return String.format("名前は、%s %s です。",
        this.lastName, this.firstName);
  }
  public String getLastName() {
    return this.lastName;
  }
  public String getFirstName() {
    return this.firstName;
  }
}
3
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
7