メモ置き場

メモ置き場です.開発したものや調べたことについて書きます.

[tex: ]

Javaの勉強(6):ラムダ式

Javaラムダ式を使ってみる.

匿名クラス

普通Javaでクラスを定義して使う場合は,1つのファイルに1つのクラスを定義して使う.しかし再利用せず使い捨てをするクラスを使いたい場合,いちいち別ファイルに定義をしてからインスタンス生成をして,というようにするのは不便である.そこで,クラスの定義とインスタンス化を同時にできるようにしましょう,というのが匿名クラス(または匿名内部クラス)である.

匿名クラスの特徴として

  • クラス名がない
  • インスタンス化するときに定義
  • デフォルトコンストラクタのみ
  • 再利用できない
  • 既存クラスを継承・インターフェースを実装する

がある.最後の特徴は,匿名クラスのインスタンスを参照するための参照変数を宣言するためには,既存クラス・インターフェース型の変数を用意しなければいけないためである.

以下に匿名クラスのコード例を示す.

// MyInterface.java
public interface MyInterface {
    void myMethod();
}
public class LambdaMain {
    public static void main(String[] args) {
        System.out.println("This is example code for anonymous class.");

        MyInterface myInterface = new MyInterface() {
            @Override
            public void myMethod() {
                System.out.println("This is myMethod!");
            }
        };

        myInterface.myMethod();
    }
}

MyInterfaceの実装をmainメソッド内に直接書いている.myInterfaceからオーバーライドしたmyMethodを呼び出すと,実装した通りの動作をしていることがわかる.

匿名クラスの定義部分は他のメソッドの引数になっていることが多い.例えばComparatorインターフェースのcompareメソッドのオーバーライドをListイターフェースのsortメソッド内部に書くといったことがある.以下に例を示す.

//Student.java
public class Student {
    private int studentNumber;
    private String name;

    public Student(String name, int studentNumber) {
        this.name = name;
        this.studentNumber = studentNumber;
    }

    public int getStudentNumber() {
        return this.studentNumber;
    }

    public String getStudentName() {
        return this.name;
    }

    @Override
    public String toString() {
        return studentNumber + " " + name;
    }
}
//LambdaMain.java
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

public class LambdaMain {
    public static void main(String[] args) {
        List<Student> list = new ArrayList<>();

        list.add(new Student("akai", 1));
        list.add(new Student("takahashi", 23));
        list.add(new Student("yamamoto", 31));
        list.add(new Student("nakasato", 17));
        list.add(new Student("kishibe", 9));

        for(Student s : list) {
            System.out.println(s);
        }

        list.sort(new Comparator<Student>(){
            @Override
            public int compare(Student s1, Student s2) {
                return s1.getStudentNumber() - s2.getStudentNumber();
            }
        });
        System.out.println();

        for(Student s : list) {
            System.out.println(s);
        }
    }
}

list.sort引数にComparatorの実装を匿名クラスとして渡している.

ラムダ式

匿名クラスの定義をさらに簡略化したものがラムダ式である.ラムダ式は,どのクラス・インターフェースの定義かが推定でき,かつ匿名クラスで実装していたメソッドが1つでどのメソッドの定義なのかが推定できる場合に使える.

ラムダ式は初見では非常にとっつきにくく感じてしまうが,結局やっていることは「抽象メソッドのオーバーライド」である.オーバーライドするときに行くつかの条件が揃ったときにラムダ式が使えるようになる.

ラムダ式(引数) -> {処理}の形で書く.例えばComparatorインターフェースはcompareメソッドのみを定義すれば良いため,上記コードはラムダ式を使って次のように書くことができる.

list.sort((s1,s2) -> s1.getStudentNumber() - s2.getStudentNumber());

ラムダ式

  1. 渡される匿名クラスのインスタンスは必ず生成されるのでnewを省略
  2. sortの引数なのでComparatorを実装したクラスが渡されることがわかるのでComparatorを省略
  3. さらにメソッド名compareも推定できるので省略
  4. メソッドに渡される引数はListの中身であるStudentであることが推定できるので引数の型は省略されs1, s2とするだけでStudentクラスのインスタンスであると推定される.今回は処理が短文であるため,{ }を省略して書くことができる.また{ }を省略した場合は,その式の値は戻り値とみなされる.

関数型インターフェース

ラムダ式が使えるには,実装するインターフェースに抽象メソッドが1つだけ含まれていることが条件である.複数の抽象メソッドがある場合,ラムダ式によってどのメソッドをオーバーライドしたのか推定できないからである.このような形式のインターフェースを「関数型インターフェース」という.

関数型インターフェースの条件は,抽象メソッドが一つだけであることであり,実装が必要ないメソッドが複数あっても良い.関数型インターフェースを定義するときは@FunctionalInterface<>/code>アノテーションをつける.複数の抽象メソッドが含まれていないかコンパイル時にチェックが入る.