関数型Javaの紹介

1概要

このチュートリアルでは、http://www.functionaljava.org[Functional Java]ライブラリの概要といくつかの例を紹介します。

2関数型Javaライブラリ

Functional Javaライブラリーは、Javaでの機能プログラミングを容易にすることを目的としたオープンソースのライブラリーです。このライブラリはhttps://en.wikipedia.org/wiki/Functional__programming[関数型プログラミング]で一般的に使用されている基本および高度なプログラミングの抽象化を多数提供しています。

ライブラリの機能の多くは F インタフェースを中心にしています。

  • この F インタフェースは、 A 型の入力を受け取り、 B 型の出力を返す関数をモデル化しています。** これらすべては、Java独自の型システムの上に構築されています。

3 Mavenの依存関係

まず、必要なhttps://search.maven.org/search?q=g:org.functionaljava[dependencies]を pom.xml ファイルに追加する必要があります。

<dependency>
    <groupId>org.functionaljava</groupId>
    <artifactId>functionaljava</artifactId>
    <version>4.8.1</version>
</dependency>
<dependency>
    <groupId>org.functionaljava</groupId>
    <artifactId>functionaljava-java8</artifactId>
    <version>4.8.1</version>
</dependency>
<dependency>
    <groupId>org.functionaljava</groupId>
    <artifactId>functionaljava-quickcheck</artifactId>
    <version>4.8.1</version>
</dependency>
<dependency>
    <groupId>org.functionaljava</groupId>
    <artifactId>functionaljava-java-core</artifactId>
    <version>4.8.1</version>
</dependency>

4関数を定義する

後で例で使用できる関数を作成することから始めましょう。

Functional Javaがなければ、基本的な乗算方法は次のようになります。

public static final Integer timesTwoRegular(Integer i) {
    return i **  2;
}

Functional Javaライブラリーを使用して、この機能をもう少しエレガントに定義できます。

public static final F<Integer, Integer> timesTwo = i -> i **  2;
  • 上記では、 Integer を入力として受け取り、その Integer の2倍を出力として返す F インターフェースの例を示しています。

これは Integer を入力とする基本関数の別の例ですが、この場合、入力が偶数か奇数かを示すために Boolean を返します。

public static final F<Integer, Boolean> isEven = i -> i % 2 == 0;

5機能を適用する

これで機能が整いましたので、それらをデータセットに適用しましょう。

Functional Javaライブラリーは、リスト、セット、配列、およびマップなどのデータを管理するための通常のタイプのセットを提供します。実現するための重要なことは、これらのデータ型は不変であるということです。

さらに、このライブラリは、必要に応じて、標準のJavaコレクションクラスとの間で変換するための** 便利な関数を提供します。

以下の例では、整数のリストを定義し、それに timesTwo 関数を適用します。同じ関数のインライン定義を使って map を呼び出すこともできます。もちろん、結果は同じになるはずです。

public void multiplyNumbers__givenIntList__returnTrue() {
    List<Integer> fList = List.list(1, 2, 3, 4);
    List<Integer> fList1 = fList.map(timesTwo);
    List<Integer> fList2 = fList.map(i -> i **  2);

    assertTrue(fList1.equals(fList2));
}

ご覧のとおり、 map は同じサイズのリストを返します。各要素の値は、関数が適用された入力リストの値です。入力リスト自体は変わりません。

これは私たちの isEven 関数を使った同様の例です:

public void calculateEvenNumbers__givenIntList__returnTrue() {
    List<Integer> fList = List.list(3, 4, 5, 6);
    List<Boolean> evenList = fList.map(isEven);
    List<Boolean> evenListTrueResult = List.list(false, true, false, true);

    assertTrue(evenList.equals(evenListTrueResult));
}
  • map メソッドはリストを返すので、他の関数をその出力に適用することができます。

public void applyMultipleFunctions__givenIntList__returnFalse() {
    List<Integer> fList = List.list(1, 2, 3, 4);
    List<Integer> fList1 = fList.map(timesTwo).map(plusOne);
    List<Integer> fList2 = fList.map(plusOne).map(timesTwo);

    assertFalse(fList1.equals(fList2));
}

上記のリストの出力は次のようになります。

List(3,5,7,9)
List(4,6,8,10)

6. 関数を使ったフィルタリング

関数型プログラミングでよく使われるもう1つの操作は、 入力を受け取り、いくつかの基準に基づいてデータを除外することです 。そしておそらくあなたがすでに推測したように、これらのフィルタリング基準は関数の形で提供されます。この関数は、データを出力に含める必要があるかどうかを示すブール値を返す必要があります。

それでは、 filter メソッドを使って入力配列から奇数を取り除くために、私たちの isEven 関数を使いましょう:

public void filterList__givenIntList__returnResult() {
    Array<Integer> array = Array.array(3, 4, 5, 6);
    Array<Integer> filteredArray = array.filter(isEven);
    Array<Integer> result = Array.array(4, 6);

    assertTrue(filteredArray.equals(result));
}

