Skip to content

Instantly share code, notes, and snippets.

@asufana
Last active May 2, 2017 15:58
Show Gist options
  • Save asufana/f9cc232c3a2c7e7437b8 to your computer and use it in GitHub Desktop.
Save asufana/f9cc232c3a2c7e7437b8 to your computer and use it in GitHub Desktop.
Java8 Functinal Interfase

Java8 Functinal Interfase

StreamAPIに慣れたら、Functinal Interfaceを定義してラムダ式を受ける実装を作ってみよう!

以下のような処理を実装するという場面から、Functional Interfaceを定義例を紹介する

  • 指定URLのHTMLを取得する
  • 取得したHTMLから任意のDOM要素を抽出する

Java8以前の実装

    private final static String url = "http://www.soliton.co.jp";
    
    @Test
    public void test() throws Exception {
        //HTML取得
        final HttpResponse response = WS.url(url).get();
        final Document doc = Jsoup.parse(response.getString());
        
        //トピックをピックアップ
        final Elements elements = doc.select("#Contents dd span"); //セレクタ
        final List<String> topics = new ArrayList<>();
        for (final Element element : elements) {
            topics.add(element.text()); //トピックタイトルを抽出
        }
        
        //表示
        topics.forEach(System.out::println);

        //サイバーセキュリティ関連の研究開発で米社と提携
        //デンマークのExcitor社を子会社化
        //ID/アクセス管理ソフト「Soliton ID Manager」をリリース
    }

まあこれはこれでよい。

ただしもうひとつ別の要素を抽出したくなった場合には、、

Java8以前の実装(複数要素の抽出)

    @Test
    public void testBeforeJava8() throws Exception {
        //HTML取得
        final HttpResponse response = WS.url(url).get();
        final Document doc = Jsoup.parse(response.getString());
        
        //トピックをピックアップ
        System.out.println("トピック----------------------");
        final List<String> topics = parseTopic(doc, "#Contents dd span"); //セレクタ
        topics.forEach(System.out::println);

        //アンカーをピックアップ
        System.out.println("アンカー----------------------");
        final List<String> anchors = parseAnchor(doc, "#Contents a"); //セレクタ
        anchors.forEach(System.out::println);

        //トピック----------------------
        //サイバーセキュリティ関連の研究開発で米社と提携
        //デンマークのExcitor社を子会社化
        //ID/アクセス管理ソフト「Soliton ID Manager」をリリース
        
        //アンカー----------------------
        //news/index.html
        //news/nr/14_13_cybersec.html
        //news/nr/14_12_excitor.html
    }
    
    /** トピックを抽出 */
    private List<String> parseTopic(final Document doc, final String selector) {
        final Elements elements = doc.select(selector);
        final List<String> topics = new ArrayList<>();
        for (final Element element : elements) {
            topics.add(element.text()); //トピックタイトルを抽出(したいので .text() を指定)
        }
        return topics;
    }
    
    /** アンカーを抽出 */
    private List<String> parseAnchor(final Document doc, final String selector) {
        final Elements elements = doc.select(selector);
        final List<String> anchor = new ArrayList<>();
        for (final Element element : elements) {
            anchor.add(element.attr("href")); //アンカーURLを抽出(したいので .attr("href") を指定)
        }
        return anchor;
    }

element.text() あるいは element.attr("href") と、抽出する要素によって抽出方法が異なるので、一つのメソッドとしてまとめることが困難。 抽出方法の異なる抽出要素別にメソッドを用意する必要がある。

Java8以後の実装

Functional Interfaceを利用して、関数(ラムダ式)を引数に受ける形にする

    @Test
    public void testAfterJava8() throws Exception {
        System.out.println("トピック----------------------");
        final List<String> topics = WSUtil.parse(url,                  //第一引数:取得先URL
                     (doc) -> { return doc.select("#Contents dd span") //第二引数:HTML抽出方法
                                   .stream()
                                   .map(element -> element.text()) //トピックタイトルを抽出 
                                   .collect(Collectors.toList());
                     });
        topics.forEach(System.out::println);
        
        System.out.println("アンカー----------------------");
        final List<String> anchors = WSUtil.parse(url,           //第一引数:取得先URL
                     (doc) -> { return doc.select("#Contents a") //第二引数:HTML抽出方法
                                   .stream()
                                   .map(element -> element.attr("href")) //アンカーURLを抽出
                                   .collect(Collectors.toList());
                     });
        anchors.forEach(System.out::println);

        //トピック----------------------
        //サイバーセキュリティ関連の研究開発で米社と提携
        //デンマークのExcitor社を子会社化
        //ID/アクセス管理ソフト「Soliton ID Manager」をリリース
        
        //アンカー----------------------
        //news/index.html
        //news/nr/14_13_cybersec.html
        //news/nr/14_12_excitor.html
    }

WSUtil.parse() がセレクタと抽出方法の両方を引数に取ることで、メソッドをひとつに纏めることができるようになる

    //第二引数としてWSCallBack(Functional Interfase)を取る ⇒ ラムダ式で記述可能
    public static class WSUtil {
        public static <T> List<T> parse(final String url, final WSCallBack<T> callback) {
            final HttpResponse response = WS.url(url).get();
            final Document document = Jsoup.parse(response.getString());
            return callback.call(document);
        }
    }
    
    @FunctionalInterface
    public interface WSCallBack<T> {
        List<T> call(final Document document);
    }

あるいは(メソッド引数に無名関数として記述せずに)変数化することで、振る舞いも含めてパラメータ化できる

        final WSCallBack<String> topicParse = (doc) -> {
            return doc.select("#Contents dd span").stream().map(element -> element.text()).collect(Collectors.toList());
        };

        final WSCallBack<String> anchorParse = (doc) -> {
            return doc.select("#Contents a").stream().map(element -> element.attr("href")).collect(Collectors.toList());
        };

適用場面

振る舞いを差し込む、あるいは振る舞いを外出しできる。これはつまりテンプレートパターンと同様。

継承を使った静的な構成がテンプレートパターン、委譲を使った動的な構成がストラテジーパターン、共に振る舞いの切り替え。

これらを使う場面、あるいは振る舞いを含めた一部の処理の切り替えには、ラムダ式による関数引数が適用できる。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment