title: 【転載】実は Java のアノテーションはただのマークで、特に能力はなく、この記事でマスターできるので、保存する価値があります!
date: 2021-08-16 09:18:01
comment: false
toc: true
category:
- Java
tags: - 転載
- Java
- アノテーション
- マーク
- 能力
- 保存
- マスター
この記事は実は Java のアノテーションはただのマークで、特に能力はなく、この記事でマスターできるので、保存する価値があります!から転載されています。
前言#
Java には不思議な存在、アノテーションがあります。毎日他の人を @するあの存在、いったい何者なのでしょうか?#
アノテーションとは#
JDK5 から、Java はメタデータのサポートを追加しました。つまりアノテーションです。アノテーションとコメントには一定の違いがあり、アノテーションはコード内の特別なマークとして理解できます。これらのマークは、コンパイル、クラスロード、実行時に読み取られ、対応する処理が実行されます。アノテーションを通じて、開発者は元のコードやロジックを変更することなく、ソースコードに補足情報を埋め込むことができます。
ビルトインアノテーション#
二当家は IDE でクラスを作成し、インターフェースを実装すると、インターフェースを実装したメソッドの上に自動的に @Override のマークが追加されることに気付きました。
このマークがアノテーションであり、@Override のような JDK にビルトインされたアノテーションは他にもいくつかあります。これらはすべて java.lang パッケージの下にあり、それぞれ見ていきましょう。
@Override#
子クラスが親クラスのメソッドをオーバーライドする際、子クラスはこのアノテーションを追加できます。これは何の役に立つのでしょうか?これは、子クラスが確かに親クラスのメソッドをオーバーライドしていることを保証し、初歩的なエラーを避けることができます。
最初は何の役に立つのかわかりませんでしたが、ある日インターフェース内のこのメソッドを削除したところ、結果としてコンパイルが通らなくなりました。
以下はコマンドラインでのコンパイル結果です。この時、私はこのマークの役割を理解しました。これはコンパイラやソースコードを読む人に、このメソッドがスーパークラスのメソッドをオーバーライドまたは実装していることを伝えることができます。
@Deprecated#
このアノテーションは、特定のプログラム要素(クラス、メソッドなど)が時代遅れであることを示すために使用されます。他のプログラムが時代遅れのクラスやメソッドを使用すると、コンパイラは警告を出します(取り消し線、これはよく見かけるでしょう)。
@SuppressWarnings#
警告を抑制します。
package com.secondgod.annotation;
import java.util.ArrayList;
import java.util.List;
public class Test {
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List objList = intList;
objList.add("二当家的");
}
}
コマンドラインで推奨警告を有効にしてコンパイルします(コマンドは javac -d . -Xlint Test.java のような形です。以下で推奨警告を有効にしてコンパイルすることについて再度言及しますので、ここでは詳しく説明しません)。
コードを少し修正すると、コンパイル警告を消すことができます。
package com.secondgod.annotation;
import java.util.ArrayList;
import java.util.List;
public class Test {
@SuppressWarnings({"unchecked", "rawtypes"})
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>();
List objList = intList;
objList.add("二当家的");
}
}
@SafeVarargs#
不確定な型(例えば:ジェネリック)の可変引数のコンストラクタやメソッドを宣言する際、Java コンパイラは unchecked 警告を報告します。これらの状況を考慮し、プログラマが宣言されたコンストラクタやメソッドの本体がその varargs パラメータに対して潜在的に不安全な操作を行わないと判断した場合、@SafeVarargs を使用してマークすることができます。そうすれば、Java コンパイラは unchecked 警告を報告しません。
package com.secondgod.annotation;
import java.util.List;
public class Test {
public final void test(List<String>... stringLists) {
}
}
コマンドラインで推奨警告を有効にしてコンパイルします。
コードを少し修正すると、コンパイル警告を消すことができます。
package com.secondgod.annotation;
import java.util.List;
public class Test {
@SafeVarargs
public final void test(List<String>... stringLists) {
}
}
@FunctionalInterface#
関数型インターフェースアノテーションで、これは Java 1.8 バージョンで導入された新機能です。関数型プログラミングが流行しているため、Java 8 もこの機能をタイムリーに追加しました。
このアノテーションは何の役に立つのでしょうか?このアノテーションは、このインターフェースが 1 つの抽象メソッドのみを持つことを保証します。注意が必要なのは、これはインターフェースのみを修飾できるということです。
- 抽象メソッドがないインターフェース
package com.secondgod.annotation;
@FunctionalInterface
public interface Test {
}
- 1 つ以上の抽象メソッドがあるインターフェース
package com.secondgod.annotation;
@FunctionalInterface
public interface Test {
String test1();
String test2();
}
- 1 つの抽象メソッドのみを持つインターフェース
コンパイルが通ります。
package com.secondgod.annotation;
@FunctionalInterface
public interface Test {
String test();
}
- 1 つの抽象メソッドのみを持つ抽象クラス
package com.secondgod.annotation;
@FunctionalInterface
public abstract class Test {
public abstract String test();
}
メタアノテーション#
@Override のソースコードを開くと、このアノテーションの定義自体にもアノテーションがあり、つまりアノテーションのアノテーションです。人々は通常これをメタアノテーションと呼びます。メタアノテーションは他のアノテーションを説明するためのアノテーションであり、カスタムアノテーションを作成する際にメタアノテーションを使用できます。
JDK にビルトインされたメタアノテーションはすべて java.lang.annotation パッケージの下にあります。
@Target#
Target はターゲットの意味で、@Target はアノテーションが適用される場所を指定します。
取値 | アノテーション使用範囲 |
---|---|
TYPE | クラスまたはインターフェースに使用可能 |
FIELD | フィールド / 属性に使用可能 |
METHOD | メソッドに使用可能 |
PARAMETER | パラメータに使用可能 |
CONSTRUCTOR | コンストラクタに使用可能 |
LOCAL_VARIABLE | ローカル変数に使用可能 |
ANNOTATION_TYPE | アノテーションタイプに使用可能(@interface で修飾されたタイプ) |
PACKAGE | java ファイルの package 情報を記録するために使用 |
TYPE_PARAMETER | 型変数の宣言文に使用可能 |
TYPE_USE | 使用される型の任意の文に使用可能 |
@Documented#
@Documented アノテーションで修飾されたアノテーションクラスは JavaDoc ツールによって文書として抽出されます。デフォルトでは、JavaDoc はアノテーションを含みませんが、アノテーションを宣言する際に @Documented を指定すると、JavaDoc などのツールによって処理され、アノテーションタイプ情報が生成されるヘルプ文書に含まれます。
@Inherited#
Inherited は継承の意味ですが、アノテーション自体が継承できるというわけではなく、スーパークラスが @Inherited アノテーションで注釈されたアノテーションで注釈されている場合、そのサブクラスが何のアノテーションも適用されていない場合、そのサブクラスはスーパークラスのアノテーションを継承します。
@Repeatable#
Repeatable は自然に繰り返し可能という意味です。Java 8 で新たに追加され、同じプログラム要素にアノテーションを繰り返し適用することを許可します。同じアノテーションを複数回使用する必要がある場合、通常は @Repeatable アノテーションを利用します。Java 8 バージョン以前は、同じプログラム要素の前に同じタイプのアノテーションを 1 つだけ持つことができ、同じ要素の前に複数の同じタイプのアノテーションを使用する必要がある場合は、アノテーション「コンテナ」を使用する必要がありました。
@Retention#
@Retention はアノテーションのライフサイクルを説明するために使用されます。つまり、そのアノテーションが保持される期間の長さです。@Retention アノテーション内のメンバー変数(value)は保持ポリシーを設定するために使用され、value は java.lang.annotation.RetentionPolicy 列挙型で、RetentionPolicy には以下の 3 つの列挙定数があります。
取値 | ライフサイクル |
---|---|
SOURCE | ソースファイル内で有効(コンパイラが使用可能) |
CLASS | class ファイル内で有効(コンパイラと仮想マシンが使用可能) |
RUNTIME | 実行時に有効(コンパイラ、仮想マシン、プログラム実行時に使用可能) |
@Native#
@Native アノテーションで修飾されたメンバー変数は、この変数がネイティブコードから参照できることを示します。これは通常、コード生成ツールによって使用されます。彼も java.lang.annotation パッケージの下にありますが、私は彼をメタアノテーションとは見なしていません。なぜなら、彼の使用ターゲットはアノテーションではないからです。
カスタムアノテーション#
Spring、Hibernate、Mybatis などのフレームワークにはアノテーションがあります。二当家もカスタムアノテーションを作成しようとしました。まずは @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é
* @author Joshua Bloch
* @jls 9.6.1.4 @Override
* @since 1.5
*/
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}
私たちも同じようにカスタムアノテーションを定義します。
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* テストアノテーション
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyAnnotation {
}
私たちがカスタムアノテーションを定義する際、通常はライフサイクルを RUNTIME に設定します。ライフサイクルが SOURCE の場合、コンパイラの協力が必要です。ライフサイクルが CLASS の場合、仮想マシンの協力が必要です。プログラム実行時の動作のみが私たちがカスタマイズできるものです。
さて、このカスタムアノテーションを使用してみましょう。
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* テストアノテーション
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
}
/**
* カスタムアノテーションのテスト
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
public class Test {
@MyAnnotation
private String name;
}
一部のアノテーションにはパラメータが付いていることもあり、二当家も追加しました。
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* テストアノテーション
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String name();
}
/**
* カスタムアノテーションのテスト
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
public class Test {
@MyAnnotation(name = "二当家的")
private String name;
}
使用時に値を設定する際は、必ず属性名を記述する必要があります。@Target などの一部のアノテーションでは、属性名が「value」であるため、記述しなくても構いません。
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* テストアノテーション
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value();
}
/**
* カスタムアノテーションのテスト
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
public class Test {
@MyAnnotation("二当家的")
private String name;
}
アノテーション内で属性を定義した場合、使用する際には必ず値を設定する必要があります。ただし、アノテーション内でデフォルト値を定義することができ、使用時には値を設定しなくても構いません。
package com.secondgod.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* テストアノテーション
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface MyAnnotation {
String value() default "二当家的";
}
/**
* カスタムアノテーションのテスト
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
public class Test {
@MyAnnotation
private String name;
}
アノテーションの内容は非常に少なく、明らかにその機能は内部で実装されていません。したがって、アノテーションは単なるマークであり、本質的には何の機能も持っていません。その機能はどのように実現されるのでしょうか?
アノテーション実践#
私たちはリフレクションとインスペクションを組み合わせてアノテーションを使用し、環境変数を使ってプロパティを自動的に初期化する小さなツールを実現します。
package com.secondgod.annotation;
import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
/**
* ツールクラス
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
public class MyUtils {
/**
* プライベートコンストラクタ、インスタンス化不可
*/
private MyUtils() {}
/**
* 環境変数に基づいてプロパティを初期化するために使用されます
*/
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@interface EnvironmentVariables {
/**
* 環境変数のキー
* @return
*/
String value();
}
/**
* クラスインスタンスを取得し、環境変数で初期化します
*
* @param clazz
* @param <T>
* @return
* @throws InstantiationException
* @throws IllegalAccessException
* @throws java.beans.IntrospectionException
* @throws InvocationTargetException
*/
public static <T> T getInstance(Class<T> clazz) throws InstantiationException, IllegalAccessException, IntrospectionException, InvocationTargetException {
T obj = clazz.newInstance();
setEnvironmentVariables(obj);
return obj;
}
/**
* 環境変数に基づいてオブジェクトのプロパティに値を設定します
*
* @param o
* @throws IllegalAccessException
*/
public static void setEnvironmentVariables(Object o) throws IllegalAccessException, IntrospectionException, InvocationTargetException {
for (Field f : o.getClass().getDeclaredFields()) {
// リフレクションを利用してアノテーションを読み取る
EnvironmentVariables environmentVariables = f.getDeclaredAnnotation(EnvironmentVariables.class);
if (environmentVariables != null) {
// アノテーションが存在する場合、環境変数を読み取る
String value = System.getProperty(environmentVariables.value());
// インスペクションを利用して値を書き込む
PropertyDescriptor pd = new PropertyDescriptor(f.getName(), o.getClass());
pd.getWriteMethod().invoke(o, value);
}
}
}
}
私たちは仮想マシンの情報を表すクラスを定義し、アノテーションで属性フィールドをマークします。
package com.secondgod.annotation;
import java.text.MessageFormat;
/**
* 仮想マシン情報
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
public class VmInfo {
@MyUtils.EnvironmentVariables(value = "java.vm.version")
private String version;
@MyUtils.EnvironmentVariables(value = "java.vm.vendor")
private String vendor;
@MyUtils.EnvironmentVariables(value = "java.vm.name")
private String name;
@MyUtils.EnvironmentVariables(value = "java.vm.specification.name")
private String specName;
@MyUtils.EnvironmentVariables(value = "java.vm.specification.vendor")
private String specVendor;
@MyUtils.EnvironmentVariables(value = "java.vm.specification.version")
private String specVersion;
@MyUtils.EnvironmentVariables(value = "java.vm.info")
private String info;
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getVendor() {
return vendor;
}
public void setVendor(String vendor) {
this.vendor = vendor;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSpecName() {
return specName;
}
public void setSpecName(String specName) {
this.specName = specName;
}
public String getSpecVendor() {
return specVendor;
}
public void setSpecVendor(String specVendor) {
this.specVendor = specVendor;
}
public String getSpecVersion() {
return specVersion;
}
public void setSpecVersion(String specVersion) {
this.specVersion = specVersion;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
public String toString() {
return MessageFormat.format("version={0},vendor={1},name={2},specName={3},specVendor={4},specVersion={5},info={6}", version, vendor, name, specName, specVendor, specVendor, info);
}
}
さて、私たちのツールをテストしてみましょう。
package com.secondgod.annotation;
import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
/**
* カスタムアノテーションのテスト
*
* @author 二当家的白帽子 https://le-yi.blog.csdn.net/
*/
public class Test {
public static void main(String[] args) throws IntrospectionException, InvocationTargetException, IllegalAccessException, InstantiationException {
VmInfo info1 = new VmInfo();
System.out.println("普通の方法でインスタンス化した後:" + info1);
VmInfo info2 = MyUtils.getInstance(VmInfo.class);
System.out.println("私たちの小さなツールを使ってインスタンス化した後:" + info2);
}
}
内容が多いため、画像が全てキャプチャされていませんが、私たちの小さなツールが期待通りに機能していることが明らかです。素晴らしいです。
結び#
Spring、Hibernate、Mybatis などのフレームワークにはカスタムアノテーションがあり、しばしば設定ファイル以外の別の設定選択肢として使用されます。それぞれに利点と欠点があり、実際の状況に応じて選択すべきです。設定ファイルを変更することでプログラムを再起動できますが、アノテーションの変更は明らかに再コンパイルが必要です。しかし、二当家は使用する場所に設定を直接置くのが非常に便利で、可読性も高いと感じています。したがって、リリース後に変更する必要がない限り、二当家はアノテーションを使用することを好みます。ただし、アノテーションを使用すると、設定が分散するため、設定ファイルの集中性には劣ります。ああ、皆さんそれぞれの意見があるでしょう。