入門Javaのリフレクション

# はじめに
## リフレクションとは
リフレクションとは、クラス・コンストラクタ・フィールド・メソッドなどを扱うためのJava標準APIです。

具体的には、次のようなクラスがあります。

| パッケージ | クラス名 | 表すもの |
|---------|--------|---------|
| `java.lang` | `Class` | クラス |
| `java.lang.reflect` | `Constructor` | コンストラクタ |
| `java.lang.reflect` | `Field` | フィールド |
| `java.lang.reflect` | `Method` | メソッド |

## リフレクションを知る意義
Springなどのフレームワークは、多くの内部処理でリフレクションを利用しています。なのでリフレクションを知ると、次のようなメリットがあります。

- フレームワークのソースコードを読んで理解できるようになる！
- ソースコードを読まずとも、フレームワーク内部の処理がイメージできるようになる！
- 自分でフレームワークを作れるようになる！
    - 近年のJavaでは既存のOSSフレームワークを使うことが多いので、あんまりやらないかもですが

ぜひリフレクションを知って、初級を卒業して中級Javaエンジニアにステップアップしましょう！

一方、普段のお仕事で書く業務ロジックなどでリフレクションを使うことは**おすすめしません**ので、注意してください（理由は後述します）。

# リフレクションの基本
## Classクラス
`Class`クラスは、クラスを表すクラスです。リフレクションの中心的存在です。

`Class`クラスの定義は`Class<T>`のようにジェネリクス付きになっています。`T`には表しているクラスが指定されます。

全てのクラスには、`class`というstaticフィールドのようなもの（後述）が暗黙的に作成されています。このフィールドのようなものには、そのクラスを表す`Class`インスタンスが代入されています。

つまり、こんなフィールドがあるイメージです。

```java:これはイメージコードです
public class Hoge {
    public static final Class<Hoge> class = (Hogeを表すClassインスタンス);
}
```

