【Java発展学習4日目】JVM参照とリフレクションAPI

# JVMの終了
`System`クラスの`exit()`メソッドを用いて`JVM`を終了し、`OS`に`終了コード(exit code)`を渡すことができる。

## 定義

```java
void System.exit(int status)
// パラメータ
// status: 終了コード(0は「正常終了」、0以外は「異常終了」を表す)
```

# 外部プログラムの実行
`ProcessBuilder`クラスを通じて`シェルスクリプト(bashスクリプト)`を実行することができる。
また、`Process`クラスを通じて結果を受け取り、`BufferedReader`を介して結果を読み込むこともできる。

## サンプルコード

```java:ShellScript.java
import java.io.BufferedReader;
import java.io.InputStreamReader;

class ShellScript {
    public static void main(String[] args){
        System.out.println("Make and Run .sh");

        // ProcessBuilderオブジェクトを用いてシェルスクリプトを作成
        ProcessBuilder pb = new ProcessBuilder("sh", "-c", "echo 'Hello!'");

        try {
            // シェルスクリプトの実行
            Process pc = pb.start();

            // 実行結果を格納するStringBuilderオブジェクト
            StringBuilder output = new StringBuilder();

            // 実行結果を読み込むBufferedReaderオブジェクト
            BufferedReader reader = new BufferedReader(new InputStreamReader(pc.getInputStream()));

            // 実行結果の読み込み
            String line;
            while ((line = reader.readLine()) != null) {
                output.append(line);
            }

            // 実行結果をコンソールに出力
            System.out.println(output.toString());
        }
        catch (Exception e) {
            // 例外発生時は標準エラーストリームに出力
            e.printStackTrace();

            // プログラムを終了
            System.exit(1);
        }
    }
}
```

```java:実行結果
Make and Run .sh
Hello!
```

***

# システムプロパティ(system property)
`システムプロパティ`は、`JVM`によって管理される`JVM`や`OS`に関するデータを、
`Set<String>`型の`キー`と`Properties`型の`値`で保持する`グローバル`な`プロパティ`であり、
`System`クラスのメソッドを通じて`システムプロパティ`の`取得`・`追加`・`変更`ができる。

`システムプロパティ`がもつ主なデータは、以下の通り。

|キー (=`Iterator<String>型`)|内容|
|:--|:--|
|`java.version`|**実行中**の`JREバージョン`|
|`java.home`|**実行中**の`Java`の`インストール先ディレクトリ`|
|`os.name`|**動作中**の`OS`|
|`line.separator`|**動作中**の`OS`の`改行コード(newline code)`|
|`user.name`|`実行ユーザ`|

## 定義

```java
// 現在の「システムプロパティ一覧」の取得
Properties System.getProperties();

// システムプロパティのキー群の取得
Set<String> Properties.stringPropertyNames()

// Set型 -> Iterator型 への変換
// -> Set型は要素の抽出や走査が不可能であるため、抽出・走査が可能なIterator型に変換
Iterator<String> Set<String>.iterator()

// 「要素を走査」してキーを取得
String Iterator<String>.next()

// キーを指定してシステムプロパティを取得
String System.getProperty(String key)
// パラメータ
// key: システムプロパティを特定するキー
```

## サンプルコード

```java:SystemProperties.java
import java.util.Iterator;
import java.util.Properties;
import java.util.Set;

class SystemProperties {
    public static void main(String[] args) {
        // システムプロパティ一覧の取得
        Properties p = System.getProperties();

        // システムプロパティのキー群の取得
        Set<String> keySet = p.stringPropertyNames();

        // Set<String>型 -> Iterator<String>型 への変換
        // -> Iterator<E>型は要素の走査が可能
        Iterator<String> keyItr = keySet.iterator();

        // システムプロパティ一覧の表示
        System.out.println("-- System Properties --");
        while (keyItr.hasNext()) {
            // キーの抽出
            String key = keyItr.next();

            System.out.println(key + ": " + System.getProperty(key));
        }
    }
}
```

```java:実行結果(抜粋)
-- System Properties --
user.name: b150005
path.separator: :
os.version: 11.4
java.runtime.name: OpenJDK Runtime Environment
os.name: Mac OS X
java.vm.name: OpenJDK 64-Bit Server VM
user.country: JP
sun.java.command: SystemProperties
java.version: 16.0.1
user.home: /Users/b150005
user.dir: /Users/b150005/Library/Mobile Documents/com~apple~CloudDocs/Java研修/advance/Advance
user.language: ja
os.arch: x86_64
file.separator: /
line.separator: 

java.vendor: AdoptOpenJDK
```

