【Java】アノテーション

本稿ではJavaのアノテーションの使い方についてみていきます。

アノテーションはプログラムの特定の箇所へ注釈として付加することができものです。

プログラムの特定箇所に付加することによって、プログラムのスペルミスを減らしたり、警告を出ないようにしたりすることができます。

@Overrideによって、メソッドのオーバーライドを明示する

@Overrideをメソッドに付加するとメソッドのオーバーライドを行うことを明示できます。

もし、オーバーライドできていない場合にはコンパイルエラーにしてくれます。

public class Test extends Parent {
	@Override
	void tests(){
	}
}

class Parent{
	
	void test(){
	}
}
C:\Users\User\Desktop\java>javac Test.java
Test.java:2: エラー: メソッドはスーパータイプのメソッドをオーバーライドまたは実
装しません
        @Override
        ^
エラー1個

スペルミスなどでオーバーライドできていなかった場合に役立ちますね。

@Deprecatedによって、非推奨機能であることを明示する

@Deprecatedをクラスやメソッドに付加すると非推奨であることを明示できます。

非推奨メソッドを使用しようとするとコンパイル時に警告を出してくれます。

public class Test {
	
	public static void main(String[] args) {
		B b = new B();
	}
}

@Deprecated
class B{
}
C:\Users\User\Desktop\java>javac Test.java
注意:Test.javaは推奨されないAPIを使用またはオーバーライドしています。
注意:詳細は、-Xlint:deprecationオプションを指定して再コンパイルしてください。

@SuppressWarningsによって、警告を無視することを明示する

@SuppressWarningsをメソッドやフィールドに使用することで警告を無視することを明示する

コンパイル時、特定の警告を表示しないようにしてくれます。

@SuppressWarningsは引数を持つアノテーションですので@SuppressWarnings(文字列)で指定します。

下記のコードをコンパイルしようとすると警告が表示されます。

public class Test {
	<T> T tests(Object o){
		return (T)o;
	}
}
実行結果
C:\Users\User\Desktop\java>javac Test.java
注意:Test.javaの操作は、未チェックまたは安全ではありません。
注意:詳細は、-Xlint:uncheckedオプションを指定して再コンパイルしてください。

下記のように@SuppressWarningsを入れると警告は出ません。

public class Test {
	@SuppressWarnings("unchecked")
	<T> T tests(Object o){
		return (T)o;
	}
}

@FunctionalInterfaceによって、関数インターフェースであることを明示する

@FunctionalInterfaceをインターフェースに付加すると単一の抽象メソッドを定義した関数インターフェースであることを明示できます。

複数の抽象メソッドを定義しようとするとエラーがでます。

@FunctionalInterface
public interface Test {
	void test();
	void test2();
}
実行結果
C:\Users\User\Desktop\java>javac *.java
Test.java:1: エラー: 予期しない@FunctionalInterface注釈
@FunctionalInterface
^
  Testは機能インタフェースではありません
    インタフェース Testで複数のオーバーライドしない抽象メソッドが見つかりました
エラー1個

関数インターフェースはラムダ式を使用できます。

自作のアノテーションを定義する

アノテーションは自作することができます。

@interface 注釈名{
}

引数も定義できます。default修飾子を使って初期値も設定できます。

@interface 注釈名{
	引数の型 value();
	引数の型 value2() default 値;
}

引数は基本型、String型、Class型、注釈型、列挙型またはそれらの1次元配列のみ使用できます。

自作アノテーションは付加できる箇所を指定したり、複数定義可能なアノテーションにしたり、アノテーションの値を実行時に保持するかなどを定義できます。

注釈型に専用の注釈を使用して定義できます。

@Target(定義可能な場所)
@Retention(保持ルール)
@Repeatable(注釈のClass型)
@interface 注釈名{
}

例えば下記のように定義できます。

@Target({ElementType.TYPE,ElementType.METHOD})//クラスやメソッドに使用できるように指定
@Retention(RetentionPolicy.SOURCE)//ソース内で有効な注釈
@Repeatable(TestAnnotations.class)//アノテーションを複数定義できるようにする
@interface TestAnnotation{
	String[] value();
	int num() default 0;
}

//TestAnnotationを複数定義できるようにするための親注釈
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.SOURCE)
@interface TestAnnotations{
	TestAnnotation[] value();
}

上記アノテーションを利用する場合はこんな感じ。