> `.class`は正確には「クラスリテラル」と呼ばれるリテラルで、**フィールドではありません**([参考URL](https://docs.oracle.com/javase/specs/jls/se11/html/jls-15.html#jls-15.8.2))。ただし、前述の説明ではイメージ重視で「フィールドのようなもの」として説明しています。

これに加えて、全てのクラスには`getClass()`メソッドが定義されています（`java.lang.Object`クラスで定義されています）。このメソッドの戻り値は、`.class`と同じ「そのクラスを表す`Class`インスタンス」です。

```java
Hoge hoge = new Hoge();
Class<Hoge> clazz = hoge.getClass();
```

## クラス名を取得する
`Class` クラスには、クラス名を取得するためのメソッドがいくつか用意されています。

| メソッド名 | 説明 |
|---------|-----|
| `getName()` | 完全修飾クラス名（FQCN）を返す |
| `getSimpleName()` | 単純クラス名を返す |

```java
Class<?> clazz = Object.class;
String fqcn = clazz.getName();
System.out.println(fqcn);
String name = clazz.getSimpleName();
System.out.println(name);
```

```bash:実行結果
java.lang.Object
Object
```

## リフレクションでインスタンスを生成する
例えば、こんな `Sample` クラスがあるとします。

```java:Sample.java
package com.example;

public class Sample {

    private int value;

    public Sample(int value) {
        this.value = value;
    }

    public int multi(int value2) {
        return value * value2;
    }

    @Override
    public String toString() {
        return "Sample{" +
                "value=" + value +
                '}';
    }
}
```

まず、`Class.forName()`メソッドで`Sample`クラスを表す`Class`インスタンスを生成します。引数には`Sample`クラスの完全修飾名を指定します。

```java
// Sampleクラスを表すClassインスタンスを生成する
Class<?> sampleClass = Class.forName("com.example.Sample");
```

次に、`getConstructor()`メソッドで`Sample`クラスのコンストラクタを取得します。引数には、欲しいコンストラクタの引数の型を指定します。

```java
// Sampleクラスのコンストラクタを取得する
Constructor<?> constructor = sampleClass.getConstructor(int.class);
```

> `int`などの基本データ型もクラスリテラル（`.class`）を利用できます。

次に、取得したコンストラクタの`newInstance()`メソッドで`Sample`インスタンスを生成します。引数には、コンストラクタに渡す引数を指定します。

```java
// コンストラクタを利用してSampleインスタンスを生成する
Object sampleInstance = constructor.newInstance(100);
```

本当に`Sample`インスタンスが生成されたのか確認してみましょう。

```java
// インスタンスの状態を表示
System.out.println(sampleInstance);
```

確かに、`value`フィールドの値が100に設定された`Sample`インスタンスであることが分かります。

```bash:実行結果
Sample{value=100}
```

## フィールドを扱う
`Class`クラスには、フィールドを取得するメソッドがいくつかあります。

- `Field getField(String name)`
- `Field getDeclaredField(String name)`

どちらも`name`で指定された名前のフィールドを取得するメソッドです。違いは次のとおりです。

| メソッド名 | publicフィールドの取得 | public以外のフィールドの取得 | スーパークラスで定義されたフィールドの取得
|---------|-----|----|----|
| `getField()` | ○ | × | ○
| `getDeclaredField()` | ○ | ○ | ×

> `getFields()`・`getDeclaredFields()`のような`Field`の配列を返すメソッドもあります。

今回は`value`フィールドがprivateなので、後者を利用します。

```java
// valueフィールドを取得
Field valueField = sampleClass.getDeclaredField("value");
```

`setAccesible()`メソッドに`true`を指定すると、privateフィールドでも値を取得したり、値を代入したりできるようになります。

```java
// valueフィールドの値を取得
Object value = valueField.get(sampleInstance);
// valueフィールドに値200を設定
valueField.set(sampleInstance, 200);
```

## メソッドを扱う
`Class`クラスには、メソッドを取得するメソッドがいくつかあります。

- `Method getMethod(String name, Class<?>... parameterTypes)`
- `Method getDeclaredMethod(String name, Class<?>... parameterTypes)`

どちらも`name`で指定された名前のメソッドを取得するメソッドです。違いは次のとおりです。

| メソッド名 | publicメソッドの取得 | public以外のメソッドの取得 | スーパークラスで定義されたメソッドの取得
|---------|-----|----|----|
| `getMethod()` | ○ | × | ○
| `getDeclaredMethod()` | ○ | ○ | ×

> `getMethods()`・`getDeclaredMethods()`のような`Method`の配列を返すメソッドもあります。

今回は前者を利用します。

```java
// multiメソッドの取得
Method multiMethod = sampleClass.getMethod("multi", int.class);
```

取得したメソッドを実行するときは`invoke()`メソッドに、メソッドを実行したいインスタンスと、引数に渡す値を指定します。

```java
// multiメソッドの実行
Object ret = multiMethod.invoke(sampleInstance, 2);  // 100 * 2 = 200が戻り値となる
```

> `Field`クラス同様、`Method`クラスにも`setAccessible()`メソッドがあります。

# アノテーションの利用
## アノテーションの自作
アノテーションとは、Java標準の`@Override`やSpringの`@RequestMapping`など、`@...`で始まるアレです。

アノテーションは自分で作ることも可能です。

```java:MyAnnotation.java
package com.example;

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;

// 実行時にもアノテーション情報を残す
@Retention(RetentionPolicy.RUNTIME)
// このアノテーションを付けられる箇所をクラス・インタフェースに指定する
@Target({ElementType.TYPE})
// このアノテーションを付けたことがJavadocにも掲載される
@Documented
public @interface MyAnnotation {
    String name() default "default";
    int value();
}
```

アノテーションを作成する場合は、`class`ではなく`@interface`として宣言します。

### アノテーションの要素
`MyAnnotation`に定義されたメソッドのようなものは**要素**です。

> よく「属性」とも呼ばれますが、英語では"element"なので「要素」が正しいと思います。

今回は`name`と`value`という要素を定義しています。

`name`要素には、`default`キーワードを利用してデフォルト値を設定しています。

### `@Retention`
`@Retention`は、`@MyAnnotation`の情報がクラスファイルや実行時に保持されるかどうかを指定します。

| `RetentionPolicy` | 説明 |
|-------------------|-----|
| `RUNTIME` | アノテーションはクラスファイルに記録され、かつ実行時もJVMによって保持されます。
| `CLASS` | アノテーションはクラスファイルに記録される一方、実行時はJVMによって保持されません。
| `SOURCE` | アノテーションはクラスファイルに記録されません。

> `@Retention`を付けなかった場合、`CLASS`と同じ扱いになります。

### `@Target`
`@Target`は、`@MyAnnotation`がメソッド・フィールドなどどこに付加できるか`ElementType`列挙型でを指定します。

| 主な`ElementType` | 説明 |
|-------------------|-----|
| `CONSTRUCTOR` | コンストラクタ
| `FIELD` | フィールド
| `METHOD` | メソッド
| `TYPE` | クラス、インタフェースなど型の宣言

全ての`ElementType`を確認したい場合は[Javadoc](https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/lang/annotation/ElementType.html)をご覧ください。

> `@Target`を付けなかった場合、型パラメータ以外の全箇所に付加可能になります。

### `@Documented`
`@Documented`を付加すると、`@MyAnnotation`を付加したクラスのJavadocに「このクラスは`@MyAnnotation`が付加されている」旨が掲載されます。

## リフレクションでアノテーションを取得する
今回は`Sample`クラスに`@MyAnnotation`を付加します。

```java:Sample.java
@MyAnnotation(999)
public class Sample {

}
```

> 名前が`value`の要素**のみ**を指定する場合は、`@MyAnnotation(999)`と書くことができます（`@MyAnnotation(value = 999)`と書いてもOK）

クラスに付加されたアノテーションを取得する場合は、`Class`クラスの`getAnnotation()`メソッドを利用します。引数には取得したいアノテーションの型を指定します。

> フィールドやメソッドに付加されたアノテーションを取得するには、`Field`クラス・`Method`クラスの`getAnnotation()`メソッドを利用します。

```java
Class<?> sampleClass = Class.forName("com.example.Sample");
// クラスに付いたアノテーションを取得
MyAnnotation myAnnotation = sampleClass.getAnnotation(MyAnnotation.class);
```

アノテーションの要素値は次のように取得します。

```java
// アノテーションの要素値を取得
String name = myAnnotation.name();  // "default"が取得できる
int value = myAnnotation.value();  // 999が取得できる
```

# プロキシ
## プロキシとは
プロキシとは、インタフェース実装クラスおよびそのインスタンスを実行時に作成する技術、およびその作成されたインスタンスを指す言葉です。

具体的には、`java.lang.reflect`パッケージに`Proxy`というクラスがあります。

## プロキシの作成
今回は、こんなインタフェースのプロキシを作成してみます。

```java:
package com.example;

public interface Command {
    int execute1(String command);

    int execute2(String command);
}
```

このインタフェースを実装したクラス＆そのインスタンスは、`Proxy.newProxyInstance()`メソッドで作成します。

```java
Command c = (Command) Proxy.newProxyInstance(
          Command.class.getClassLoader(),
          new Class<?>[]{ Command.class },
          new CommandInvocationHandler());
```

第１引数はクラスローダーです。クラスローダーとは、クラスファイルを読み込む役割を担ったものです。`インタフェース名.class.getClassLoader()`で良いでしょう。

第２引数には、プロキシが実装すべきインタフェースを配列で指定します。

第３引数は、プロキシ内の処理を記述する`InvocationHandler`実装クラスです（後述）。

## プロキシ内の処理を記述する
プロキシの各メソッドが実行された際の処理は、`InvocationHandler`インタフェースを実装して記述します。

```java
package com.example;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Objects;

public class CommandInvocationHandler implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if (Objects.equals("execute1", method.getName())) {
            // execute1()の処理
            if ("UP".equals(args[0])) {
                return 1;
            } else if ("DOWN".equals(args[0])) {
                return 0;
            } else {
                throw new IllegalArgumentException("Arg must be 'UP' or 'DOWN'");
            }
        }
        if (Objects.equals("execute2", method.getName())) {
            // execute2()の処理
            if ("LEFT".equals(args[0])) {
                return 2;
            } else if ("RIGHT".equals(args[0])) {
                return 3;
            } else {
                throw new IllegalArgumentException("Arg must be 'LEFT' or 'RIGHT'");
            }
        }
        throw new IllegalArgumentException("Invalid method");
    }
}
```

## プロキシの呼び出し
作成されたプロキシのクラス名はどうなっているのか、表示して確認してみましょう。

```java
// クラス名を表示
System.out.println(c.getClass().getName());
```

こんな風に`com.sun.proxy.$ProxyN`となっていたら、プロキシが作られている証拠です。

```bash:実行結果
com.sun.proxy.$Proxy0
```

メソッドを呼び出すと、`InvocationHandler`実装クラスに記述した処理が実行されます。

```java
// execute1()の呼び出し
int ret1 = c.execute1("UP");
System.out.println(ret1);

// execute2()の呼び出し
int ret2 = c.execute2("RIGHT");
System.out.println(ret2);

// execute1()の呼び出し（例外発生）
int ret3 = c.execute1("LEFT");
System.out.println(ret3);
```

```bash:実行結果
1
3
Exception in thread "main" java.lang.IllegalArgumentException: Arg must be 'UP' or 'DOWN'
	at com.example.CommandInvocationHandler.invoke(CommandInvocationHandler.java:16)
	at com.sun.proxy.$Proxy0.execute1(Unknown Source)
	at com.example.ProxySample.main(ProxySample.java:19)
```

# リフレクションの使いどころ
前にも書きましたが、リフレクションは基本的にフレームワークやライブラリを作るための技術です。

業務ロジックで使ってしまうと、クラス名を文字列で書くことになるため、後から保守する際に、そのクラスの利用箇所を見つけるのが非常に難しくなります。さらに`setAccessible()`を利用してprivateフィールドに値を代入したりすると、せっかくカプセル化したものが無駄になってしまいます。

繰り返しになりますが、業務ロジックにはリフレクションを使わないでください。