***

# ロケール(locale)
[参考: Localeクラス](https://docs.oracle.com/javase/jp/16/docs/api/java.base/java/util/Locale.html)
`Locale`クラスを通じて`参照`・`変更`が可能な、`OS`がもつ`ユーザ`の`国コード`や`言語コード`に関する情報。

## 定義

```java
// デフォルトロケールの取得
Locale Locale.getDefault()

// デフォルトロケールの設定
void Locale.setDefault(Locale newLocale)
// パラメータ
// newLocale: 「デフォルトロケール」を表すLocaleオブジェクト

// 国コードの取得
String Locale.getCountry()

// 国名の取得
String Locale.getDisplayLanguage()

// 言語コードの取得
String Locale.getLanguage()

// 言語名の取得
String Locale.getDisplayLanguage()
```

# タイムゾーン(time zone)
`TimeZone`クラスを通じて`参照`・`変更`が可能な、`OS`がもつ`ユーザ`の`タイムゾーン`に関する情報。

## 定義

```java
// デフォルトタイムゾーンの取得
TimeZone TimeZone.getDefault()

// デフォルトタイムゾーンの設定
void TimeZone.setDefault(TimeZone zone)
// パラメータ
// zone: 「デフォルトタイムゾーン」を表すTimeZoneオブジェクト

// タイムゾーンの表示名を取得
String TimeZone.getDisplayName()

// タイムゾーンがサマータイムを採用している場合はtrueを返却
boolean TimeZone.useDaylightTime()

// サマータイムを考慮せず世界標準時との時差[ms]を取得
abstract int TimeZone.getRawOffset()

// サマータイムを考慮して世界標準時との時差[ms]を取得
int TimeZone.getOffset(long date)
// パラメータ
// date: 「1970年1月1日00:00:00 GMT」との差[ms]
```

## サンプルコード

```java:LocaleAndTimeZone.java
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;


public class LocaleAndTimeZone {
    public static void main(String[] args) {
        // デフォルトロケールの取得
        Locale loc = Locale.getDefault();

        // ロケール情報の表示
        System.out.println("Locale: " + loc.getDisplayName());
        System.out.println("Country Code: " + loc.getCountry());
        System.out.println("Country Name: " + loc.getDisplayCountry());
        System.out.println("Language Code: " + loc.getLanguage());
        System.out.println("Language Name: " + loc.getDisplayLanguage());

        // デフォルトタイムゾーンの取得
        TimeZone tz = TimeZone.getDefault();

        // カレンダーの取得
        Calendar cal = Calendar.getInstance();
        // カレンダー情報の時間値[ms]
        long calDate = cal.getTimeInMillis();

        // タイムゾーン情報の表示
        System.out.println("TimeZone: " + tz.getDisplayName());
        
        // サマータイムの採用情報をもとに分岐処理
        if (tz.useDaylightTime()) {
            System.out.println("DST is adopted.");
            
            // サマータイムを考慮して世界標準時(UTC)との時差を表示
            System.out.print("time difference from UTC: ");
            System.out.println(tz.getOffset(calDate) / 3600000 + "[hrs.]");
        }
        else {
            System.out.println("DST is not adopted.");

            // サマータイムを考慮せず世界標準時(UTC)との時差を表示
            System.out.print("time difference from UTC: ");
            System.out.println(tz.getRawOffset() / 3600000 + "[hrs.]");
        }
    }
}
```

```java:実行結果
Locale: 日本語 (日本)
Country Code: JP
Country Name: 日本
Language Code: ja
Language Name: 日本語
TimeZone: 日本標準時
DST is not adopted.
time difference from UTC: 9[hrs.]
```

***

# メモリ状態
`Runtime`クラスを利用することで、`JVM`の現在の`メモリ状態`を取得することができる。

## 定義

```java
// 現在のメモリの「空き容量」[byte]を取得
long Runtime.freeMemory()

// 「現在利用可能」なメモリの「総容量」[byte]を取得
long Runtime.totalMemory()

// 利用可能なメモリの「総容量の最大値」[byte]を取得
long Runtime.maxMemory()
```

## サンプルコード

```java:JVMMemory.java
import java.math.BigDecimal;
import java.math.RoundingMode;

public class JVMMemory {
    public static void main(String[] args) {
        // Runtimeインスタンスの取得
        Runtime rt = Runtime.getRuntime();
        
        System.out.println("-- Information of JVM Memory --");

        // 空きメモリ容量[byte]
        long freeL = rt.freeMemory();
        double freeD = (double) freeL;
        // 総容量[byte]
        long totalL = rt.totalMemory();
        double totalD = (double) totalL;
        // 最大容量[byte]
        long maxL = rt.maxMemory();
        double maxD = (double) maxL;

        // BigDecimalを用いたメモリデータ表示
        System.out.println("Free: " + new BigDecimal(freeD / 1024 / 1024 / 1024).setScale(2, RoundingMode.HALF_UP) + "[GB]");
        System.out.println("Total: " + new BigDecimal(totalD / 1024 / 1024 / 1024).setScale(2, RoundingMode.HALF_UP) + "[GB]");
        System.out.println("Use: " + new BigDecimal((totalD - freeD) / 1024 / 1024 / 1024).setScale(2, RoundingMode.HALF_UP) + "[GB]");
        System.out.println("Maximum: " + new BigDecimal(maxD / 1024 / 1024 / 1024).setScale(2, RoundingMode.HALF_UP) + "[GB]");
    }
}
```

```java:実行結果
-- Information of JVM Memory --
Free: 1.01[GB]
Total: 1.02[GB]
Use: 0.01[GB]
Maximum: 16.00[GB]
```

***

# リフレクション(reflection API)
`JVM`がもつ`実行時型情報(RTTI; run-time type information)`を取得するメソッドを提供する`API`。
`リフレクションAPI`の`Class<T>`クラスを通じて、`実行時型情報`を取得することができる。

なお、`実行時型情報`のうち、`クラス(=配列・インタフェース・列挙型を含む)情報`は`Class<T>`インスタンス、
`メンバ情報`(`フィールド`・`メソッド`・`コンストラクタ`・`修飾子`)はそれぞれ`Field`・`Method`・`Constructor`・`Modifier`インスタンスが保持する。
ただし、`修飾子情報`の実体は`Modifier`クラスの`int型クラス定数`で表される。

## 定義(クラス情報の取得)

```java:クラス情報の取得
// クラス名を「FQCN(fully-qualified class name; 完全限定名)」で指定してクラス情報を取得
Class<?> Class<T>.forName(String className)
// パラメータ
// className: FQCN

// 「クラス名」を指定してクラス情報を取得
Class<?> <クラス名>.class

// 「オブジェクト名」を指定してクラス情報を取得
Class<?> Object.getClass()
```

## 定義(クラス情報の取得)

```java:クラス情報の取得
// 「FQCN」の取得
String Class<T>.getName()

// 「クラス名」の取得
String Class<T>.getSimpleName()

// 「所属パッケージ名」の取得
Package Class<T>.getPackage()

// 「所属モジュール名」の取得
Module Class<T>.getModule()

// 「親クラス情報」を取得
Class<? super T> Class<T>.getSuperclass()

// 「配列」であればtrueを返却
boolean Class<T>.isArray()

// 「インタフェース」であればtrueを返却
boolean Class<T>.isInterface()

// 「列挙型」であればtrueを返却
boolean Class<T>.isEnum()
```

## 定義(各メンバ情報の取得)
`getDeclared*()`メソッドは`クラス`が保持する「**全ての**`メンバ情報`」を返却するのに対し、
`get*()`メソッドは「`public`な`メンバ情報`のみ」を返却する。

```java:各メンバ情報の取得
// 「フィールド情報」の一覧を取得
Field[] Class<T>.getDeclaredFields()

// 「フィールド情報」を指定して取得
Field Class<T>.getDeclaredField(String name)
// パラメータ
// name: フィールド名

// 「メソッド情報」の一覧を取得
Method[] Class<T>.getDeclaredMethods()

// 「メソッド情報」を指定して取得
// -> 多重定義されている場合があるため、「メソッド名」と「引数の型」を指定
Method Class<T>.getDeclaredMethod(String name, Class<?>... parameterTypes)
// パラメータ
// name: メソッド名
// parameterTypes: メソッドの引数の型

// 「コンストラクタ情報」の一覧を取得
Constructor<?>[] Class<T>.getDeclaredConstructors()

// 「コンストラクタ情報」を指定して取得
Constructor<T> Class<T>.getDeclaredConstructor(Class<?>... parameterTypes)
// パラメータ
// parameterTypes: コンストラクタの引数の型

// 「修飾子情報」を取得
int getModifiers()
```

## サンプルコード

```java:Reflection.java
import java.lang.reflect.*;

public class Reflection {
    public static void main(String[] args) throws Exception {
        // 「クラス」からクラス情報を取得
        Class<?> cStatic = ReflectionTest.class;

        // 「クラス情報」から引数を指定してコンストラクタ情報を取得
        // -> NoSuchMethodException(検査例外)が発生する可能性があるため、
        //    以下の方法でエラーハンドリングを行う必要がある
        //    1. 呼び出すメソッド(=main())に"throws文"を付与
        //    2. "try-catch文"で例外発生時の処理を記述
        Constructor<?> con = cStatic.getConstructor(String.class, int.class);

        // 「コンストラクタ情報」からオブジェクトの生成
        // -> Constructor<?>型 -> 外部クラス型 へのキャストが必要
        ReflectionTest ref = (ReflectionTest) con.newInstance("A", 5);
        System.out.println("-- Before --");
        System.out.println(ref);
        System.out.println();

        // 「オブジェクト」からクラス情報を取得
        Class<?> cDynamic = ref.getClass();

        // 「クラス情報」からフィールド情報を取得
        // -> privateなメンバを取得する場合は"getDeclared*()"メソッドを利用
        Field fStr = cStatic.getDeclaredField("str");
        Field fInt = cDynamic.getDeclaredField("i");

        // privateなフィールドのアクセス権限を変更
        fStr.setAccessible(true);
        fInt.setAccessible(true);

        // 「フィールド情報」からオブジェクトの動的フィールドの値を変更
        fStr.set(ref, "Changed");
        fInt.set(ref, 13);
        System.out.println("-- After --");
        System.out.println(ref);
        System.out.println();

        // 「クラス情報」や各「メンバ情報」の修飾子を特定
        // -> Modifierクラスの「クラス定数」を利用する場合
        switch (cStatic.getModifiers()) {
            case Modifier.PUBLIC:
                System.out.println("Class " + cStatic.getSimpleName() + " is declared as public."); break;
            case Modifier.PROTECTED:
                System.out.println("Class " + cStatic.getSimpleName() + " is declared as protected."); break;
            case Modifier.PRIVATE:
                System.out.println("Class " + cStatic.getSimpleName() + " is declared as private."); break;
            default:
                System.out.println("Class " + cStatic.getSimpleName() + " is declared as default."); break;
        }
        Field fREF_S = cDynamic.getDeclaredField("REF_STATIC");
        int modfREF_S = fREF_S.getModifiers();
        // Modifierクラスの「クラスメソッド」を利用する場合
        if (Modifier.isPrivate(modfREF_S)) {
            System.out.print("Field " + fREF_S.getName() + " is declared as private");
            if (Modifier.isStatic(modfREF_S)) {
                System.out.print(", static");
                if (Modifier.isFinal(modfREF_S)) {
                    System.out.println(", final.");
                }
            }
        }
    }
}

class ReflectionTest {
    private String str = "";
    private int i = 0;
    // 動的final変数(利用しない)
    private final String REF = "Final String(dynamic)";
    // 静的final変数
    private static final String REF_STATIC = "Final String(static)";

    // デフォルトコンストラクタ
    public ReflectionTest() {}

    // オーバーロードしたコンストラクタ
    public ReflectionTest(String str) {
        this.str = str;
    }
    public ReflectionTest(int i) {
        this.i = i;
    }
    public ReflectionTest(String str, int i) {
        this.str = str;
        this.i = i;
    }

    // オーバーロードした動的メソッド
    public void add(String addStr) {
        this.str += addStr;
    }
    public void add(int j) {
        this.i += j;
    }
    public void add(String addStr, int j) {
        this.str += addStr;
        this.i += j;
    }

    @Override
    public String toString() {
        return "ReflectionTest(" + this.str + ", " + this.i + ") " + "[" + REF + ", " + REF_STATIC + "]";
    }
}
```

```java:実行結果
-- Before --
ReflectionTest(A, 5) [Final String(dynamic), Final String(static)]

-- After --
ReflectionTest(Changed, 13) [Final String(dynamic), Final String(static)]

Class ReflectionTest is declared as default.
Field REF_STATIC is declared as private, static, final.
```

***

# 用語集

|用語|内容|
|:--|:--|
|システムプロパティ(system property)|`JVM`が管理する、`JVM`や`OS`に関するデータを保持するプロパティ。|
|環境変数(environment variable)|`OS`が管理する、`OS`に関するデータを保持する変数。|
|実行時型情報(RTTI; Run-Time Type Information)|`型`がもつ`パッケージ`・`親クラス`・`メンバ`・`修飾子`などの情報。|