@TestAnnotation("ばかぁ!")
public class Test {

	@TestAnnotation({"おたんこなーす!","いうことなーす!"})
	@TestAnnotation(value={"ばかぁ!","あほぉ!"}, num=20)
	public static void main(String[] args) {
	}
	
}

引数がvalueだけの時は省略可能です。

default引数に関しては省略可能です。

引数が配列の場合は{}を利用して複数定義可能です。

またアノテーションの値はプログラムから取得可能にすることもできます。

RetentionPolicy.RUNTIMEを指定している場合、プログラムから値を取得可能です。

試しにソース管理のためのアノテーションを定義して、プログラム内から値を取得してみましょう。

import java.lang.annotation.Annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Repeatable;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.lang.reflect.InvocationTargetException;
 
@Create(value=User.YAMADA,date="2000/10/10")
@Update(value=User.INU,date="2000/11/10")
@Update(value=User.YAMADA,date="2000/12/1")
@Update(date="2000/10/10")
@Update(value=User.OKADA,date="2010/1/4")
@Update(value=User.KAWASAKI,date="2010/2/2")
@Update(value=User.BBA,date="2020/5/25")
@RiskLevel(ProgramDifficulty.VERYHIGH)
public class Test {
	
	@RiskLevel(ProgramDifficulty.VERYHIGH)
	public static void main(String[] args) throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		process1();
		process2();
		process3();
		process4();
	}
 
	@RiskLevel(ProgramDifficulty.LOW)
	public static void process1() {
		System.out.println("簡単なプログラム");
	}
	@RiskLevel(ProgramDifficulty.MIDDLE)
	public static void process2() {
		System.out.println("普通のプログラム");
	}
	@RiskLevel(ProgramDifficulty.HIGH)
	public static void process3() {
		System.out.println("難しいプログラム");
	}
	@RiskLevel(ProgramDifficulty.VERYHIGH)
	public static void process4() throws NoSuchMethodException, SecurityException, IllegalAccessException, IllegalArgumentException, InvocationTargetException {
		//クラスに付加されたアノテーションを表示
		Annotation[] annotations = Test.class.getAnnotations();
		for ( Annotation a  :annotations ) {
			
			//作成情報の表示
			if ( a instanceof Create ) {
				Create c = (Create)a;
				User user = c.value();
				System.out.println("CREATE "+user+" "+c.date());
			}
			
			//更新情報リストの表示
			if ( a instanceof Updates ) {
				Updates upds = (Updates)a;
				Update[] updList = upds.value();
				for ( Update u : updList ) {
					User user = u.value();
					System.out.println("UPDATE "+user+" "+u.date());
				}
			}
			
			//リスクレベルの表示
			if ( a instanceof RiskLevel ) {
				RiskLevel lv = (RiskLevel)a;
				ProgramDifficulty level = lv.value();
				System.out.println(level);
			}
			
		}
	}
}
 
//ユーザー
enum User{
	UNKNOWN,YAMADA,KAWASAKI,OKADA,INU,BBA;
}
 
//プログラム難易度
enum ProgramDifficulty{
	UNKNOWN,LOW,MIDDLE,HIGH,VERYHIGH;
}
 
//ソース作成情報
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Create{
	User value() default User.UNKNOWN;
	String date();
}
 
//ソース更新情報
@Repeatable(Updates.class)
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Update{
	User value() default User.UNKNOWN;
	String date();
}
 
//ソース更新情報リスト
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@interface Updates{
	Update[] value();
}
 
//修正リスクレベル
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD, ElementType.PACKAGE, ElementType.MODULE})
@interface RiskLevel{
	ProgramDifficulty value() default ProgramDifficulty.UNKNOWN;
}
実行結果
簡単なプログラム
普通のプログラム
難しいプログラム
CREATE YAMADA 2000/10/10
UPDATE INU 2000/11/10
UPDATE YAMADA 2000/12/1
UPDATE UNKNOWN 2000/10/10
UPDATE OKADA 2010/1/4
UPDATE KAWASAKI 2010/2/2
UPDATE BBA 2020/5/25
VERYHIGH

私は自作アノテーションを作る機会がほとんどないので、実務でどう使われるのかはわからないですね。

アノテーションで指定したclass一覧をテストの対象にするみたいな仕組みなら作ったことがありますけど、実務ではどういった使われ方するんだろう・・・

Java

Posted by nompor