Java開発者のための関数型プログラミング、パート2

Javaコンテキストで関数型プログラミングを紹介するこの2部構成のチュートリアルにようこそ。Java開発者向けの関数型プログラミングパート1では、JavaScriptの例を使用して、純粋関数、高階関数、遅延評価、クロージャ、カリー化の5つの関数型プログラミング手法を開始しました。これらの例をJavaScriptで提示することで、Javaのより複雑な関数型プログラミング機能に踏み込むことなく、より単純な構文で手法に集中することができました。

パート2では、Java 8より前のJavaコードを使用してこれらの手法を再検討します。ご覧のとおり、このコードは機能しますが、書き込みや読み取りは簡単ではありません。また、Java8のJava言語に完全に統合された新しい関数型プログラミング機能についても紹介します。つまり、ラムダ、メソッド参照、関数型インターフェース、およびStreamsAPIです。

このチュートリアル全体を通して、パート1の例に戻って、JavaScriptとJavaの例を比較します。また、Java 8より前の例のいくつかを、ラムダやメソッド参照などの関数型言語機能で更新するとどうなるかがわかります。最後に、このチュートリアルには、機能的思考の練習に役立つように設計された実践的な演習が含まれています。これは、オブジェクト指向Javaコードの一部を同等の機能に変換することによって行います。

ダウンロードコードを取得するこのチュートリアルのサンプルアプリケーションのソースコードをダウンロードします。JavaWorld用にJeffFriesenによって作成されました。

Javaによる関数型プログラミング

多くの開発者はそれを認識していませんが、Java 8より前のJavaで関数型プログラムを作成することは可能でした。Javaでの関数型プログラミングの全体像を把握するために、Java8より前の関数型プログラミング機能を簡単に確認しましょう。これらを理解すれば、Java 8で導入された新機能(ラムダや関数型インターフェースなど)が関数型プログラミングに対するJavaのアプローチをどのように簡素化したかを理解できるでしょう。

関数型プログラミングに対するJavaのサポートの制限

Java 8で関数型プログラミングが改善されたとしても、Javaは依然として必須のオブジェクト指向プログラミング言語です。より機能的にする範囲タイプやその他の機能がありません。Javaは、すべての型に名前を付ける必要があるという規定である記名的型付けによっても妨げられています。これらの制限にもかかわらず、Javaの機能機能を採用する開発者は、より簡潔で再利用可能で読みやすいコードを記述できるというメリットがあります。

Java8より前の関数型プログラミング

匿名の内部クラスとインターフェースおよびクロージャは、古いバージョンのJavaで関数型プログラミングをサポートする3つの古い機能です。

  • 匿名の内部クラスを使用すると、機能(インターフェイスによって記述される)をメソッドに渡すことができます。
  • 関数型インターフェースは、関数を記述するインターフェースです。
  • クロージャを使用すると、外部スコープの変数にアクセスできます。

次のセクションでは、パート1で紹介した5つの手法を再検討しますが、Java構文を使用します。これらの機能的手法のそれぞれがJava8より前にどのように可能であったかがわかります。

Javaで純粋関数を書く

リスト1は、DaysInMonth匿名の内部クラスと関数型インターフェースを使用して記述されたサンプル・アプリケーションのソース・コードを示しています。このアプリケーションは、Java8よりずっと前にJavaで実現可能だった純粋関数の記述方法を示しています。

リスト1.Javaの純粋関数(DaysInMonth.java)

interface Function { R apply(T t); } public class DaysInMonth { public static void main(String[] args) { Function dim = new Function() { @Override public Integer apply(Integer month) { return new Integer[] { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 }[month]; } }; System.out.printf("April: %d%n", dim.apply(3)); System.out.printf("August: %d%n", dim.apply(7)); } }

Functionリスト1のジェネリック・インターフェースは、型の単一パラメーターと型Tの戻り値の型を持つ関数を記述していますRFunctionインタフェースは宣言しR apply(T t)、指定された引数にこの機能を適用する方法を。

