はじめに
今回は Java 8 の Lambda と Nashorn の連携についてご紹介します。Lamdba を引き数に取る Java のメソッドを Nashorn から呼び出す方法と Nashorn から Java の Parallel Stream を操作する方法をご覧頂けます。
おことわり
以下は jlaskey による Nashorn Blog への投稿の翻訳です。原文はhttps://blogs.oracle.com/nashorn/entry/nashorn_and_lambda_what_theでご覧頂けます。翻訳文の URL はhttps://blogs.oracle.com/nashorn_ja/entry/nashorn_and_lambda_what_theです。
訳文
Java 8 Lambda を設計したブライアン・ゲーツから、Nashorn で Lambda を利用するサンプルプログラムを書いてみてはどうかと提案を受けました。私はずっと Nashorn にかかりきりでしたので、Lambda は殆ど触っていませんでした。そこでスチュワート・マークスが書いた Lambda のサンプルコードを読んでみたところ、それ程難しくない事が分かりました。Lambda API の詳細はJDK 8 b92 API 仕様のページで見つかりました。
Lambda を Nashorn から使用する仕組みは、JavaScript の知識があれば直ぐに理解できます。
- Lambda を受け取るメソッドには JavaScript の function を渡します
- JavaScript の配列は事前に Java の Collection に変換しておく必要があります
- 複数行に渡るメソッド呼び出しを記述する際、Java では'.'を行の最初に書く事が多いですが、JavaScript にはセミコロンの自動挿入があるため、'.'は行の最後に書く必要があります。
これらを除けば、JavaScript から Lambda にアクセスするプログラムは Java で書いた場合と殆ど変わりありません。
#!/usr/bin/env jjs -scripting
var copyright = <<<EOS;
/*
* Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* - Neither the name of Oracle nor the names of its
* contributors may be used to endorse or promote products derived
* from this software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
* IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
* EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
* PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
* LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
* NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
EOS
var Collectors = java.util.stream.Collectors;
// コピーライトの文字列を空白文字で分割します
var tokens = copyright.split(/\s+/);
// JavaScript の配列 (tokens) を Java の ArrayList に変換します
var list = new java.util.ArrayList();
tokens.map(function(e) list.add(e));
// 結果を格納するための JavaScript の配列
var result = [];
// 処理を並列化します
list.parallelStream().
// 単語のみを抜き出します
filter(function(t) t.match(/^[A-Za-z]+$/)).
// 大文字を小文字に変換します
map(function(t) t.toLowerCase()).
// 重複している単語を除きます
collect(Collectors.groupingBy(function(t) t)).
// 処理結果を JavaScript の配列に格納します
forEach(function(t) result.push(t));
// 結果を並び替えます
result.sort();
print(result);
処理結果は以下の通りです。
a,above,advised,all,and,any,are,arising,be,binary,business,but,by,caused,code,
conditions,consequential,contributors,copyright,damages,derived,disclaimer,
documentation,endorse,even,event,express,fitness,following,for,form,from,
goods,holders,however,if,implied,in,is,its,liable,limited,list,loss,materials,
may,merchantability,must,name,names,negligence,neither,no,nor,not,of,on,or,
oracle,other,out,owner,particular,permitted,possibility,prior,procurement,
products,promote,provided,purpose,redistribution,redistributions,reproduce,
retain,rights,shall,software,source,specific,strict,substitute,such,that,
the,theory,this,to,tort,use,used,warranties,way,whether,with,without,written
補足
サンプルコード中の collect(Collectors.groupingBy(function(t) t)) の部分は distinct() で置き換え可能です。
filter, map, collect, forEach の引き数に弓括弧が無く、return 無しに値を返せているのは JavaScript 1.8 の Expression Closure です。この拡張記法は --no-syntax-extensions オプションで抑制出来ます。EcmaScript 6 のアロー記法 (t) => t.match(/^[A-Za-z]+$/) も使えるようにする予定です。function(t) => { return t.match(/^[A-Za-z]+$/); } の様に記述することも可能です。