LoginSignup
1
1

More than 3 years have passed since last update.

沈思黙考:自分で作る関数に関数型インタフェースを活かす方法(java)

Posted at

初めに

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 パターン」を実現できるというもは、すでに想像されていることでしょう。
ぜひあなたのプログラムにも適用して、プログラムの再利用を図ってください。

1
1
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1