このmain()メソッドは、Functionインターフェイスを実装する匿名の内部クラスをインスタンス化します。このapply()メソッドはボックスを解除しmonth、それを使用して月日整数の配列にインデックスを付けます。このインデックスの整数が返されます。(簡単にするためにうるう年は無視しています。)

main()次にapply()、4月と8月の日数を返すように呼び出すことにより、この関数を2回実行します。これらのカウントはその後印刷されます。

関数を作成することができました、そしてそれで純粋関数!純粋関数はその引数のみに依存し、外部状態には依存しないことを思い出してください。副作用はありません。

リスト1を次のようにコンパイルします。

javac DaysInMonth.java

結果のアプリケーションを次のように実行します。

java DaysInMonth

次の出力を確認する必要があります。

April: 30 August: 31

Javaで高階関数を書く

次に、ファーストクラス関数とも呼ばれる高階関数について説明します。ことを忘れないでください高階関数は、関数の引数を受け取り、および/または機能の結果を返します。Javaは、関数を、匿名の内部クラスで定義されているメソッドに関連付けます。このクラスのインスタンスは、高階関数として機能する別のJavaメソッドに渡されるか、別のJavaメソッドから返されます。次のファイル指向のコードフラグメントは、関数を高階関数に渡す方法を示しています。

File[] txtFiles = new File(".").listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getAbsolutePath().endsWith("txt"); } });

このコードフラグメントは、java.io.FileFilter機能インターフェイスに基づく関数をjava.io.FileクラスのFile[] listFiles(FileFilter filter)メソッドに渡し、txt拡張子が付いたファイルのみを返すように指示します。

リスト2は、Javaで高階関数を操作する別の方法を示しています。この場合、コードはコンパレータ関数をsort()昇順ソートの場合は高階関数に渡し、2番目のコンパレータ関数をsort()降順ソートの場合に渡します。

リスト2.Javaの高階関数(Sort.java)

import java.util.Comparator; public class Sort { public static void main(String[] args) { String[] innerplanets = { "Mercury", "Venus", "Earth", "Mars" }; dump(innerplanets); sort(innerplanets, new Comparator() { @Override public int compare(String e1, String e2) { return e1.compareTo(e2); } }); dump(innerplanets); sort(innerplanets, new Comparator() { @Override public int compare(String e1, String e2) { return e2.compareTo(e1); } }); dump(innerplanets); } static  void dump(T[] array) { for (T element: array) System.out.println(element); System.out.println(); } static  void sort(T[] array, Comparator cmp) { for (int pass = 0; pass 
    
      pass; i--) if (cmp.compare(array[i], array[pass]) < 0) swap(array, i, pass); } static void swap(T[] array, int i, int j) { T temp = array[i]; array[i] = array[j]; array[j] = temp; } }
    

リスト2は、java.util.Comparator任意であるが同一のタイプの2つのオブジェクトで比較を実行できる関数を記述した関数インターフェースをインポートします。

このコードの2つの重要な部分は、sort()メソッド(バブルソートアルゴリズムを実装する)とメソッドのsort()呼び出しmain()です。がsort()引数として-コンパレータ-機能には程遠いですが、それは機能を受け取る高階関数を示しています。compare()メソッドを呼び出すことにより、この関数を実行します。この関数の2つのインスタンスは、の2つのsort()呼び出しで渡されますmain()

リスト2を次のようにコンパイルします。

javac Sort.java

結果のアプリケーションを次のように実行します。

java Sort

次の出力を確認する必要があります。

Mercury Venus Earth Mars Earth Mars Mercury Venus Venus Mercury Mars Earth

Javaでの遅延評価

