StreamAPIに慣れたら、Functinal Interfaceを定義してラムダ式を受ける実装を作ってみよう!
以下のような処理を実装するという場面から、Functional Interfaceを定義例を紹介する
- 指定URLのHTMLを取得する
- 取得したHTMLから任意のDOM要素を抽出する
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」をリリース
}
まあこれはこれでよい。
ただしもうひとつ別の要素を抽出したくなった場合には、、
@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")
と、抽出する要素によって抽出方法が異なるので、一つのメソッドとしてまとめることが困難。
抽出方法の異なる抽出要素別にメソッドを用意する必要がある。
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());
};
振る舞いを差し込む、あるいは振る舞いを外出しできる。これはつまりテンプレートパターンと同様。
継承を使った静的な構成がテンプレートパターン、委譲を使った動的な構成がストラテジーパターン、共に振る舞いの切り替え。
これらを使う場面、あるいは振る舞いを含めた一部の処理の切り替えには、ラムダ式による関数引数が適用できる。