English | 简体中文 | 繁體中文 | Русский язык | Français | Español | Português | Deutsch | 日本語 | 한국어 | Italiano | بالعربية

Java 基礎教程

Java フローコントロール

Java 配列

Java 面向オブジェクト(I)

Java 面向オブジェクト(II)

Java 面向オブジェクト(III)

Java 異常処理

Java リスト(List)

Java Queue(キュー)

Java Mapコレクション

Java Setコレクション

Javaの入出力(I/O)

Java Reader/Writer

Javaの他のトピック

Java ラムダ式

この記事では、Javaのlambda式と、lambda式と機能インターフェース、汎型機能インターフェース、およびストリームAPIの使用法について例を通じて学びます。

lambda式はJavaで 8初めて導入された。主な目的は言語の表現力を向上させることです。

しかし、lambdaを学ぶ前に、まず機能インターフェースについて理解する必要があります。

機能インターフェースとは何ですか?

Javaインターフェースが1つの抽象メソッドのみを含む場合、それを機能インターフェースと呼びます。この1つのメソッドだけがインターフェースの期待される用途を指定します。

例えば、java.langパッケージのRunnableインターフェースは、run()という一つのメソッドしか持っていないため、機能インターフェースです。

示例1:Javaで機能インターフェースを定義

import java.lang.FunctionalInterface;
@FunctionalInterface
public interface MyInterface{
    //単一抽象メソッド
    double getValue();
}

上記の例では、インターフェースMyInterfaceには抽象メソッドgetValue()が一つだけあります。したがって、それは機能インターフェースです。

ここでは、注解@FunctionalInterfaceを使用しています。この注解は、Javaコンパイラにそのインターフェースが機能インターフェースであることを強制します。したがって、複数の抽象メソッドは許可されません。ただし、これは強制ではありません。

Java 7中、機能インターフェースは単一抽象メソッド(SAM)タイプ。Java 7中、SAMタイプは通常匿名クラスで実現されます。

示例2:Javaで匿名クラスを使用してSAMを実現

public class FunctionInterfaceTest {
    public static void main(String[] args) {
        //匿名クラス
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("私は先ほどRunnable機能インターフェースを実装しました。");
            }
        }).start();
    }
}

出力

私は先ほどRunnable機能インターフェースを実装しました。

ここでは、匿名クラスをメソッドに渡すことができます。これはJavaで 7コードを少なくするプログラムを書くことができます。しかし、文法は難しく、多くの追加のコード行が必要です。

Java 8SAM機能をさらに拡張しました。機能インターフェースが一つのメソッドしか持っていないことを知っているので、そのメソッドの名前を定義する必要はありません。ラムダ式でこれが可能です。

ラムダ式の概要

ラムダ式は匿名または名前のないメソッドです。ラムダ式は独立して実行できません。代わりに、機能インターフェースで定義されたメソッドを実現するために使用されます。

Javaでラムダ式をどう定義するか?

これがJavaでラムダ式を定義する方法です。

(パラメータ リスト) -> ラムダ ボディ

新しい演算子(->)が矢印演算子またはラムダ演算子と呼ばれます。いくつかの例を探ってみましょう。

仮にこのようなメソッドを持っていると仮定して:

double getPiValue() {
    return 3.1415;
}

このメソッドは以下のようにlambda式を使って書くことができます:

() -> 3.1415

ここでは、このメソッドにはパラメータがありません。したがって、演算子の左側には空のパラメータが含まれており、右側にはlambda主体が含まれ、lambda式の操作を指定します。この場合、値を返します3.1415。

Lambda主体のタイプ

Javaでは、lambda主体には2種類あります。

1.単一行式の主体

() -> System.out.println("Lambdas are great");

このようなタイプのlambda主体は表現主体と呼ばれます。

2.コードブロックで構成された主体。

() -> {
    double pi= 3.1415;
    return pi;
});

このようなタイプのlambda体はブロック体と呼ばれます。ブロック体はlambda体に複数の文を含めることを許可し、これらの文は括弧内に含まれ、括弧の後にセミコロンを付けます。

注意:ブロック体には常にreturn文を含める必要がありますが、単一行式の主体にはreturn文は必要ありません。

示例3:Lambda式

Javaプログラムを書いてみましょう。そのプログラムはlambda式を使ってPiの値を返します。

前述したように、lambda式は独立して実行されません。代わりに、機能インターフェースで定義された抽象メソッドの実装として機能します。

したがって、まず機能インターフェースを定義する必要があります。

import java.lang.FunctionalInterface;
//これは機能インターフェースです
@FunctionalInterface
interface MyInterface{
    // 抽象方法
    double getPiValue();
}
public class Main {
    public static void main(String[] args) {
    //声明对MyInterface的引用
    MyInterface ref;
    
    // lambda式
    ref=() -> 3.1415;
    
    System.out.println("Pi=" + ref.getPiValue());
    } 
}

出力

Pi= 3.1415