遅延評価は、Java 8にとって新しいものではない、もう1つの関数型プログラミング手法です。この手法は、値が必要になるまで式の評価を遅らせます。ほとんどの場合、Javaは変数にバインドされている式を熱心に評価します。Javaは、次の特定の構文の遅延評価をサポートしています。

  • ブール&&および||演算子。左側のオペランドがfalse(&&)またはtrue(||)の場合、右側のオペランドを評価しません。
  • ?:その後、ブール式を評価し、オペレータは、ブール式の真/偽の値に基づいて(互換性のあるタイプの)二つの代替の表現の一つだけ評価されます。

Functional programming encourages expression-oriented programming, so you'll want to avoid using statements as much as possible. For example, suppose you want to replace Java's if-else statement with an ifThenElse() method. Listing 3 shows a first attempt.

Listing 3. An example of eager evaluation in Java (EagerEval.java)

public class EagerEval { public static void main(String[] args) { System.out.printf("%d%n", ifThenElse(true, square(4), cube(4))); System.out.printf("%d%n", ifThenElse(false, square(4), cube(4))); } static int cube(int x) { System.out.println("in cube"); return x * x * x; } static int ifThenElse(boolean predicate, int onTrue, int onFalse) { return (predicate) ? onTrue : onFalse; } static int square(int x) { System.out.println("in square"); return x * x; } }

Listing 3 defines an ifThenElse() method that takes a Boolean predicate and a pair of integers, returning the onTrue integer when the predicate is true and the onFalse integer otherwise.

Listing 3 also defines cube() and square() methods. Respectively, these methods cube and square an integer and return the result.

The main() method invokes ifThenElse(true, square(4), cube(4)), which should invoke only square(4), followed by ifThenElse(false, square(4), cube(4)), which should invoke only cube(4).

Compile Listing 3 as follows:

javac EagerEval.java

Run the resulting application as follows:

java EagerEval

You should observe the following output:

in square in cube 16 in square in cube 64

The output shows that each ifThenElse() call results in both methods executing, irrespective of the Boolean expression. We cannot leverage the ?: operator's laziness because Java eagerly evaluates the method's arguments.

Although there's no way to avoid eager evaluation of method arguments, we can still take advantage of ?:'s lazy evaluation to ensure that only square() or cube() is called. Listing 4 shows how.

Listing 4. An example of lazy evaluation in Java (LazyEval.java)

interface Function { R apply(T t); } public class LazyEval { public static void main(String[] args) { Function square = new Function() { { System.out.println("SQUARE"); } @Override public Integer apply(Integer t) { System.out.println("in square"); return t * t; } }; Function cube = new Function() { { System.out.println("CUBE"); } @Override public Integer apply(Integer t) { System.out.println("in cube"); return t * t * t; } }; System.out.printf("%d%n", ifThenElse(true, square, cube, 4)); System.out.printf("%d%n", ifThenElse(false, square, cube, 4)); } static  R ifThenElse(boolean predicate, Function onTrue, Function onFalse, T t) { return (predicate ? onTrue.apply(t) : onFalse.apply(t)); } }

Listing 4 turns ifThenElse() into a higher-order function by declaring this method to receive a pair of Function arguments. Although these arguments are eagerly evaluated when passed to ifThenElse(), the ?: operator causes only one of these functions to execute (via apply()). You can see both eager and lazy evaluation at work when you compile and run the application.

Compile Listing 4 as follows:

javac LazyEval.java

Run the resulting application as follows:

java LazyEval

You should observe the following output:

SQUARE CUBE in square 16 in cube 64

A lazy iterator and more

Neal Ford's "Laziness, Part 1: Exploring lazy evaluation in Java" provides more insight into lazy evaluation. The author presents a Java-based lazy iterator along with a couple of lazy-oriented Java frameworks.

Closures in Java

An anonymous inner class instance is associated with a closure. Outer scope variables must be declared final or (starting in Java 8) effectively final (meaning unmodified after initialization) in order to be accessible. Consider Listing 5.

Listing 5. An example of closures in Java (PartialAdd.java)

