Dev Study
← 解説「アノテーションとリフレクション」に戻る

サンプルコードで身につける: アノテーションとリフレクション

解説で学んだ概念を、5つの具体例で確認します。素朴な例から実務寄りの例へと進みます。

13種類の保持方針で、実行時に見えるのは1つ

同じに3つのを付け、から何個見えるかを確かめます。見えるのは (RUNTIME) を付けて定義した1つだけです。アノテーションを自作するときは、どこまで残すかを定義の側で決めます。

Java

2.class の中ではどう記録されているか

上の Target.class を で見たものです。RUNTIME のものは RuntimeVisibleAnnotations、@Retention を付けなかったものは RuntimeInvisibleAnnotations という欄に入り、SOURCE のものは のどこにもありませんでした。付けない場合は消えるのではなく、記録はされるがリフレクションに出てこない、という扱いです。

$ javap -v Target.class(JDK 25、末尾の抜粋)追跡ログ
RuntimeVisibleAnnotations:
0: #14(#15=s#16)
KeptAtRuntime(
value="a"
)
RuntimeInvisibleAnnotations:
0: #18(#15=s#19)
DefaultRetention(
value="b"
)

3コンストラクタの引数の型を実行時に読む

が何を受け取るかを実行時に調べる例で、引数のクラス(Repo、List、int)に加え、宣言に書いた List<String> の String まで読めます。一方、new した ArrayList<String> と ArrayList<Integer> は同じクラスでした。残るのは宣言に書かれた型で、の側はを持っていません。

Java

4引数の名前は既定では残らない

型は実行時に読めますが、引数の名前は既定のコンパイルでは残らず、arg0、arg1 のような仮の名前が返ります。 に を付けてコンパイルすると、ソースに書いた名前が読めるようになります。下の出力は 25 で同じコードをコンパイルし直したときのものです。

$ javac -parameters でコンパイルし直して実行(JDK 25)コマンド出力
repo : Repo namePresent=true
names : java.util.List namePresent=true
limit : int namePresent=true
Java

5TypeScript のデコレータと型はどうなるか

同じ形を TypeScript で書いた対照実験です(typescript 7.0.2、Node 24.21.0、experimentalDecorators を有効にした旧方式のデコレータ)。デコレータは実行時に関数として呼ばれ、型は emitDecoratorMetadata を付けてコンパイルし、さらに reflect-metadata を読み込んだときだけ実行時に読めました。その場合もの Clock は Object、string[] は Array になっています。

emitDecoratorMetadata の有無による出力 JS の差分と、実行結果コマンド出力
$ diff out-plain/main.js out-meta/main.js
...
29c32,33
< Injectable()
---
> Injectable(),
> __metadata("design:paramtypes", [Repo, Object, Array, Number])
$ node out-plain/main.js
decorator called for: Service
Service.length (number of ctor params): 4
design:paramtypes: undefined
$ node -r reflect-metadata out-meta/main.js
decorator called for: Service
Service.length (number of ctor params): 4
design:paramtypes: [
[class Repo],
[Function: Object],
[Function: Array],
[Function: Number]
]
// デコレータは、クラスが定義されるときに呼ばれる関数
function Injectable(): ClassDecorator {
  return (target) => {
    console.log("decorator called for:", (target as any).name);
  };
}

interface Clock { now(): number }   // コンパイル時にしか存在しない
class Repo {}

@Injectable()
class Service {
  constructor(private repo: Repo, private clock: Clock, private names: string[], private limit: number) {}
}

console.log("Service.length (number of ctor params):", Service.length);
console.log("design:paramtypes:", (Reflect as any).getMetadata?.("design:paramtypes", Service));

図やコードに出てくる用語(タップで説明)