ラムダ式とは?Java初心者が理解できるまで調べた!

2020-10-03

今まで見て見ぬ振りをしてきていたラムダ式について理解を深めていきたいと思います。

ラムダ式(lambda expression)とは

ラムダ式は、引数を受け取って値を返す短いコードです。
メソッドに似ていますが、名前を付ける必要はなく、メソッドに直接実装することができます。
ラムダ式はJava 8から追加されました。2014年3月18日にJava8はリリースされたのでまだ日の浅い機能です。

構文

( 引数 ) -> { 処理 }

引数には実装するメソッドの引数が入ります。

省略について

  • 引数の型は書かなくても書いても良い(自動で判断されるため)、ただし複数ある場合どちらかのみ省略はできない。
    ( str ) -> { 処理 }
  • 引数が1つの場合は括弧の省略が可能です。この場合引数の型を書いてはいけない。
    str -> { 処理 }
  • 処理の{}も省略可能。ただし、一文であること、またreturnを記述できない。
    str -> System.out.println(str)

構文を見てもなんだかよくわからないので徐々に理解を深めていきましょう。

ラムダ式の仕組みとサンプルコード

その前に

ラムダ式の前にラムダ式で使われている匿名クラスについては下記で説明しているのでそちらを読んでみてください。

匿名クラスを使ったサンプルコード (ラムダ式になる前)

まずはラムダ式にする前のサンプルコードをみてみましょう。

//関数型インタフェース
@FunctionalInterface
interface Age{
    void printAge(); // 抽象メソッド
}

class Main{
    public static void main(String[] args)  {
        // インターフェースを生成して匿名クラスで実装したもの代入
        Age obj = new Age(){ //匿名クラス開始
        //メソッドをオーバーライド
        @Override
        public void printAge(){
            // 年齢を表示
            System.out.print("年齢は20");
        }
    };
        // printage() メソッドを呼び出し
        obj.printAge();
    }
}

抽象メソッドを1つのみもつインタフェースを関数型インタフェースと呼びます。

ラムダ式を使ったサンプルコード

ラムダ式を使用すると匿名クラスからnew Ageとpublic void printAge()を省略して書くことができます。

interface Age{
    void printAge();
}

class Main{
    public static void main(String[] args)  {
    //ラムダ式で書く
    Age obj =() -> {System.out.print("年齢は20");};
      // printage() メソッドを呼び出し
      obj.printAge();
  }
}

省略されすぎていてはじめは混乱しますが、ラムダ式の仕組みは以上です。
ラムダ式では関数型インタフェース(抽象メソッドが1つだけあるインターフェース)使用しています。

あらかじめ用意された関数型インターフェース

Java 8ではラムダを使用する度に関数型インターフェースを用意しなくていいように、パッケージjava.util.functionに関数型インターフェースが用意されています。

その中から代表的なものを紹介します。

関数型インターフェース関数メソッド説明
Supplier<T>T get()引数は受け取らずに結果だけを戻す。
Supplierは供給者の意味。(結果を供給する)
Consumer<T>void accept(T)単一の引数を受け取って結果を戻さない。
Consumerは消費者の意味。(引数の消費者)
Function<T, R>R apply(T)引数を受け取って指定された型(R)の結果を戻す。
Functionは関数の意味。(処理)
Predicate<T>boolean test(T)引数を受け取って真偽値を戻す。
Predicateは断定の意味。(真偽値を断定)

その他にもIntPredicate() (int型の引数を受け取って真偽値を返す)など用意されているので詳しくは以下のリンクをご確認ください。

参考サイト:https://docs.oracle.com/より:Package java.util.function

<T>や<E>がなんなの?と思った方は以下も読んでいただければと思います。

サンプルコード

せっかくなので用意されている関数型コードを使ったサンプルコードを作成してみました。

Supplier<T>

import java.util.function.*;
public class Main {
    public static void main(String[] args ) {
        //関数型インターフェースSupplierをラムダ式
        Supplier sup = () ->{
            String str ="Programmer Life";
            return str;
    };
        System.out.println(sup.get()); // Programmer Life
    }
}

Consumer<T>