interface Function { R apply(T t); } public class PartialAdd { Function add(final int x) { Function partialAdd = new Function() { @Override public Integer apply(Integer y) { return y + x; } }; return partialAdd; } public static void main(String[] args) { PartialAdd pa = new PartialAdd(); Function add10 = pa.add(10); Function add20 = pa.add(20); System.out.println(add10.apply(5)); System.out.println(add20.apply(5)); } }

Listing 5 is the Java equivalent of the closure I previously presented in JavaScript (see Part 1, Listing 8). This code declares an add() higher-order function that returns a function for performing partial application of the add() function. The apply() method accesses variable x in the outer scope of add(), which must be declared final prior to Java 8. The code behaves pretty much the same as the JavaScript equivalent.

Compile Listing 5 as follows:

javac PartialAdd.java

Run the resulting application as follows:

java PartialAdd

You should observe the following output:

15 25

Currying in Java

You might have noticed that the PartialAdd in Listing 5 demonstrates more than just closures. It also demonstrates currying, which is a way to translate a multi-argument function's evaluation into the evaluation of an equivalent sequence of single-argument functions. Both pa.add(10) and pa.add(20) in Listing 5 return a closure that records an operand (10 or 20, respectively) and a function that performs the addition--the second operand (5) is passed via add10.apply(5) or add20.apply(5).

Currying lets us evaluate function arguments one at a time, producing a new function with one less argument on each step. For example, in the PartialAdd application, we are currying the following function:

f(x, y) = x + y

We could apply both arguments at the same time, yielding the following:

f(10, 5) = 10 + 5

However, with currying, we apply only the first argument, yielding this:

f(10, y) = g(y) = 10 + y

We now have a single function, g, that takes only a single argument. This is the function that will be evaluated when we call the apply() method.

Partial application, not partial addition

The name PartialAdd stands for partial application of the add() function. It doesn't stand for partial addition. Currying is about performing partial application of a function. It's not about performing partial calculations.

You might be confused by my use of the phrase "partial application," especially because I stated in Part 1 that currying isn't the same as partial application, which is the process of fixing a number of arguments to a function, producing another function of smaller arity. With partial application, you can produce functions with more than one argument, but with currying, each function must have exactly one argument.

Listing 5 presents a small example of Java-based currying prior to Java 8. Now consider the CurriedCalc application in Listing 6.

Listing 6. Currying in Java code (CurriedCalc.java)

interface Function { R apply(T t); } public class CurriedCalc { public static void main(String[] args) { System.out.println(calc(1).apply(2).apply(3).apply(4)); } static Function
    
     > calc(final Integer a) { return new Function
     
      >() { @Override public Function
      
        apply(final Integer b) { return new Function
       
        () { @Override public Function apply(final Integer c) { return new Function() { @Override public Integer apply(Integer d) { return (a + b) * (c + d); } }; } }; } }; } }
       
      
     
    

Listing 6 uses currying to evaluate the function f(a, b, c, d) = (a + b) * (c + d). Given expression calc(1).apply(2).apply(3).apply(4), this function is curried as follows:

  1. f(1, b, c, d) = g(b, c, d) = (1 + b) * (c + d)
  2. g(2, c, d) = h(c, d) = (1 + 2) * (c + d)
  3. h(3, d) = i(d) = (1 + 2) * (3 + d)
  4. i(4) = (1 + 2) * (3 + 4)

Compile Listing 6:

javac CurriedCalc.java

Run the resulting application:

java CurriedCalc

You should observe the following output:

21

Because currying is about performing partial application of a function, it doesn't matter in what order the arguments are applied. For example, instead of passing a to calc() and d to the most-nested apply() method (which performs the calculation), we could reverse these parameter names. This would result in d c b a instead of a b c d, but it would still achieve the same result of 21. (The source code for this tutorial includes the alternative version of CurriedCalc.)

