Java プロパティファイル

Java 11 Gold 取得に向けた学習記録

# プロパティファイル

アプリケーションの設定情報を、外部ファイルとしてアプリケーションと分離させることでコードのコンパイルやアプリケーションのビルドをせずに挙動を変える手法がある。このときの外部ファイルを**プロパティファイル**と言う。

プロパティファイルの実体は、キーバリュー形式で値を保持する単なる**テキストファイル**である。このファイルの拡張子は通常 `.properties` とする。`settings.properties`や`config.properties`というファイル名が多く使用される。

キーとバリューは以下のように記述する。`=`と`:`を利用することができる。キーには`.`を使用することができる。

```text:config.properties
password=hello
database.password=world
database.username:suzuki
```

プロパティファイルを扱うためには[`Properties`](#properties)クラスか[`ResourceBundle`](#resourcebundle)クラスを利用する。

## `Properties`

プロパティファイルを読み込んで利用する際は、`java.util.Properties`クラスを利用する。

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/Properties.html

### `load()`

プロパティファイルを読み込む際は `Properties` クラスの `load()` メソッドを使用する。

`load()`メソッドには、文字データのストリームを扱う `Reader` インターフェース型を受け取るものと、バイナリデータのストリームを扱う `InputStream` インターフェース型を受け取るものがある。

> `public void load​(Reader reader) throws IOException`
> `public void load​(InputStream inStream) throws IOException`

https://qiita.com/AsahinaKei/items/5f62d67576b2d221d077#%E3%82%B9%E3%83%88%E3%83%AA%E3%83%BC%E3%83%A0

`config.properties`をプロジェクトのルートディレクトリ直下に配置する。

```text:config.properties
a=hello
b:world
```

```java:Readerを使う方法
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.Properties;

Properties properties = new Properties();

try (Reader reader = new FileReader("config.properties")) {
    // ファイルの読み込み
    properties.load(reader);

} catch (IOException e) {
}
```


```java:InputStreamを使う方法
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

Properties properties = new Properties();

try (InputStream input = new FileInputStream("config.properties")) {
    // ファイルの読み込み
    properties.load(input);

} catch (IOException e) {
}
```

### `get()` / `getProperty()`

`Properties`オブジェクトから情報を取得する方法には、`get()`メソッドを使用する方法と`getProperty()`メソッドを使用する方法の2通りがある。

| シグネチャ | 定義されているクラス | 引数の型 | 補足 |
|:-:|:-:|:-:|:-:|
| `V get​(Object key)` | `java.util.Hashtable`<br>から継承している | `Object`型 | デフォルト値の設定ができない。 |
| `public String getProperty​(String key)` | `java.lang.Properties` | `String`型 | プロパティが存在しない場合のデフォルト値が設定できる。<br>プロパティの操作に特化している。<br>**推奨** |


```java:get()
// キャストが必要
String a = (String) properties.get("a");
String b = (String) properties.get("b");

System.out.println(a);
System.out.println(b);
// >> hello
// >> world
```

```java:getProperty()
String a = properties.getProperty("a");
String b = properties.getProperty("b");
String c = properties.getProperty("c", "default_value"); // デフォルト値の設定

System.out.println(a);
System.out.println(b);
System.out.println(c);
// >> hello
// >> world
// >> default_value
```

### `list()`

取得するプロパティの一覧は`list()`メソッドによって出力することができる。

> `public void list​(PrintStream out)`
`public void list​(PrintWriter out)`

```java:list()
properties.list(System.out);
// >> -- listing properties --
// >> a=hello
// >> b=world
```

## `ResourceBundle`

プロパティファイルは`java.util.ResourceBundle`クラスを使用して扱うこともできる。

https://docs.oracle.com/javase/jp/11/docs/api/java.base/java/util/ResourceBundle.html

`ResourceBundle`クラスは、**ローカライズ**（i18n）機能の一部であり、異なるロケール（地域や言語）に基づいた文字列やその他の**リソースバンドル**を管理するために利用される。

https://qiita.com/AsahinaKei/items/ae5a6c53f12abafa2e93

ファイル名は通常、**基底名**（`baseName`）に**ロケール識別子**（言語コード、国コード）を付けた形式になる。ロケール識別子を付与しないプロパティファイルは、ロケールが見つからないときのデフォルトのプロパティファイルとして扱われる。

このようにロケールに合わせたプロパティファイルを複数用意しておき、リソースバンドルとして利用することで、設定を簡単にローカライズできるようになる。

`MyResources.properties`はクラスパスのルートディレクトリである`src/main/resources/`に配置する。

```text:MyResources.properties
a=hello
b:world
```

ロケール `ja_JP` 環境下では、 `MyResources.properties` ではなく `MyResources_ja_JP.properties` が読み込まれる。

```text:MyResources_ja_JP.properties
a=ハロー
b:ワールド
```

### `ResourceBundle.getBundle()`

リソースバンドルを取得するためには、`ResourceBundle.getBundle()`メソッドを利用する。`getBundle()`メソッドは `Properties` クラスを利用した場合の `load()` メソッドに相当する。

引数 `baseName` にはファイル名ではなく[基底名](#resourcebundle)を指定する。

> `static ResourceBundle getBundle​(String baseName)`
`static ResourceBundle getBundle​(String baseName, Locale locale)`

```java:ResourceBundle.getBundle()
ResourceBundle bundle = ResourceBundle.getBundle("MyResources");
```


第二引数の`locale`を指定しない場合は、プラットフォームのデフォルトのロケールが使用される。つまり、下の2つのコードは同義ということになる。

```java
ResourceBundle bundle =  ResourceBundle.getBundle("MyResources");
ResourceBundle bundle =  ResourceBundle.getBundle("MyResources", Locale.getDefault());
```

`locale`を指定した場合、特定のロケールに基づくバンドルを取得することができる。

### `getString()`

`getString()`を使用することで、取得したバンドルからプロパティを取得することができる。

> `public final String getString​(String key)`

```java:getString()
String a = bundle.getString("a");
String b = bundle.getString("b");

System.out.println(a);
System.out.println(b);
// >> hello
// >> world
```

プロパティファイルを複数用意しておけば、ロケールに基づくプロパティを取得することができる。

```text:MyResources.properties
a=hello
b:world
```

```text:MyResources_ja_JP.properties
a=ハロー
b:ワールド
```
```java:getString()
ResourceBundle bundle = ResourceBundle.getBundle("MyResources");
ResourceBundle bundleJa = ResourceBundle.getBundle("MyResources", new Locale("ja", "JP"));

String a = bundle.getString("a");
String aJa = bundleJa.getString("a");
String b = bundle.getString("b");
String bJa = bundleJa.getString("b");

System.out.println(a);
System.out.println(aJa);
System.out.println(b);
System.out.println(bJa);
// >> hello
// >> こんにちは
// >> world
// >> ワールド
```

IntelliJ IDEA を使用して動作確認していた際、プロパティファイルのエンコーディングが `ISO-8859-1` になっていて文字化けしたため、プロパティファイルの保存時に`UTF-8`へ変更した。

![properties.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1069520/9f58b262-64e5-fca5-a883-20393e806c09.png)

### `MissingResourceException`

ロケールを指定してリソースバンドルを取得しようと試みた際に、仮に対象のロケールに基づくプロパティファイルが見つからなかった場合、デフォルトのプロパティファイル（`MyResources.properties`）が存在しないと `java.util.MissingResourceException` が発生する。


# 参考
- [徹底攻略Java SE 11 Gold問題集[1Z0-816]対応](https://amzn.asia/d/0eV9L1Wp)