初めに
streamなどでラムダ式を使うことはできても、自分で作る関数に関数型インタフェースを活かすことができない方がいるかもしれない。
そんな方にむけて、簡単な例を使って、自作関数への関数型インタフェースの適用方法を紹介したいと思う。
例題
あるオブジェクトのプロパティを別のプロパティにコピーすることは、よくあることだろう。
その際、プロパティの値がnullでない場合だけ、コピーするという条件がつくこともよくあることだ。
そして、この例題のように、様々な型プロパティのコピーがずらっと並ぶこともよくある。
このようにコピペで対応してもよい場合もあるだろうが、少し工夫をしたい。
次のコードに対し、共通関数を作って呼び出しを簡素化しよう。
なお、getName()はStringを返し、setName()は、Stringをセットする。
getFlag()はBooleanを返し、setFlag()は、Booleanをセットする。
if(objA.getName()!=null) {
objB.setName(objA.getName());
}
if(objA.getFlag()!=null) {
objB.setFlag(objA.getFlag());
}
疑似コード
以下のように書けるといいですね。
javaではどのよにして実現するか見ていきましょう。
setIfNotNull(value,func) {
if(value!= null) {
func(value);
}
}
setIfNotNull(objA.getName(),objB.setName);
setIfNotNull(objA.getFlag(),objB.setFlag);
インターフェースを使った回答(うまくいかない例)
上記疑似コードでfuncはメソッドを示していますが、javaではどのように表現すればよいでしょうか?
メソッドを示すものといえば、インターフェイスです。ここでもインターフェイスを使えば実現できるかもしれません。
public interface SetNameInterface {
void setName(String value);
}
public Class ObjB implements SetNameInterface{
void setName(String name);
}
public void setIfNotNull(String value,SetNameInterface obj) {
if(value!= null) {
obj.setName(value);
}
}
setIfNotNull(objA.getName(),objB);
setName()には対応できましたが、setFlag()には対応できないのは明らかです。
この方法は直観的ですが、うまくいきません。
インターフェースを使った回答(うまくいくが冗長)
setName()とsetFlag()をsetIfNotNull()からは、共通の関数名funcで呼び出したいのだから、
funcをインターフェイスとしてもつオブジェクトをsetName()とsetFlag()に対して持たせるのはどうでしょうか?
public Class ObjB {
public void setName(String name) {
System.out.println("OnjB.setName:"+name);
}
public void setFlag(Boolean flag) {
System.out.println("OnjB.setFlag:"+flag);
}
}
public interface FuncInterface<T> {
void func(T value);
}
public class ObjBSetName implements FuncInterface<String> {
ObjB obj;
public ObjBSetName(ObjB obj) {
this.obj = obj;
}
@Override
public void func(String value) {
obj.setName(value);
}
}
public class ObjBSetFlag implements FuncInterface<Boolean> {
ObjB obj;
public ObjBSetFlag(ObjB obj) {
this.obj = obj;
}
@Override
public void func(Boolean value) {
obj.setFlag(value);
}
}
public <T>void setIfNotNull(T value,FuncInterface<T> obj) {
if(value!= null) {
obj.func(value);
}
}
setIfNotNull(objA.getName(),new ObjBSetName(objB));
setIfNotNull(objA.getFlag(),new ObjBSetFlag(objB));
ObjBSetName ,ObjBSetFlag は、ともにFuncInterfaceインターフェースで定義されてるfunc関数を持ち、
func関数が呼び出されるとそれぞれの本来の処理objB.setName()やobjB.setFlag()を呼び出しています。
なお、FuncInterfaceインターフェースは、メソッドに渡すパラメータの型を総称型(ここではT)で宣言しており、
それに合わせて、setIfNotNull()もvalueおよびobjを総称型を使うように変更します。
匿名クラスを使った回答
これでやりたいことは実現できたのですが、ObjBSetName、ObjBSetFlagを用意するところが冗長です。
匿名クラスを使ってみましょう。
匿名クラスの書き方は、
new 親クラスまたはインターフェイス名() {
匿名クラスの内容 (フィールドやメソッドの定義)
};
であるので、
var objBSetName = new FuncInterface<String>() {
@Override
public void func(String value) {
objB.setName(value);
}
};
var objBSetFlag = new FuncInterface<Boolean>() {
@Override
public void func(Boolean value) {
objB.setFlag(value);
}
};
と書けて、
呼び出しは、
setIfNotNull(objA.getName(),objBSetName);
setIfNotNull(objA.getFlag(),objBSetFlag);
となります。匿名クラスの中では、オブジェクトobjBを直接参照できます。
ラムダ式を使った回答
関数が1つだけ定義されているインターフェースの場合、匿名クラスをラムダ式に置き換えられるので、
FuncInterface<String> objBSetName = (v) -> objB.setName(v);
FuncInterface<Boolean> objBSetFlag = (v) -> objB.setFlag(v);
と書き換えできる。ラムダ式には、funcという表記はないが、FuncInterfaceで定義されているfuncを実装しています。
ラムダ式には、funcが消えているのがわかりにくい点ですが、匿名クラスとよく見比べれば理解できるでしょう。
さらに変数を省略して、
setIfNotNull(objA.getName(),(v) -> objB.setName(v));
setIfNotNull(objA.getFlag(),(v) -> objB.setFlag(v));
とまとめることができます。
関数型インタフェースを使った回答
さて、FuncInterfaceというインターフェースの宣言ですが、実は、関数型インタフェースとして
すでにConsumerが存在しますので、こちらと置き換えてしまいましょう。
(インターフェースを自作する前に提供されている関数型インタフェースが使えないか調べるとよいでしょう)
public <T>void setIfNotNull(T value,Consumer<T> obj) {
if(value!= null) {
obj.accept(value);
}
}
さらに、引数が1つですので、呼び出しは、メソッド参照を使って、
setIfNotNull(objA.getName(),objB::setName);
setIfNotNull(objA.getFlag(),objB::setFlag);
となります。
かなりすっきりしましたね。
まとめ
今回は、小さな例で説明しましたが、「処理」を関数に渡すことができる、ということがおわかりいただけた
と思います。そのための宣言が「関数型インタフェース」です。
これを使えば、「Strategy パターン」を実現できるというもは、すでに想像されていることでしょう。
ぜひあなたのプログラムにも適用して、プログラムの再利用を図ってください。