import java.util.function.*;
public class Main {
    public static void main(String[] args) {
        //関数型インターフェースConsumerをラムダ式
        Consumer<String> con = (p) ->{
            System.out.println(p);
        };
        con.accept("Programmer Life"); // Programmer Life
    }
}

Function<T, R>

import java.util.function.*;
public class Main {
    public static void main(String[] args) {
        //関数型インターフェースFunctionをラムダ式
        Function <Integer, Integer> fun = i -> i * 5;
        System.out.println(fun.apply(3)); // 15
    }
}

Predicate<T>

import java.util.function.*;
public class Main {
    public static void main(String[] args) {
        //関数型インターフェースPredicateをラムダ式
        Predicate<String> pre = (p) ->{
            return p.equals("A");
        };
        System.out.println(pre.test("A")); // true
        System.out.println(pre.test("B")); // false
    }
}

サンプルコード forEachラムダ式

ラムダ式でよく使われているIterableインターフェースのforEach()を使ってArrayListの中身を表示させます。
以下はforEachの定義です。

default void forEach(Consumer<? super T> action)

正直何これ?状態です。
参考:https://docs.oracle.com/インタフェースIterable<T>

もう少しわかりやすく書き直すと以下のようになります。

List<String> lists = ...
lists.forEach(new Consumer<String>() {
@Override
    public void accept(String t) {
        System.out.println(t);
    }
});

forEachの引数にConsumerが使われています。
forEachとラムダ式を組み合わせた構文は以下のようになります。

List型変数.forEach( 引数 -> 処理 );

サンプルコード ラムダ式前

ラムダ式の前に、ラムダ式が使われる前はforEach()の代わりに拡張for文が使われていました。

import java.util.ArrayList;
public class LambdaArrayTest {
   public static void main(String[] args) {
      ArrayList<Integer> numbers = new ArrayList<Integer>();
      numbers.add(1);
      numbers.add(2);
      numbers.add(3);
      numbers.add(4);
      for(int n : numbers ){
         System.out.println(n);
      }
   }
}

結果

1
2
3
4

拡張for文を使ってlistの中身を表示しています。Itaratorを使ってやる方法もありますが今後はラムダ式に変わっていくと言われています。

サンプルコード ラムダ式を使用

import java.util.ArrayList;
public class LambdaArrayTest {
   public static void main(String[] args) {
      ArrayList<Integer> numbers = new ArrayList<Integer>();
      numbers.add(1);
      numbers.add(2);
      numbers.add(3);
      numbers.add(4);
      numbers.forEach( (n) -> { System.out.println(n); } );
   }
}

結果

1
2
3
4

サンプルコード ラムダ式を使って偶数だけ表示する

import java.util.ArrayList;
public class LambdaArrayTest {
   public static void main(String[] args) {
      ArrayList<Integer> numbers = new ArrayList<Integer>();
      numbers.add(1);
      numbers.add(2);
      numbers.add(3);
      numbers.add(4);
      numbers.forEach(n -> { if (n%2 == 0) System.out.println(n); });
   }
}

結果

2
4

サンプルコード ラムダ式を使って小文字にする

import java.util.ArrayList;
public class LambdaArrayTest{
   public static void main(String[] args) {
      ArrayList<String> names = new ArrayList<String>();
      names.add("Tanaka");
      names.add("Kato");
      names.add("Sato");
      names.add("Nakamoto");
      names.forEach(name -> System.out.println(name.toLowerCase()));
   }
}

結果

tanaka
kato
sato
nakamoto

おわり

ラムダ式を使って書かなくても動くのでいいのですが、ラムダ式で書かなければ使用できないAPI(Stream API)がありとても便利です。また覚えてしまえば読みやすく感じるのだとか。
とはいえまだ現場では使っていないのかなと思っていましたが、この前職場でラムダ式を使えと大声で話しているチームがいたので使っているところもあるんだなと身が引き締まりました。

参考

https://www.tutorialspoint.com/how-to-implement-function-t-r-interface-with-lambda-expression-in-java
https://www.tutorialspoint.com/how-to-use-consumer-and-biconsumer-interfaces-in-lambda-expression-in-java
https://www.w3schools.com/java/java_lambda.asphttps://www.baeldung.com/foreach-java