Functional programming in Java 8

Functional programming before Java 8 isn't pretty. Too much code is required to create, pass a function to, and/or return a function from a first-class function. Prior versions of Java also lack predefined functional interfaces and first-class functions such as filter and map.

Java 8 reduces verbosity largely by introducing lambdas and method references to the Java language. It also offers predefined functional interfaces, and it makes filter, map, reduce, and other reusable first-class functions available via the Streams API.

We'll look at these improvements together in the next sections.

Writing lambdas in Java code

A lambda is an expression that describes a function by denoting an implementation of a functional interface. Here's an example:

() -> System.out.println("my first lambda")

From left to right, () identifies the lambda's formal parameter list (there are no parameters), -> signifies a lambda expression, and System.out.println("my first lambda") is the lambda's body (the code to be executed).

A lambda has a type, which is any functional interface for which the lambda is an implementation. One such type is java.lang.Runnable, because Runnable's void run() method also has an empty formal parameter list:

Runnable r = () -> System.out.println("my first lambda");

You can pass the lambda anywhere that a Runnable argument is required; for example, the Thread(Runnable r) constructor. Assuming that the previous assignment has occurred, you could pass r to this constructor, as follows:

new Thread(r);

Alternatively, you could pass the lambda directly to the constructor:

new Thread(() -> System.out.println("my first lambda"));

This is definitely more compact than the pre-Java 8 version:

new Thread(new Runnable() { @Override public void run() { System.out.println("my first lambda"); } });

A lambda-based file filter

My previous demonstration of higher-order functions presented a file filter based on an anonymous inner class. Here's the lambda-based equivalent:

File[] txtFiles = new File(".").listFiles(p -> p.getAbsolutePath().endsWith("txt"));

Return statements in lambda expressions

In Part 1, I mentioned that functional programming languages work with expressions as opposed to statements. Prior to Java 8, you could largely eliminate statements in functional programming, but you couldn't eliminate the return statement.

The above code fragment shows that a lambda doesn't require a return statement to return a value (a Boolean true/false value, in this case): you just specify the expression without return [and add] a semicolon. However, for multi-statement lambdas, you'll still need the return statement. In these cases you must place the lambda's body between braces as follows (don't forget the semicolon to terminate the statement):

File[] txtFiles = new File(".").listFiles(p -> { return p.getAbsolutePath().endsWith("txt"); });

Lambdas with functional interfaces

I have two more examples to illustrate the conciseness of lambdas. First, let's revisit the main() method from the Sort application shown in Listing 2:

public static void main(String[] args) { String[] innerplanets = { "Mercury", "Venus", "Earth", "Mars" }; dump(innerplanets); sort(innerplanets, (e1, e2) -> e1.compareTo(e2)); dump(innerplanets); sort(innerplanets, (e1, e2) -> e2.compareTo(e1)); dump(innerplanets); }

We can also update the calc() method from the CurriedCalc application shown in Listing 6:

static Function
    
     > calc(Integer a) { return b -> c -> d -> (a + b) * (c + d); }
    

Runnable, FileFilter, and Comparator are examples of functional interfaces, which describe functions. Java 8 formalized this concept by requiring a functional interface to be annotated with the java.lang.FunctionalInterface annotation type, as in @FunctionalInterface. An interface that is annotated with this type must declare exactly one abstract method.

You can use Java's pre-defined functional interfaces (discussed later), or you can easily specify your own, as follows:

@FunctionalInterface interface Function { R apply(T t); }

You might then use this functional interface as shown here:

public static void main(String[] args) { System.out.println(getValue(t -> (int) (Math.random() * t), 10)); System.out.println(getValue(x -> x * x, 20)); } static Integer getValue(Function f, int x) { return f.apply(x); }

New to lambdas?

