Java インターフェイスってnewできないんじゃ...、それ匿名クラスらしい

自分も思いっきり惑わされたのですが、結論から言うと、インターフェイスを継承した匿名クラスを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つであることに注意してください。

Java8のラムダ式を理解する - Qiita

 

f:id:ts0818:20170722150906j:plain

 

関数型インターフェイスラムダ式

関数型インターフェースは抽象メソッドが1つなので、ラムダ式で利用するのに非常に都合がいいです。
この中から使用頻度の高いFunction、Consumer、Predicateを紹介します。

Java8のラムダ式を理解する - Qiita

java.util.functionパッケージ以下にインターフェースが新しく追加され、それを関数型インターフェイスというようです。

パッケージjava.util.function

関数型インタフェースは、ラムダ式やメソッド参照のターゲットとなる型を提供します。

java.util.function (Java Platform SE 8)

実際に、試してみる。

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) {

    Consumer buyer = (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) {
    
    Predicate checker = (String str) -> { return str.equals("涙のリクエスト"); };
    boolean result = checker.test("涙のリクエスト");
    System.out.println(result);

  }
}

 

ラムダ式の使いどころ

そもそもなぜラムダ式が生まれたのでしょうか。
それは値ではなく関数を引数として渡したかったからです。
代表的な例としてCollections.sortやStreamAPIのメソッド群があります。

Java8のラムダ式を理解する - Qiita

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}; 
    List numbersList = 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 }; 
    List numbersList = 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のようなメソッドを終端操作と呼びます。普通は、

    Predicate predicate = (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}; 
    List numbersList = 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}; 
    List numbersList = 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 + ","); });

  }
}

 

Java8 Streamざっくりまとめ - Qiita

Javaラムダ式メモ(Hishidama's Java8 Lambda Expression Memo)

Java8のラムダ式を理解する - Qiita

 

ラムダ式も徐々に覚えていかねばですね。