自分も思いっきり惑わされたのですが、結論から言うと、インターフェイスを継承した匿名クラスをnew(インスタンス化する)していたということみたいです。
・インターフェースをnewする違和感が解決した - Qiita
通常の場合、インターフェイスは、インターフェイスを継承したクラスでインターフェースのメソッドをオーバーライドしたりしたものを定義し、そのクラスをインスタンス化して実装されるものですが、「匿名クラス」や「ラムダ式」 を利用することで、インターフェイスを継承したクラスを用意しなくても、インターフェースが利用できます。
それぞれでインターフェイスを利用
「クラスで継承 」「匿名クラス」「ラムダ式」のそれぞれでインターフェイスを利用してみる。
「クラスで継承」の場合
package normal; public interface InterfaceSample { abstract void hello(); abstract void goodEvening(String name); abstract String goodMorning(String name); }
package normal; public class ImplementionInterface implements InterfaceSample { @Override public void hello() { System.out.println("Hello!!!!!! by 『シャイニング(ジャック・ニコルソン)』"); } @Override public void goodEvening(String name) { // System.out.println(name + "氏、今晩は、過ごしやすうございますね。"); } @Override public String goodMorning(String name) { // System.out.println(name + "氏、お早くから、ご苦労様でございます。"); return null; } }
package normal; public class SampleNormal { public static void main(String[] args) { ImplementionInterface ifam = new ImplementionInterface(); ifam.hello(); ifam.goodEvening("ジャック・ニコルソン"); ifam.goodMorning("ジャック・ニコルソン"); } }
「匿名クラス」の場合
package anonymous; public interface InterfaceSample { abstract void hello(); abstract void goodEvening(String name); abstract String goodMorning(String name); }
package anonymous; public class SampleAnonymous { public static void main(String[] args) { InterfaceSample ifam = new InterfaceSample() { @Override public void hello() { // System.out.println("Hello!!!!!! by 『シャイニング(ジャック・ニコルソン)』"); } @Override public void goodEvening(String name) { // System.out.println(name + "氏、今晩は、過ごしやすうございますね。"); } @Override public String goodMorning(String name) { // System.out.println(name + "氏、お早くから、ご苦労様でございます。"); return name; } }; ifam.hello(); ifam.goodEvening("ジャック・ニコルソン"); ifam.goodMorning("ジャック・ニコルソン"); } }
「ラムダ式」の場合
package lamda; public interface InterfaceSample1 { abstract void hello(); }
package lamda; public interface InterfaceSample2 { abstract void goodEvening(String name); }
package lamda; public interface InterfaceSample3 { abstract String goodMorning(String name); }
package lamda; public class SampleLamda { public static void main(String[] args) { // メソッドに引数が無い場合 InterfaceSample1 ifam1 = () -> {System.out.println("Hello!!!!!! by 『シャイニング(ジャック・ニコルソン)』");}; ifam1.hello(); // メソッドに引数がある場合 InterfaceSample2 ifam2 = (String name) -> {System.out.println("スタンダードな書き方");}; ifam2 = (name) -> {System.out.println("引数のデータ型を省略できたりする");}; ifam2 = name -> {System.out.println(name + "さん、引数が1つの場合『()』が省略可");}; ifam2.goodEvening("ダニエル"); // メソッドに引数、戻り値がある場合 InterfaceSample3 ifam3 = (name) -> {return name;}; ifam3 = name -> name + "氏"; String name = ifam3.goodMorning("ミヤギ"); System.out.println(name); } }
「ラムダ式」でインターフェースを実装する場合は、インターフェイスが持っているメソッドを1つにしないと駄目みたいです。なので、インターフェースが3つになってます。
ラムダ式で利用するインターフェースは抽象メソッドが1つであることに注意してください。
関数型インターフェイスとラムダ式
関数型インターフェースは抽象メソッドが1つなので、ラムダ式で利用するのに非常に都合がいいです。
この中から使用頻度の高いFunction、Consumer、Predicateを紹介します。
java.util.functionパッケージ以下にインターフェースが新しく追加され、それを関数型インターフェイスというようです。
実際に、試してみる。
Function<T, R>
Function<T, R>のTはメソッドの引数の型、Rは戻り値の型を指定します。 メソッドは R apply(T) です。
package lamda; import java.util.function.Function; public class SampleFunction { public static void main(String[] args) { Function<Integer, String> funcer = (i) -> {return "*" + i;}; String result = funcer.apply(100); System.out.println(result); } }
BiFunction<T, T, R>
package lamda; import java.util.function.BiFunction; public class SampleBiFunction { public static void main(String[] args) { BiFunction<Integer, Integer, Integer> biAdder = (a, b) -> { return a + b; }; int result = biAdder.apply(10, 20); System.out.println(result); } }
Consumer<T>
Consumer<T>のTはメソッドの引数の型を指定します。 メソッドは void accept(T) です。
package lamda; import java.util.function.Consumer; public class SampleConsumer { public static void main(String[] args) { Consumerbuyer = (String goods) -> { System.out.println(goods + "を手に入れました。"); }; buyer.accept("お米券"); } }
Predicate<T>
Predicate<T>のTはメソッドの引数の型を指定します。 メソッドは boolean test(T) です。
package lamda; import java.util.function.Predicate; public class SamplePredicate { public static void main(String[] args) { Predicatechecker = (String str) -> { return str.equals("涙のリクエスト"); }; boolean result = checker.test("涙のリクエスト"); System.out.println(result); } }
ラムダ式の使いどころ
そもそもなぜラムダ式が生まれたのでしょうか。
それは値ではなく関数を引数として渡したかったからです。
代表的な例としてCollections.sortやStreamAPIのメソッド群があります。
Javaで用意されているComparatorインターフェイスや、Collectionインターフェースのstreamというメソッドは、自分自身のStreamインスタンスを返し、そのインスタンスが持つメソッドは、関数型インターフェースを引数にとることができるようです。(つまり、ラムダ式が使えるっちゅうことですね。)
実際に、上記サイトの真似をして、実装していきたいと思います。
package lamda; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; import java.util.List; public class SampleLamda01 { public static void main(String[] args) { int[] numbers = {-1, 0, 9, 2, 6, 3, 1}; List<integer> numbersList = new ArrayList<>(); // コレクションArrayListの初期化 for(int num : numbers) { numbersList.add(num); } // インタフェースComparator<T> // 関数型インタフェースなので、ラムダ式またはメソッド参照の代入先として使用可 Comparator<integer> cpa = (a, b) -> {return a - b;}; // 並び替え Collections.sort(numbersList, cpa); // 結果を出力 for(Integer num: numbersList) { System.out.print(num + ", "); } } }
並び替えが一瞬にしてできてしまう。
Comparator<Integer> c = (a, b) -> { return a - b; }; Collections.sort(numbersList, c);
の部分は、
Collections.sort(numbersList, (a, b) -> { return a - b; });
とも書けるようです。
ラムダ式の内容を変えることで、ソート順を変更できるようです。
小さい順
Collections.sort(numbersList, (a, b) -> { return a - b; });
絶対値の小さい順
Collections.sort(numbersList, (a, b) -> { return a*a - b*b; });
続いて、コレクションインターフェイスのstream()メソッドが返す自身のstreamインスタンスの持つメソッド(関数型インターフェイスを引数に持つ)を試していきます。StreamAPIというようです。
void forEach(Consumer<T>)
forEachメソッドはConsumerを引数に取り、要素の数だけ処理を繰り返します。
package lamda; import java.util.ArrayList; import java.util.List; public class SampleStream { public static void main(String[] args) { int[] numbers = {-1, 8, 3, 4, -7}; ListnumbersList = new ArrayList(); for(int num : numbers) { numbersList.add(num); } numbersList.stream().forEach((i) -> { System.out.print(i + ", "); }); } }
Stream filter(Predicate<T>)
filterメソッドはPredicateを引数に取り、条件に合致しないものを除いたStreamを返します。 返ってくるのもStreamなのでそのままForEachメソッドを呼び出すことができます。
package lamda; import java.util.ArrayList; import java.util.List; import java.util.function.Consumer; import java.util.function.Predicate; public class SampleFilter { public static void main(String[] args) { int[] numbers = { -1, 8, 3, 4, -7 }; ListnumbersList = new ArrayList<>(); for(int num : numbers) { numbersList.add(num); } Predicate predicate = (i) -> { return i > 0;}; // 戻り値はStream型 Consumer consumer = (i) -> { System.out.print(i + ", "); }; numbersList.stream().filter(predicate) // 0より大きい値を返す .forEach(consumer); // } }
filterメソッドは戻り値がStream型なので続けてforEachを呼び出すことができます。 そのため、filterのようなメソッドは中間操作と呼ばれています。 対してforEachのようなメソッドを終端操作と呼びます。普通は、
Predicatepredicate = (i) -> { return i > 0;}; // 戻り値はStream型 Consumer consumer = (i) -> { System.out.print(i + ", "); }; numbersList.stream().filter(predicate) // 0より大きい値を返す .forEach(consumer); //
の部分を
numbersList.stream().filter((i) -> { return i > 0; }) .forEach((i) -> { System.out.print(i + ","); });
と記述していきます。
Stream map(Function<T, R>)
mapメソッドはFunctionを引数に取り、処理後の結果をStreamにして返します。 filterと同じく中間操作です。
package lamda; import java.util.ArrayList; import java.util.List; public class SampleMap { public static void main(String[] args) { int[] numbers = {-1, 8, 3, 4, -7}; ListnumbersList = new ArrayList<>(); for(int num : numbers) { numbersList.add(num); } numbersList.stream().filter((i) -> { return i > 0; }) .map((i) -> { return "*" + i + "*"; }) .forEach((s) -> { System.out.print(s + ","); }); } }
Stream sorted(Comparator<>)
java.utilパッケージですが、Comparatorを引数に取るsortedメソッドも用意されています。 filterと同じく中間操作です。
package lamda; import java.util.ArrayList; import java.util.List; public class SampleSort { public static void main(String[] args) { int[] numbers = {-1, 8, 3, 4, -7}; ListnumbersList = new ArrayList<>(); for(int num : numbers) { numbersList.add(num); } numbersList.stream().filter((i) -> { return i > 0; }) .sorted((i1, i2) -> { return i1 - i2; }) .map((i) -> { return "*" + i + "*"; }) .forEach((s) -> { System.out.print(s + ","); }); } }
・Javaラムダ式メモ(Hishidama's Java8 Lambda Expression Memo)
ラムダ式も徐々に覚えていかねばですね。
今回はこのへんで。