kagamihogeの日記

kagamihogeの日記です。

JDK 8のLambdaをEclipseで学ぶ

そろそろ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 IDEEclipse 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()のところはLambda式でこのように書ける。これで() -> がいっこ出てきた。

            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) is ambiguous for the type AccessControllerとコンパイルエラーになる。

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());
     }
}

メソッドオーバーロードでLambda式を使う。

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;
    }
}