If you're new to lambdas, you might need more background in order to understand these examples. In that case, check out my further introduction to lambdas and functional interfaces in "Get started with lambda expressions in Java." You'll also find numerous helpful blog posts on this topic. One example is "Functional programming with Java 8 functions," in which author Edwin Dalorzo shows how to use lambda expressions and anonymous functions in Java 8.

Architecture of a lambda

Every lambda is ultimately an instance of some class that's generated behind the scenes. Explore the following resources to learn more about lambda architecture:

  • "How lambdas and anonymous inner classes work" (Martin Farrell, DZone)
  • "Lambdas in Java: A peek under the hood" (Brian Goetz, GOTO)
  • "Why are Java 8 lambdas invoked using invokedynamic?" (Stack Overflow)

I think you'll find Java Language Architect Brian Goetz's video presentation of what's going on under the hood with lambdas especially fascinating.

Method references in Java

Some lambdas only invoke an existing method. For example, the following lambda invokes System.out's void println(s) method on the lambda's single argument:

(String s) -> System.out.println(s)

The lambda presents (String s) as its formal parameter list and a code body whose System.out.println(s) expression prints s's value to the standard output stream.

To save keystrokes, you could replace the lambda with a method reference, which is a compact reference to an existing method. For example, you could replace the previous code fragment with the following:

System.out::println

Here, :: signifies that System.out's void println(String s) method is being referenced. The method reference results in much shorter code than we achieved with the previous lambda.

A method reference for Sort

I previously showed a lambda version of the Sort application from Listing 2. Here is that same code written with a method reference instead:

public static void main(String[] args) { String[] innerplanets = { "Mercury", "Venus", "Earth", "Mars" }; dump(innerplanets); sort(innerplanets, String::compareTo); dump(innerplanets); sort(innerplanets, Comparator.comparing(String::toString).reversed()); dump(innerplanets); }

The String::compareTo method reference version is shorter than the lambda version of (e1, e2) -> e1.compareTo(e2). Note, however, that a longer expression is required to create an equivalent reverse-order sort, which also includes a method reference: String::toString. Instead of specifying String::toString, I could have specified the equivalent s -> s.toString() lambda.

More about method references

There's much more to method references than I could cover in a limited space. To learn more, check out my introduction to writing method references for static methods, non-static methods, and constructors in "Get started with method references in Java."

Predefined functional interfaces

Java 8 introduced predefined functional interfaces (java.util.function) so that developers don't have create our own functional interfaces for common tasks. Here are a few examples:

  • The Consumer functional interface represents an operation that accepts a single input argument and returns no result. Its void accept(T t) method performs this operation on argument t.
  • The Function functional interface represents a function that accepts one argument and returns a result. Its R apply(T t) method applies this function to argument t and returns the result.
  • The Predicate functional interface represents a predicate (Boolean-valued function) of one argument. Its boolean test(T t) method evaluates this predicate on argument t and returns true or false.
  • The Supplier functional interface represents a supplier of results. Its T get() method receives no argument(s) but returns a result.

The DaysInMonth application in Listing 1 revealed a complete Function interface. Starting with Java 8, you can remove this interface and import the identical predefined Function interface.

More about predefined functional interfaces

"Get started with lambda expressions in Java" provides examples of the Consumer and Predicate functional interfaces. Check out the blog post "Java 8 -- Lazy argument evaluation" to discover an interesting use for Supplier.

Additionally, while the predefined functional interfaces are useful, they also present some issues. Blogger Pierre-Yves Saumont explains why.

Functional APIs: Streams

Java 8 introduced the Streams API to facilitate sequential and parallel processing of data items. This API is based on streams, where a stream is a sequence of elements originating from a source and supporting sequential and parallel aggregate operations. A source stores elements (such as a collection) or generates elements (such as a random number generator). An aggregate is a result calculated from multiple input values.

A stream supports intermediate and terminal operations. An intermediate operation returns a new stream, whereas a terminal operation consumes the stream. Operations are connected into a pipeline (via method chaining). The pipeline starts with a source, which is followed by zero or more intermediate operations, and ends with a terminal operation.

