interfaceにNewというstaticメソッドを定義してみた
java8からはinterfaceにstaticメソッドを定義できて便利です。
こういうの面白いかなと思って、New という static メソッドを持つ interface 定義してみました。そしたら、interface だけでも結構いけるんじゃね?って思ったので、記事を書いてみました。
ちょっとしたネタです。
ここでは Map のインスタンスに interface をラップして、(その interface で定義した)getHoge() メソッドの呼び出しで Map#get("hoge") を呼ぶような委譲処理をやってみます。
(interface の実装では Proxy.newProxyInstance を使っています。)
1.ベースとなる interface(と実装)の定義
これ(1.)は読み飛ばして、次(2.)だけ読むといいです。
interface Bean { static <T extends Bean> T __new(Class<? extends T> clazz) { return __new(null, clazz); } static <T extends Bean> T __new(Map<String, Object> store, Class<? extends T> clazz) { return __new(null, store, clazz); } static <T extends Bean> T __new(T obj, Map<String, Object> store) { List<Class<?>> classes = new ArrayList<>(); for (Class<?> clazz : obj.getClass().getInterfaces()) { if (Bean.class.isAssignableFrom(clazz)) { classes.add(clazz); } } return (T) __new(obj, store, (Class<? extends Bean>[]) classes.toArray(new Class<?>[classes.size()])); } // 実装部分(インスタンス生成とメソッドの実装) static <T extends Bean> T __new(T obj, Map<String, Object> store, Class<? extends Bean>... classes) { Map<String, Object> _store = store == null ? new HashMap<>() : store; return (T) Proxy.newProxyInstance(Bean.class.getClassLoader(), classes, (proxy, method, args) -> { String name = method.getName(); if (name.startsWith("get") && ((args == null) || (args.length == 0))) { if (name.equals("get")) { return _store; } String key = name.substring(3); if (!key.isEmpty()) { key = key.substring(0, 1).toLowerCase() + key.substring(1); } Object ret = _store.get(key); Class<?> retType = method.getReturnType(); if (retType.isPrimitive() && (ret == null)) { if (retType == boolean.class) { return false; } if (retType == byte.class) { return (byte) 0; } if (retType == short.class) { return (short) 0; } if (retType == char.class) { return (char) 0; } if (retType == int.class) { return 0; } if (retType == long.class) { return 0L; } if (retType == float.class) { return 0f; } if (retType == double.class) { return 0d; } } return ret; } else if (name.startsWith("set") && ((args != null) && (args.length == 1))) { String key = name.substring(3); if (!key.isEmpty()) { key = key.substring(0, 1).toLowerCase() + key.substring(1); } _store.put(key, args[0]); return null; } return method.invoke(obj == null ? _store : obj, args); }); } // 以下、外部から呼び出すメソッド static <T extends Bean> T alias(T obj) { return (T) __new(obj, obj.get()); } static <S extends Bean, T extends Bean> S alias(T obj, Class<S> clazz) { return (S) __new(obj, obj.get(), clazz); } static <T extends Bean> T copy(T obj) { return (T) __new(obj, new HashMap<>(obj.get())); } static <S extends Bean, T extends Bean> S copy(T obj, Class<S> clazz) { return (S) __new(obj, new HashMap<>(obj.get()), clazz); } Map<String, Object> get(); }
※戻り値の型のチェックは厳密ではありません。
(例えば、String型の値をsetしてInteger型でgetしようとするとClassCastExceptionが投げられます。)
実装方法については、メソッドのマッチ方法とその場合の処理を指定するといった感じにすると汎用的になるでしょう。
2.作成した interface を使ってみる
こちらのコードが(2.)です
interface Name extends Bean { static Name New() { return Bean.__new(Name.class); } static Name accessor(Map<String, Object> map) { return Bean.__new(map, Name.class); } String getName(); void setName(String name); } interface Name2 extends Bean { static Name2 New() { return Bean.__new(Name2.class); } static Name2 accessor(Map<String, Object> map) { return Bean.__new(map, Name2.class); } String getName(); void setName(String name); } // 以下が実行例です。 Map<String, Object> map = new HashMap<>(); map.put("name", "brigen"); // MapをNameインターフェースでラップするインスタンスを生成 Name n_1 = Name.accessor(map); System.out.println("n_1.getName() => " + n_1.getName()); // => brigen // 新しく内部にMapを持つNameインターフェースのインスタンスを生成 Name n_2 = Name.New(); System.out.println("n_2.getName() => " + n_2.getName()); // => null n_2.setName("hoge"); System.out.println("n_2.getName() => " + n_2.getName()); // => hoge System.out.println("n_1.getName() => " + n_1.getName()); // => brigen // 同じMapをラップするNameインターフェースを生成 Name n_3 = Bean.alias(n_1); System.out.println("n_3.getName() => " + n_3.getName()); // => brigen // 複製したMapをNameインターフェースでラップするインスタンスを生成 Name n_4 = Bean.copy(n_1); System.out.println("n_4.getName() => " + n_4.getName()); // => brigen n_4.setName("foo"); System.out.println("n_4.getName() => " + n_4.getName()); // => foo System.out.println("n_1.getName() => " + n_1.getName()); // => brigen // 同じMapをName2インターフェースでラップするインスタンスを生成 Name2 n2_1 = Bean.alias(n_1, Name2.class); System.out.println("n2_1.getName() => " + n2_1.getName()); // => brigen // 複製したMapをName2インターフェースでラップするインスタンスを生成 Name2 n2_2 = Bean.copy(n_1, Name2.class); System.out.println("n2_2.getName() => " + n2_2.getName()); // => brigen n2_2.setName("bar"); System.out.println("n2_2.getName() => " + n2_2.getName()); // => bar System.out.println("n_1.getName() => " + n_1.getName()); // => brigen
java8でメソッド参照の機能が追加されたので、Name.New() は場合によっては Name::New と使えそうです。