そろそろJDK 8のLambdaをちょっとは勉強しなくてはと思いつつ、Eclipseとかが無いと何も出来ない程度には惰弱なJavaプログラマ勢なので、環境を作って勉強できるように試みた。
やること
Trying Out Lambda Expressions in the Eclipse IDEという「Eclipse IDEでLambda式を試そうぜ」な大変有り難い記事があるのでコレを読んでサンプルコードを試す。また、Eclipse IDE with JDK 8 supportのリンクからJDK8対応のEclipseがダウロードできるようになっている。この記事用に用意されたEclipseだと思われるが、ちょろっと試す分にはコレで充分そうであるのでコレを使う。
2014/06/11 追記 Eclipse Kepler SR2のJava 8サポートのパッチ - kagamihogeの日記
2014/06/26 追記 Java 8対応のEclipse 4.4 Lunaがリリースされています。
サンプル動かして試して理解するのが目的なので、割ととりとめの無い記述のエントリになってます。詳しいことはリンク先のエントリを読んで頂きたく。
準備
JDK8のインストール
何はともあれJDK8が無いと何も始まらない。
JDK 8 — Project KenaiからDownloadsを選んでJDK 8u20 Early Access Releases — Project Kenaiに行き各プラットフォームごとのJDKを手に入れる。Windowsの場合、インストーラをポチポチとクリックしていくだけ。
java -versionでバージョン確認してみる。
JDK8対応版のEclipse
Trying Out Lambda Expressions in the Eclipse IDEのEclipse IDE with JDK 8 supportからダウンロードする。
Window -> PreferencesのJava -> CompilerでCompiler compliance levelが1.8(BETA)なことを確認する。
Javaプロジェクト作成時にJavaSE-1.8にする。
package jdk8test; import java.util.concurrent.Callable; public class Main { public static void main(String[] args) throws Exception { Callable<String> c = () -> "Hello from Callable"; System.out.println(c.call()); } }
動きました。
Lambdaを知る
Trying Out Lambda Expressions in the Eclipse IDEに沿ってサンプルのソースコードを動かしていってみる。特に断りが無い限り、このURLからコードを拝借していますが、俺なりに変えている場所もあります。
The Hello Application with Lambda Expressions
引数2つ取ってString返すメソッドをいっこ持つinterfaceを作る。で、それをLambda式で構築する。関数型インターフェース[Functional Interface]とか言うらしい。
package jdk8test; public class Hello { interface HelloService { String hello(String firstname, String lastname); } public static void main(String[] args) { HelloService helloService = (String firstname, String lastname) -> { String hello = "Hello " + firstname + " " + lastname; return hello; }; System.out.println(helloService.hello("kagami", "hoge")); } }
Hello kagami hogeと表示される。旧来は↓のように書いてました、ってヤツですかね。
HelloService helloService2 = new HelloService() { @Override public String hello(String firstname, String lastname) { String hello = "Hello " + firstname + " " + lastname; return hello; } }; System.out.println(helloService2.hello("kagami", "hoge"));
Using Lambda Expressions with Some Common Functional Interfaces
JDK標準ライブラリのFunctional InterfaceでLambda式使える例はこんな感じっすよ、とか書いてある。
FileFilterインターフェース。サンプルは長いんでテキトーに短くした。
package jdk8test; import java.io.File; import java.io.FileFilter; import java.nio.file.Paths; public class FileFilterTest { public static void main(String[] args) { FileFilter fileFilter = (f) -> { return f.getName().lastIndexOf(".zip") > 0; }; //true System.out.println( fileFilter.accept(Paths.get("C", "java", "jdk1.8.0", "src.zip").toFile())); //false System.out.println( fileFilter.accept(Paths.get("C", "java", "jdk1.8.0", "README.html").toFile())); }; }
前はこんな感じなんで、大分スッキリする感じ。
FileFilter fileFilter2 = new FileFilter() { @Override public boolean accept(File pathname) { // ... return false; } };
あと、たまーにコンテンツアシストが上手くいかないことあるが、まぁ仕方ないね。
Runnableインタフェース。
package jdk8test; public class HelloRunnable { public static void main(String args[]) { (new Thread(() -> { System.out.println("Hello from a thread"); })).start(); } }
前はこんな感じですかね。
new Thread(new Runnable() { @Override public void run() { System.out.println("Hello from a thread"); } }).start();
Comparatorインタフェース。
package jdk8test; public class Employee { public String name; public Employee(String name) { super(); this.name = name; } }
package jdk8test; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class EmployeeSort { public static void main(String[] args) { Employee e1 = new Employee("kagami"); Employee e2 = new Employee("hoge"); Employee e3 = new Employee("foobar"); List<Employee> list = new ArrayList<>(); Collections.addAll(list, e1, e2, e3); Collections.sort(list, (x, y) -> x.name.compareTo(y.name)); for (Employee e : list) { System.out.println(e.name); } } }
foobar, hoge, kagamiと表示される。前は↓な感じなんで、コレは短くなってる感強い。
Collections.sort(list, new Comparator<Employee>() { @Override public int compare(Employee x, Employee y) { return x.name.compareTo(y.name); } });
How Are the Target Type and Lambda Parameter Type Inferred?
returnにLambda式を使う。functional interfaceでないとダメらしい。
package jdk8test; public class HelloRunnable2 { public static Runnable getRunnable() { return () -> { System.out.println("Hello from a thread"); }; } public static void main(String args[]) { new Thread(getRunnable()).start(); } }
Lambda Expressions as Target Typesとかいう例はちょっと複雑。
package jdk8test; import java.util.concurrent.Callable; public class HelloCallable2 { public static void main(String[] args) { try { Callable<Runnable> c = () -> () -> { System.out.println("Hello from Callable"); }; c.call().run(); } catch (Exception e) { System.err.println(e.getMessage()); } } }
() -> () ->とかいう時点で初心者の俺は割りと目まいがする。ので、一歩ずつ進むことにする。
まず、旧来はこんな感じにして書く。長い……
Callable<Runnable> c3 = new Callable<Runnable>() { @Override public Runnable call() throws Exception { return new Runnable() { @Override public void run() { System.out.println("Hello from Callable"); } }; } };
次。外側のnew Callable
Callable<Runnable> c4 = () -> { return new Runnable() { @Override public void run() { System.out.println("Hello from Callable"); } }; };
次。return new RunnableのところはLambda式でこのように書ける。
Callable<Runnable> c5 = () -> { return () -> { System.out.println("Hello from Callable"); }; };
で、最終的にこうなる。
Callable<Runnable> c6 = () -> () -> { System.out.println("Hello from Callable"); };
配列の初期化をこんな風にかける。
package jdk8test; import java.util.concurrent.Callable; public class CallableArray { public static void main(String[] args) throws Exception { Callable<String>[] c = new Callable[] { () -> "Hello from Callable a", () -> "Hello from Callable b", () -> "Hello from Callable c" }; System.out.println(c[1].call()); } }
Lambda式のキャスト。下記のコードはThe method doPrivileged(PrivilegedAction
String user = AccessController.doPrivileged(() -> System.getProperty("user.name"));
この場合は下記のように書く。
String user = AccessController.doPrivileged((PrivilegedAction<String>)() -> System.getProperty("user.name"));
参考演算子で返ってくるものをLambda式で書く。
package jdk8test; import java.util.concurrent.Callable; public class HelloCallableConditional { public static void main(String[] args) throws Exception { boolean flag = true; Callable<String> c = flag ? (() -> "Hello from Callable: flag true") : (() -> "Hello from Callable: flag false"); System.out.println(c.call()); } }
package jdk8test; import java.util.concurrent.Callable; public class HelloRunnableOrCallable { static String hello(Runnable r) { return "Hello from Runnable"; } static String hello(Callable c) { return "Hello from Callable"; } public static void main(String[] args) { String hello = hello(() -> "Hello Lambda"); System.out.println(hello); } }
これを実行するとHello from Callableと表示される。なぜ引数がRunnableではなくCallableが選ばれるのか? 理由は、Callable#callは返り値の型がObjectで、Runnable#runは返り値の型がvoidなため。mainでhelloメソッドに渡す引数「() -> "Hello Lambda"」は、文字列を返す何かしらである。で、void Runnable#runはその要件にマッチしないが、Object Callable#callはマッチする。
thisはそのLambda式をラップしてるインスタンスを指す。
package jdk8test; public class HelloLambda { Runnable r = () -> { System.out.println(this); }; public String toString() { return "Hello from Class HelloLambda"; } public static void main(String args[]) { new HelloLambda().r.run(); } }
Lambda式のパラメータ名とローカル変数名が衝突する場合、コンパイルエラーになる。下記のコードはLambda式のe1, e2のところで Lambda expression's parameter e1 cannot redeclare another local variable defined in an enclosing scope. なコンパイルエラーになる。
Employee e1 = new Employee("kagami"); Employee e2 = new Employee("hoge"); List<Employee> list = new ArrayList<>(); Collections.addAll(list, e1, e2); Collections.sort(list, (Employee e1, Employee e2) -> e1.name.compareTo(e2.name));
まず上の方のsortの例で使ったEmployeeにstaticメソッドcompareByNameをつくる。
public class Employee { ... public static int compareByName(Employee x, Employee y) { return x.name.compareTo(y.name); } }
こう書くのを、
Collections.sort(list, (x, y) -> x.name.compareTo(y.name));
こう書ける。
Collections.sort(list, Employee::compareByName);
non-staticの場合はどうか。Employeeにメソッドを追加する。
public class Employee { ... public int compareByNameNonStatic(Employee x, Employee y) { return x.name.compareTo(y.name); } }
この場合はインスタンス経由になる。
Employee dummy = new Employee(""); Collections.sort(list, dummy::compareByNameNonStatic);
Virtual Extension Methods - Lambda式じゃないけどJDK8の新機能てことでこの記事で紹介されている。
ざっくり言えばInterfaceにデフォルトの実装を持たせられる。
public class VirtualExtension { interface Hoge { default String getString() { return "Hoge"; } } public static void main(String[] args) { Hoge h = new Hoge() {}; System.out.println(h.getString()); } }
JDK8のjava.util.Map
public interface Map<K,V> { int size(); ... boolean isEmpty(); ... default boolean remove(Object key, Object value) { Object curValue = get(key); if (!Objects.equals(curValue, value) || (curValue == null && !containsKey(key))) { return false; } remove(key); return true; } .... default V replace(K key, V value) { return containsKey(key) ? put(key, value) : null; } }