【Java8】 .propertiesファイルからキャストしつつ値を取得する
環境
$ java -version openjdk version "1.8.0_212" OpenJDK Runtime Environment (build 1.8.0_212-8u212-b03-0ubuntu1.18.04.1-b03) OpenJDK 64-Bit Server VM (build 25.212-b03, mixed mode)
結論
以下のPropertiesWrapper
がそれ用のクラス。
import java.io.FileInputStream; import java.io.InputStream; import java.lang.Double; import java.lang.Integer; import java.util.Properties; import java.util.function.Function; public class PropertiesTest { public static void main(String[] args) { PropertiesWrapper props = new PropertiesWrapper("./test.properties"); String foo = props.getValue("foo", "defaultValue", (s) -> s); int bar = props.<Integer>getValue("bar", 0, (s) -> Integer.parseInt(s)); double baz = props.<Double>getValue("baz", 0.1, (s) -> Double.parseDouble(s)); System.out.println(foo); System.out.println(bar); System.out.println(baz); } } class PropertiesWrapper { private Properties props; public PropertiesWrapper(String filepath) { this.props = new Properties(); try (InputStream inputStream = new FileInputStream(filepath)){ props.load(inputStream); } catch (Exception e) { e.printStackTrace(); } } public <T> T getValue(String key, T defaultValue, Function<String, T> castFunc) { String value = props.getProperty(key); T cast; try{ cast = castFunc.apply(value); }catch(Exception e){ cast = defaultValue; } return cast; } }
過程
↓みたいに色んな型の値がある.propertiesファイルがあるとする。
foo=x bar=100 baz=5.0
Javaで.propertiesファイルから読んだ値はString
型で取得されるため、数値を読み込むにはキャストする必要がある。
Properties props = new Properties(); String value = props.getProperty("key"); int x = Integer.parseInt(value); double y = Double.parseDouble(value); float z = Float.parseFloat(value);
取得した値がキャストできないとNumberFormatException
が発生するので変換処理を集約したい。そこで以下のようなjava.util.Properties
クラスのラッパークラスを作る。getValue()
で.propertiesファイルから値を取得する。
class PropertiesWrapper { private Properties props; public PropertiesWrapper(String filepath) { this.props = new Properties(); try (InputStream inputStream = new FileInputStream(filepath)){ props.load(inputStream); } catch (Exception e) { e.printStackTrace(); } } public Object getValue(String key) { // このメソッドにキャスト処理を集約したい return props.getProperty(key); } }
getValue()
の中でキャストしたりエラーハンドリングしたりしたい。先に書いたように、数値型へのキャストはprimitive型のラッパークラスを用いる。なのでジェネリクスを使ってみる。長いのでメソッド部分だけ。
public <T> T getValue(String key) { String value = props.getProperty(key); return (T) value; }
.propertiesファイルから取得した値を型パラメータTでキャストして返している(コンパイル時に未検査のキャストの警告が出る)。呼ぶときは例えばpropertiesWrapper.<Integer>getValue("bar")
などとして呼ぶ。実行すると
Exception in thread "main" java.lang.ClassCastException: java.lang.String cannot be cast to java.lang.Integer at PropertiesTest.main(PropertiesTest.java:12)
みたいなエラーが出る。java.lang.String
クラスとjava.lang.Number
クラスには何の関連もないので(T)
みたいなキャストは無効。なので、キャストする方法も一緒に渡してみる。
public <T> T getValue(String key, Function<String, T> castFunc) { String value = props.getProperty(key); T cast = castFunc.apply(value); return cast; }
String
型を受け取りT
型の値を返すFunction
型の関数型インターフェースcastFunc
を受け取るようにした。実際に動かしてみる。
public class PropertiesTest { public static void main(String[] args) { PropertiesWrapper props = new PropertiesWrapper("./test.properties"); int bar = props.<Integer>getValue("bar", (s) -> Integer.parseInt(s)); System.out.println(bar); // -> 100 } }
test.properties
にあるbar
の値をInteger.parseInt
でキャストする。ラムダ式の部分はメソッド参照(Integer::parseInt
)でもいいけど、メソッド参照はラムダ式よりも遅いってどっかで見た。やったら実際遅かった。ちなみにちゃんとしたT
型を返すようになったので、さっき出ていた未検査のキャストの警告は消えている。
キャストにミスったときはデフォルトの値を返すことにして、エラーハンドリングも加味した結果、以下のようになった。
public <T> T getValue(String key, T defaultValue, Function<String, T> castFunc) { String value = props.getProperty(key); T cast; try{ cast = castFunc.apply(value); }catch(Exception e){ cast = defaultValue; } return cast; }
感想
結局.propertiesファイルから取得するときにキャスト用の関数をその都度渡しているのがなんか…。でもキャストと言いつつ、castFunc
の実装によっては適当なエラーチェックなどを挟めるのはいいかもしれない。まあこのタイミングでやることか?という疑問はある。