この例では、前の例で使用したように List の代わりに Array を使用し、関数が正常に機能していることがわかります。 ** 関数が抽象化され実行される方法のために、それらは入力と出力を集めるためにどの方法が使われたかを知る必要はありません。

この例では、独自の isEven 関数も使用しましたが、Functional Java独自の Integer クラスには、http://www.functionaljava.org/javadoc/4.8.1/functionaljava/fj/function/Integers.html[basicの標準関数もあります数値比較]。

7. 関数を使ったブール論理の適用

関数型プログラミングでは、「すべての要素が何らかの条件を満たす場合にのみこれを実行する」や「少なくとも1つの要素が何らかの条件を満たす場合にのみこれを実行する」などのロジックを頻繁に使用します。

  • Functional Javaライブラリは、http://www.functionaljava.org/javadoc/4.4/functionaljava/fj/data/Option.html#exists-fj.F-[ exists ]およびhttp://www.functionaljava.org/javadoc/4.4/functionaljava/から、このロジックへのショートカットを提供します。 ://www.functionaljava.org/javadoc/4.4/functionaljava/fj/data/Option.html#forall-fj.F-[ forall ]methods:**

public void checkForLowerCase__givenStringArray__returnResult() {
    Array<String> array = Array.array("Welcome", "To", "baeldung");
    assertTrue(array.exists(s -> List.fromString(s).forall(Characters.isLowerCase)));

    Array<String> array2 = Array.array("Welcome", "To", "Baeldung");
    assertFalse(array2.exists(s -> List.fromString(s).forall(Characters.isLowerCase)));

    assertFalse(array.forall(s -> List.fromString(s).forall(Characters.isLowerCase)));
}

上記の例では、入力として文字列の配列を使用しました。 fromString 関数を呼び出すと、配列の各文字列が文字のリストに変換されます。これらのリストのそれぞれに、 forall(Characters.isLowerCase) を適用しました。

ご想像のとおり、 Characters.isLowerCase は、文字が小文字の場合にtrueを返す関数です。したがって、 forall(Characters.isLowerCase) を文字のリストに適用すると、リスト全体が小文字で構成されている場合にのみ true が返されます。これは、元の文字列がすべて小文字であることを示します。

最初の2つのテストでは、少なくとも1つの文字列が小文字かどうかだけを知りたかったので exists を使用しました。 3番目のテストは forall を使用して、すべての文字列が小文字かどうかを検証しました。

8関数でオプションの値を処理する

コード内でオプションの値を処理するには、通常 == null または isNotBlank のチェックが必要です。 Java 8では、これらのチェックをよりエレガントに処理するための Optional クラスが提供されています。また、Functional Javaライブラリーは、そのhttp://www.functionaljava.org/javadoc/4.8.1/functionaljava/fjを通じて欠落データを適切に処理するための同様の構成を提供します。/data/Option.html[Option]クラス:

public void checkOptions__givenOptions__returnResult() {
    Option<Integer> n1 = Option.some(1);
    Option<Integer> n2 = Option.some(2);
    Option<Integer> n3 = Option.none();

    F<Integer, Option<Integer>> function = i -> i % 2 == 0 ? Option.some(i + 100) : Option.none();

    Option<Integer> result1 = n1.bind(function);
    Option<Integer> result2 = n2.bind(function);
    Option<Integer> result3 = n3.bind(function);

    assertEquals(Option.none(), result1);
    assertEquals(Option.some(102), result2);
    assertEquals(Option.none(), result3);
}

9関数を使って集合を減らす

最後に、集合を減らすための機能について見ていきます。 「セットを減らす」は、「1つの値にまとめる」と言うのにふさわしい方法です。

  • Functional Javaライブラリは、この機能を折りたたみ** と呼びます。

要素を折りたたむことが何を意味するのかを示すために関数を指定する必要があります。この例としては、配列またはリスト内の整数を追加する必要があることを示す Integers.add 関数があります。

折りたたみ時に関数が行うことに基づいて、右または左のどちらから折り畳みを開始したかによって結果が異なります。これが、Functional Javaライブラリーが両方のバージョンを提供している理由です。

public void foldLeft__givenArray__returnResult() {
    Array<Integer> intArray = Array.array(17, 44, 67, 2, 22, 80, 1, 27);

    int sumAll = intArray.foldLeft(Integers.add, 0);
    assertEquals(260, sumAll);

    int sumEven = intArray.filter(isEven).foldLeft(Integers.add, 0);
    assertEquals(148, sumEven);
}

最初の foldLeft は単純にすべての整数を足します。一方、2番目のものは最初にフィルタを適用し、次に残りの整数を追加します。

10結論

この記事は、Functional Javaライブラリーの簡単な紹介です。

いつものように、この記事の全ソースコードはhttps://github.com/eugenp/tutorials/tree/master/libraries/src/test/java/com/baeldung/fj[over on GitHub]にあります。