【Java】Java8から追加された関数型インターフェースとラムダ式について解説


Java8で新しく登場した「関数型インターフェースってなんだろう・・・」と業務で説明を受けてもわかりませんでした。


他にも「ラムダ式ってどんな式?」と色々わからなかったのでいろいろ調べて学習した備忘録を残します。


Java8で追加された「関数型インターフェース」と「ラムダ式」はこちらのOracle公式ページに追加内容が記載されています。
docs.oracle.com

ラムダ式とは

ひとことでいうと、メソッド定義を式として扱える機能のこと


インターフェースを実装したインスタンスを返す式とも言えます。


メソッドを変数のように扱える機能、それがラムダ式です。


メソッド定義を式で扱うというのは上記で記載した関数型インターフェースの抽象メソッドの実装を式として扱う時に使ったりします。

ラムダ式の使い方について

まず、ラムダ式の構文から見ていきます。

(引数) -> {処理}

これは、()内のこの引数をもらったら、{}ブロック内の処理を行うという書き方です。


ー>という書き方が見慣れないですが、引数と処理の間に書く構文だと思い込みます。


引数の型の省略や処理内容が一行であれば{}も省略可能です。


例として、関数型インタフェースのConsumerクラス型の参照変数にラムダ式を代入してみます。
※ >Consumerとは新しく追加された関数型インタフェースで引数をとり戻り値を返さないvoid型のインタフェースです。

Consumer<String> consumerobj = (t) -> {System.out.println(t);}

これは、String型の引数tを受け取りtを出力するという命令です。


Consumer型変数に直接式を代入していますね。この処理を代入するのがラムダ式です。


ちなみに括弧の引数の型を省略していますが、これはConsumer型変数を宣言する際にString型と定義しているためjava型推論が行われます。


そのため受け取るt変数はString型であると認識するため明示しなくても問題ありません。

ラムダ式のメリット

ラムダ式のメリットは、「コード量が減りすっきりする」というところではないでしょうか。


ラムダ式によって引数の型が省略できたり、構文によっては処理ブロックも省略できます。


また、匿名クラスの時はnew構文を使用してインスタンスを生成していましたが、ラムダ式の場合それも必要ありません。


抽象メソッドが一つだけのインターフェースであれば匿名クラスに置き換わる実装式になります。

関数型インターフェースとは?

関数型インタフェースとは抽象メソッドを一つしか持たないインターフェースのことを指す
下は、Java8で追加された関数型インターフェースのPredicate

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
 }

また、ラムダ式を代入することで関数型インターフェースを実装したインスタンスを持つことができる。


抽象メソッドが1つのみという制約はあるがdefaultメソッドはカウントしない。


@FunctionalInterfaceアノテーションを付けることで明示的に関数型インターフェースであることを表せる
これは実装者が誤って抽象メソッドを追加してしまったりすることを事前に防ぐこともできる。


アノテーションを付けることで抽象メソッドが2つ以上になると静的エラーとなるため

他に追加された関数型インターフェース

java.util.functionには上記で紹介したPredicateの他にもいくつか関数型インターフェースが追加されています。

Function

引数Tを受け取りRを返すインターフェース
※読み方はファンクションだと思います。

public interface Function<引数,戻り値>

抽象メソッド

R apply(T t)

Rは戻り値、Tは引数

実装例
引数の文字列"hoge"を受け取り、末尾に"hoge"を結合する処理の場合...

	public static void main(String[] args){
	    String str = "hoge";
	    Function<String, String> function = (String t) -> {return t.concat("hoge");};
	    System.out.println(function.apply(str));
	}

実行結果

hogehoge

以下でStringを受け取り、Stringで返す参照変数を定義している。

 Function<String, String> function

以下のラムダ式で引数tを受け取ったらtの末尾に"hoge"を追加するような処理を代入
処理が一行だと処理ブロック{}は省略できます。この例だとreturnも省略できる。

(String t) -> {return t.concat("hoge");};
引数が2つ必要な場合のBiFunction

先ほどのFunctionに加えて引数を2つ取るものとしてBiFunctionがあります。

public interface BiFunction<T,U,R>

メソッドは同じapplyです。

引数を受け取りboolean値を返却するPredicate

ある引数を受け取り処理で判定を行ってtrueまたはfalseを返したいというときに使えるインターフェース
読み方はプレディケート?述語って意味らしい

public interface Predicate<引数の型>

抽象メソッド

boolean test(T t)

実装例
Stirngの引数を受け取り、”hoge”と一致するかを判定する。

	public static void main(String[] args){
	    String str = "hoge";
        Predicate<String> predicate = (String t) -> {return t.equals("hoge");};
        System.out.print(predicate.test(str));
	}

実行結果

true
引数が2つ必要な場合のBiPredicate

引数を2つ取りたい場合は、BiPredicateがあります。
抽象メソッドも同じtest

public interface BiPredicate<T,U>
引数を受け取り何も返却しないConsumer

ある引数を受け取って何か処理をして終わり。返却値なしで使用するインターフェース
読み方はコンシューマー 消費者という意味

public interface Consumer<T>

抽象メソッド

void accept(T t)

実装例
Stirngの引数を受け取り、そのまま標準出力

	public static void main(String[] args){
	    String str = "hoge";
        Consumer<String> consumer = (String t) -> {System.out.println(t);};
        consumer.accept(str);
	}

実行結果

hoge
引数が2つ必要な場合の BiConsumer

引数2つで抽象メソッドも同じです。

引数は受け取らずに戻り値を返すSupplier

引数は渡さないが処理結果を返すインターフェース
読み方はサプライヤ 供給者という意味です。

public interface Supplier<T>

抽象メソッド

T get()


実装例
呼び出したら1+1の結果を返してくれる例
あまり良いサンプルではないが、以下の処理はメソッドを呼び出したら1+1の結果を返してくれます。

	public static void main(String[] args){
        Supplier<Integer> supplier = () -> {return 1+1;};
        System.err.println(supplier.get());
	}

実行結果

2

さいごに

最後まで読んでいただき、ありがとうございました。


もし、記載している内容が間違えていれば遠慮なくコメントしてください!
私自身の勉強になりますし、他の人がこの記事を見た時に間違えた情報を見ることになってしまうので💦