Streams is an example of a functional API. It offers filter, map, reduce, and other reusable first-class functions. I briefly demonstrated this API in the Employees application shown in Part 1, Listing 1. Listing 7 offers another example.

Listing 7. Functional programming with Streams (StreamFP.java)

import java.util.Random; import java.util.stream.IntStream; public class StreamFP { public static void main(String[] args) { new Random().ints(0, 11).limit(10).filter(x -> x % 2 == 0) .forEach(System.out::println); System.out.println(); String[] cities = { "New York", "London", "Paris", "Berlin", "BrasÌlia", "Tokyo", "Beijing", "Jerusalem", "Cairo", "Riyadh", "Moscow" }; IntStream.range(0, 11).mapToObj(i -> cities[i]) .forEach(System.out::println); System.out.println(); System.out.println(IntStream.range(0, 10).reduce(0, (x, y) -> x + y)); System.out.println(IntStream.range(0, 10).reduce(0, Integer::sum)); } }

The main() method first creates a stream of pseudorandom integers starting at 0 and ending at 10. The stream is limited to exactly 10 integers. The filter() first-class function receives a lambda as its predicate argument. The predicate removes odd integers from the stream. Finally, the forEach() first-class function prints each even integer to the standard output via the System.out::println method reference.

The main() method next creates an integer stream that produces a sequential range of integers starting at 0 and ending at 10. The mapToObj() first-class function receives a lambda that maps an integer to the equivalent string at the integer index in the cities array. The city name is then sent to the standard output via the forEach() first-class function and its System.out::println method reference.

Lastly, main() demonstrates the reduce() first-class function. An integer stream that produces the same range of integers as in the previous example is reduced to a sum of their values, which is subsequently output.

Identifying the intermediate and terminal operations

Each of limit(), filter(), range(), and mapToObj() are intermediate operations, whereas forEach() and reduce() are terminal operations.

Compile Listing 7 as follows:

javac StreamFP.java

Run the resulting application as follows:

java StreamFP

I observed the following output from one run:

0 2 10 6 0 8 10 New York London Paris Berlin BrasÌlia Tokyo Beijing Jerusalem Cairo Riyadh Moscow 45 45

You might have expected 10 instead of 7 pseudorandom even integers (ranging from 0 through 10, thanks to range(0, 11)) to appear at the beginning of the output. After all, limit(10) seems to indicate that 10 integers will be output. However, this isn't the case. Although the limit(10) call results in a stream of exactly 10 integers, the filter(x -> x % 2 == 0) call results in odd integers being removed from the stream.

More about Streams

If you're unfamiliar with Streams, check out my tutorial introducing Java SE 8's new Streams API for more about this functional API.

In conclusion

Many Java developers won't pursue pure functional programming in a language like Haskell because it differs so greatly from the familiar imperative, object-oriented paradigm. Java 8's functional programming capabilities are designed to bridge that gap, enabling Java developers to write code that's easier to understand, maintain, and test. Functional code is also more reusable and more suitable for parallel processing in Java. With all of these incentives, there's really no reason not to incorporate Java's functional programming options into your Java code.

Write a functional Bubble Sort application

関数型思考は、ニールフォードによって造られた用語であり、オブジェクト指向パラダイムから関数型プログラミングパラダイムへの認知的シフトを指します。このチュートリアルで見たように、関数型手法を使用してオブジェクト指向コードを書き直すことにより、関数型プログラミングについて多くを学ぶことができます。

リスト2のSortアプリケーションに戻って、これまでに学んだことを締めくくります。この簡単なヒントでは、最初にJava 8より前の手法を使用し、次にJava8の手法を使用して純粋に機能的なバブルソート作成する方法を示します。機能的な機能。

このストーリー「Java開発者のための関数型プログラミング、パート2」は、もともとJavaWorldによって公開されました。