【Javaお勉強日記】ラムダ式についてわーっとやる
わーっとやっちゃいけないところの様な気もするけど、やり過ぎるとコールバック地獄みたいになると聞いたので、わーっとで。
ラムダ式とは
匿名クラスを簡単に書いたもの。
匿名クラスとは、指定のインタフェースを実装するオブジェクトを返す式。
例えば
List型オブジェクトの、sortメソッドを使いたい。
List<Hoge> testList = new ArrayList<>();
testList.add(new Hoge("aaa"));
testList.add(new Hoge("ccc"));
testList.add(new Hoge("bbb"));
testList.sort(c);
ってやってソートするんだけど、最後に使う「c」がくせもの。
ここには、「Comparatorインタフェースを実装したクラスのインスタンス」を渡さないといけない。
つまり、「Comparatorインタフェースを実装したクラス」を作らないといけない。
作るのはそんなに難しくなくて、
// Comparator<T>を実装したComparatorクラスを作る
public class HogeComparator implements Comparator<Hoge> {
@Override
// compareメソッドをオーバーライドする
public int compare(Hoge o1, Hoge o2) {
// o1とo2を比較して、o1が小さい場合は負、同じなら0、o1が大きければ正の値を返す
String h1 = o1.getFuga();
String h2 = o2.getFuga();
// 文字列同士なら、StringがcompareToメソッドを持っているので、その結果をそのまま返せばOK
return h1.compareTo(h2);
}
}
っていう感じで作って
List<Hoge> testList = new ArrayList<>();
testList.add(new Hoge("aaa"));
testList.add(new Hoge("ccc"));
testList.add(new Hoge("bbb"));
HogeComparator c = new HogeComparator();
testList.sort(comparetor);
でインスタンス作って、そのインスタンスをsortメソッドに引数として渡せば良いんだけど、わざわざComparator実装クラスを作って、インスタンスを作って、sortの引数に入れる、って言うのはちょっと手間。
そこで、一々クラス作らなくてもいいようにしようぜというのが匿名クラス。
Comparator<Hoge> c = new Comparator<Hoge>(){
public int compare(Hoge o1, Hoge o2){
String h1 = o1.getFuga(), h2 = o2.getFuga();
return h1.compareTo(h2);
}
};
もう、その場でクラス作ってインスタンス作って変数cに入れちゃおうぜ、っていう。javascriptの匿名関数みたいなやつ。嫌い。
でもこのcってsortの引数に入れるだけだし、もう
testList.sort(new Comparator<Hoge>(){
public int compare(Hoge o1, Hoge o2){
String h1 = o1.getFuga(), h2 = o2.getFuga();
return h1.compareTo(h2);
}
});
でいいじゃん、っていう使い方をするんだけど、あー、コールバック地獄みが見えてきた~~
ということで、それをもっと簡潔に書こうぜ、っていうのがラムダ式。
・sortメソッドは「Comparatorインタフェースを実装したオブジェクトを引数に取る」と決まっているから、型の宣言要らないんじゃない?
・引数の型もインタフェースで宣言されてるから、一々宣言し直さなくてもいいじゃん。
・Comparatorインタフェースには、compareメソッドしかない。つまり、どのメソッドをオーバーライドするかの宣言も要らないんじゃない?
・いちいち「h1 = o1.getFuga()」しなくても、compareTo(o1.getFuga,o2.getFuga)でいいよね。
・compareメソッドは戻り値を返すって決まってるから、「return」って書かなくても結果が戻るって分かるよね。
……って、これもいらん、アレも要らん、と消していくと、
testList.sort( (o1, o2){ o1.getFuga().compareTo(o2.getFuga() ) } );
が、残る。
でもこれだけだとコンパイラが何が何だか解らなくなっちゃうので、「ここは省略表記ですよー」という印を付けてあげる。
そのために、「->」を使って、{}を省略して、
testList.sort( (o1, o2) -> o1.getFuga().compareTo( o2.getFuga() ) );
と、なる。この、sortの引数として入っている()の中身がラムダ式。
どっかで見たね!javascriptのアロー関数に似てるね!でもアロー関数と違って、「メソッドの引数として指定されている、特定のインタフェースを実装したクラス(のインスタンス)を作る」「しかも、そのインタフェースには抽象メソッドが一つだけしか定義されていない」時にしか使えないから注意。(デフォルトメソッドやスタティックメソッドはあってもいい)
こういう、「抽象メソッドが一つしか無いインタフェース」を、【関数型インタフェース】と呼ぶ。
便利な使い方
例えば、
public static void pickUp(List<Member> members){
for(Member member : members){
if(member.getId < 10){
System.out.println(member.getName());
}
}
}
こういう感じに「IDが10より若い人だけ取り出す関数」があったとする。
でも、これだと別の条件で抽出したい時に使い回すのに不便。
なので、外部から条件を受け取るようにしたい。
標準の関数型インタフェース
そんなときに活躍するのが、関数型インタフェース。自作しても良いけど、よく使うものはjava.util.functionパッケージ内に定義されている。それが標準の関数型インタフェース
そのうちの一つ、boolean型を返す「Predicate」インタフェース型の変数pを引数として取るようにして、
public static void pickUp(List<Member> members, Predicate p){
for(Member member : members){
if(p.test(member)){
System.out.println(member.getName());
}
}
}
と書き直してみる。
Predicateインタフェースは関数型インタフェースで、定義されているメソッドは「test」ひとつ。
なので、
Predicate<Member> p = m -> m.getId() < 10;
でPredicate型のtestメソッドをラムダ式でオーバーライドして、testメソッドの引数として渡してあげる。
(本当は引数に直接ラムダ式書けるはずなんだけど、そなーりんとが怒る(´・ω・`))
とりあえずわーっとここまで。
この記事が気に入ったらサポートをしてみませんか?