上記の例では、

  • MyInterfaceという名前の機能インターフェースを作成しました。getPiValue()という名前の抽象メソッドを含んでいます

  • Mainクラス内で、MyInterfaceのリファレンスを宣言しています。注意していただきたいのは、インターフェースのリファレンスを宣言できますが、インターフェースをインスタンス化することはできません。その理由は、
     

    //エラーが発生します
    MyInterface ref=new myInterface();
    // これは有効です
    MyInterface ref;
  • それから、リファレンスにlambda式を割り当てます。

    ref=() -> 3.1415;
  • 最後に、referenceインターフェースを使ってgetPiValue()メソッドを呼び出します。

    System.out.println("Pi=" + ref.getPiValue());

パラメータを持つLambda式

これまでに至るまで、パラメータを持たないlambda式を生成してきました。しかし、メソッドと同様に、lambda式もパラメータを持つことができます。例えば、

(n) -> (n%2)=0

在此,括号内的变量n是传递给lambda表达式的参数。Lambda主体接受参数并检查其是偶数还是奇数。

示例4:将lambda表达式与参数一起使用

@FunctionalInterface
interface MyInterface {
    //抽象方法
    String reverse(String n);
}
public class Main {
    public static void main(String[] args) {
                //声明对MyInterface的引用
                //将lambda表达式分配给引用
        MyInterface ref = (str) -> {
            String result = "";
            for (int i = str.length()-1; i >= 0 ; i--){
                result += str.charAt(i);
            }
            
            return result;
        });
        //调用接口的方法
        System.out.println("Lambda reversed = ") + ref.reverse("Lambda"));
    }
}

出力

Lambda reversed = adbmaL

泛型功能接口

到目前为止,我们已经使用了仅接受一种类型的值的功能接口。例如,

@FunctionalInterface
interface MyInterface {
    String reverseString(String n);
}

上面的功能接口仅接受String并返回String。但是,我们可以使功能接口通用,以便接受任何数据类型。如果不熟悉泛型,请访问Java泛型

示例5:泛型功能接口和Lambda表达式

// GenericInterface.java
@FunctionalInterface
interface GenericInterface<T> {
    // 泛型方法
    T func(T t);
}
// GenericLambda.java
public class Main {
    public static void main(String[] args) {
                //声明对GenericInterface的引用
                // GenericInterface对String数据进行操作
                //为其分配一个lambda表达式
        GenericInterface<String> reverse = (str) -> {
            String result = "";
            for (int i = str.length()-1; i >= 0 ; i--)
            result += str.charAt(i);
            return result;
        });
        System.out.println("Lambda reversed = ") + reverse.func("Lambda"));
                //声明对GenericInterface的另一个引用
                // GenericInterface对整数数据进行操作
                //为其分配一个lambda表达式
        GenericInterface<Integer> factorial = (n) -> {
            int result = 1;
            for (int i = 1; i <= n; i++)
            result = i * result;
            return result;
        });
        System.out.println("5の階乗 = " + factorial.func(5));
    }
}

出力

Lambda reversed = adbmaL
5の階乗 = 120

上記の例では、GenericInterfaceという名前の泛型機能インターフェースを作成しました。それはfunc()という名前の泛型メソッドを含んでいます。

クラス内で:

  • GenericInterface<String> reverse - このインターフェースへの参照を作成します。今や、このインターフェースはString型のデータを処理できます。

  • GenericInterface<Integer> factorial -このインターフェースへの参照を作成します。この場合、インターフェースはInteger型のデータに対して操作を行います。

Lambda式とStream API

新しいjava.util.streamパッケージがJDKに追加されました8で、Java開発者が検索、フィルタリング、マッピング、減少などの操作を実行したり、リストなどのコレクションを操作したりすることができます。

例えば、データストリーム(例では文字列リスト)があり、各文字列が国名と/地域の組み合わせ。今や、このデータストリームを処理し、ネパールからの位置を取得できます。

そのため、Stream APIとLambda式を組み合わせて、ストリームに対して一括操作を実行できます。

示例6:lambdaとStream APIの組み合わせ使用のデモ

import java.util.ArrayList;
import java.util.List;
public class StreamMain {
    //使用ArrayList创建一个列表对象
    static List<String> places = new ArrayList<>();
    //准备我们的数据
    public static List getPlaces(){
        //地点和国家被添加到列表中
        places.add("Nepal, Kathmandu");
        places.add("Nepal, Pokhara");
        places.add("India, Delhi");
        places.add("USA, New York");
        places.add("Africa, Nigeria");
        return places;
    }
    public static void main(String[] args) {
        List<String> myPlaces = getPlaces();
        System.out.println("ネパールからの場所:");
        
        myPlaces.stream()
                .filter((p -> p.startsWith("Nepal"))
                .map((p -> p.toUpperCase())
                .sorted()
                .forEach((p -> System.out.println(p));
    }
}

出力

ネパールからの場所:
NEPAL, KATHMANDU
NEPAL, POKHARA

上記の例では、以下の文句に注意してください:

myPlaces.stream()
        .filter((p -> p.startsWith("Nepal"))
        .map((p -> p.toUpperCase())
        .sorted()
        .forEach((p -> System.out.println(p));

ここでは、Stream APIのfilter()、map()、forEach()などのメソッドを使用しています。これらのメソッドは、ラムダ式を入力として受け取ります。

上記の文法に基づいて、独自の表現を定義することができます。上記の例のように、これによりコード行数を大幅に減少させることができます。