자바 개발자를위한 함수형 프로그래밍, Part 2

Java 컨텍스트에서 함수형 프로그래밍을 소개하는이 두 부분으로 구성된 자습서에 다시 오신 것을 환영합니다. Java 개발자를위한 함수형 프로그래밍, Part 1에서는 JavaScript 예제를 사용하여 순수 함수, 고차 함수, 지연 평가, 클로저 및 커링의 5 가지 함수 프로그래밍 기술을 시작했습니다. 이러한 예제를 JavaScript로 제시하면 Java의 더 복잡한 기능적 프로그래밍 기능을 사용하지 않고도 더 간단한 구문의 기술에 집중할 수있었습니다.

2 부에서는 Java 8 이전의 Java 코드를 사용하여 이러한 기술을 다시 살펴 보겠습니다. 보시다시피이 코드는 작동하지만 작성하거나 읽기가 쉽지 않습니다. 또한 Java 8의 Java 언어에 완전히 통합 된 새로운 기능 프로그래밍 기능을 소개합니다. 즉, 람다, 메서드 참조, 기능 인터페이스 및 Streams API입니다.

이 튜토리얼에서는 Part 1의 예제를 다시 살펴보고 JavaScript와 Java 예제가 어떻게 비교되는지 살펴볼 것입니다. 또한 람다 및 메서드 참조와 같은 기능적 언어 기능으로 Java 8 이전 예제 중 일부를 업데이트하면 어떻게되는지 확인할 수 있습니다. 마지막으로,이 튜토리얼에는 기능적 사고연습 하는 데 도움이되도록 설계된 실습 연습이 포함되어 있습니다.이 연습 은 객체 지향 Java 코드를 기능적으로 동등한 것으로 변환하여 수행 할 것입니다.

다운로드 코드 받기이 자습서에서 예제 응용 프로그램의 소스 코드를 다운로드합니다. JavaWorld를 위해 Jeff Friesen이 만들었습니다.

Java를 사용한 함수형 프로그래밍

많은 개발자들이 그것을 깨닫지 못했지만, Java 8 이전에 Java로 기능성 프로그램을 작성할 수있었습니다. Java에서 기능적 프로그래밍을 잘 이해하기 위해 Java 8 이전의 기능적 프로그래밍 기능을 빠르게 검토해 보겠습니다. 이러한 기능을 모두 사용하면 Java 8에 도입 된 새로운 기능 (예 : 람다 및 기능적 인터페이스)이 기능 프로그래밍에 대한 Java의 접근 방식을 단순화 한 방법에 대해 더 감사 할 것입니다.

함수형 프로그래밍에 대한 Java 지원의 한계

Java 8의 기능적 프로그래밍 향상에도 불구하고 Java는 명령형 객체 지향 프로그래밍 언어로 남아 있습니다. 범위 유형 및 기능을 향상시키는 기타 기능이 없습니다. Java는 또한 모든 유형에 이름이 있어야한다는 규정 인 명목 입력에 의해 방해를받습니다. 이러한 제한에도 불구하고 Java의 기능적 기능을 수용하는 개발자는 더 간결하고 재사용 가능하며 읽기 쉬운 코드를 작성할 수 있다는 이점이 있습니다.

Java 8 이전의 기능적 프로그래밍

인터페이스 및 클로저와 함께 익명 내부 클래스는 이전 버전의 Java에서 기능 프로그래밍을 지원하는 세 가지 이전 기능입니다.

  • 익명 내부 클래스 를 사용하면 기능 (인터페이스로 설명)을 메서드에 전달할 수 있습니다.
  • 기능 인터페이스기능 을 설명하는 인터페이스입니다.
  • 클로저 를 사용하면 외부 범위의 변수에 액세스 할 수 있습니다.

다음 섹션에서는 Part 1에서 소개 한 5 가지 기술을 다시 살펴 보지만 Java 구문을 사용합니다. Java 8 이전에는 이러한 각 기능 기술이 어떻게 가능했는지 알 수 있습니다.

자바로 순수 함수 작성

목록 1 DaysInMonth은 익명의 내부 클래스와 기능적 인터페이스를 사용하여 작성된 예제 애플리케이션에 대한 소스 코드를 보여줍니다 . 이 애플리케이션은 Java 8 이전에 Java에서 달성 할 수 있었던 순수 함수를 작성하는 방법을 보여줍니다.

Listing 1. 자바의 순수 함수 (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의 리턴 유형이 있는 함수를 설명합니다 R. Function인터페이스는 선언 R apply(T t)주어진 인수에이 기능을 적용 방법.

main()메서드는 Function인터페이스 를 구현하는 익명 내부 클래스를 인스턴스화합니다 . 이 apply()메서드는 박스를 풀고 month이를 사용하여 일수 정수 배열을 인덱싱합니다. 이 인덱스의 정수가 반환됩니다. (간단 함을 위해 윤년을 무시하고 있습니다.)

main()next apply()는 4 월과 8 월의 일 수를 반환하도록 호출하여이 함수를 두 번 실행합니다 . 이러한 카운트는 이후에 인쇄됩니다.

우리는 함수를 만들 수 있었고, 거기에서 순수한 함수를 만들었습니다! 리콜 • 그래도 순수 기능 만을 인수없이 외부 상태에 따라 달라집니다. 부작용이 없습니다.

다음과 같이 목록 1을 컴파일하십시오.

javac DaysInMonth.java

결과 애플리케이션을 다음과 같이 실행하십시오.

java DaysInMonth

다음 출력을 관찰해야합니다.

April: 30 August: 31

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()오름차순 정렬을 위해 상위 함수에 비교 함수를 전달 sort()하고 내림차순 정렬 을 위해 두 번째 비교 함수를 전달 합니다.

Listing 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는 임의이지만 동일한 유형의 두 객체에 대해 비교를 수행 할 수있는 함수를 설명하는 기능적 인터페이스를 가져옵니다 .

이 코드의 두 가지 중요한 부분은 sort()메서드 (버블 정렬 알고리즘을 구현 함)와 메서드 의 sort()호출입니다 main(). sort()기능적 이지 는 않지만 함수 (비교기)를 인수로받는 고차 함수를 보여줍니다. compare()메서드 를 호출하여이 함수를 실행합니다 . 이 함수의 두 인스턴스가 두 전달 sort()에 전화 main().

다음과 같이 목록 2를 컴파일하십시오.

javac Sort.java

결과 애플리케이션을 다음과 같이 실행하십시오.

java Sort

다음 출력을 관찰해야합니다.

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

자바의 지연 평가

지연 평가 는 Java 8에 새로 도입되지 않은 또 다른 기능적 프로그래밍 기술입니다.이 기술은 값이 필요할 때까지 표현식 평가를 지연시킵니다. 대부분의 경우 Java는 변수에 바인딩 된 표현식을 열심히 평가합니다. Java는 다음 특정 구문에 대한 지연 평가를 지원합니다.

  • 왼쪽 피연산자가 false ( ) 또는 true ( ) 일 때 오른쪽 피연산자를 평가하지 않는 부울 &&||연산자 .&&||
  • ?:이어서 부울 표현식을 평가하고 작업자는 부울 표현식의 참 / 거짓 값에 기초하여 (호환 형의) 두 개의 대안적인 표현 중 하나만 평가한다.

함수형 프로그래밍은 표현 지향 프로그래밍을 장려하므로 가능한 한 명령문을 사용하지 않는 것이 좋습니다. 예를 들어, Java의 if- else문을 ifThenElse()메소드 로 바꾸려고한다고 가정하십시오 . 목록 3은 첫 번째 시도를 보여줍니다.

Listing 3. Java (EagerEval.java)의 eager 평가 예제

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; } }

3 개 정의하는 목록 ifThenElse()복귀, 부울 술어와 정수의 쌍을 소요 방법 onTrue술어 인 경우 정수를 사실onFalse정수 그렇지.

목록 3은 cube()square()메소드 도 정의 합니다. 이러한 메서드는 각각 정수를 큐브 및 제곱하고 결과를 반환합니다.

main()메소드가 호출하는 ifThenElse(true, square(4), cube(4))경우에만 호출되어야 square(4)뒤에 ifThenElse(false, square(4), cube(4))만 호출한다 cube(4).

다음과 같이 목록 3을 컴파일하십시오.

javac EagerEval.java

결과 애플리케이션을 다음과 같이 실행하십시오.

java EagerEval

다음 출력을 관찰해야합니다.

in square in cube 16 in square in cube 64

출력은 ifThenElse()부울 식에 관계없이 각 호출이 두 메서드를 모두 실행 한다는 것을 보여줍니다 . ?:Java가 메소드의 인수를 열심히 평가하기 때문에 연산자의 게으름을 활용할 수 없습니다 .

메서드 인수의 열성적인 평가를 피할 수있는 방법은 없지만 ?:의 지연 평가를 이용하여 square()또는 만 cube()호출 되도록 할 수 있습니다. 목록 4는 방법을 보여줍니다.

Listing 4. 자바 (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)); } }

목록 4는 ifThenElse()이 메소드를 선언하여 한 쌍의 Function인수 를 수신함으로써 고차 함수로 바뀝니다 . 로 전달 될 때 이러한 인자 간절히 평가되지만 ifThenElse()상기 ?:연산자는 실행 (비아 이러한 함수 중 하나에서만 발생 apply()). 응용 프로그램을 컴파일하고 실행할 때 작업에서 eager 및 lazy 평가를 모두 볼 수 있습니다.

다음과 같이 목록 4를 컴파일하십시오.

javac LazyEval.java

결과 애플리케이션을 다음과 같이 실행하십시오.

java LazyEval

다음 출력을 관찰해야합니다.

SQUARE CUBE in square 16 in cube 64

게으른 반복자 등

Neal Ford의 "Laziness, Part 1 : Java에서 지연 평가 탐색"은 지연 평가에 대한 더 많은 통찰력을 제공합니다. 저자는 두 개의 lazy 지향 Java 프레임 워크와 함께 Java 기반 lazy iterator를 제시합니다.

자바의 클로저

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

기능적 사고 는 Neal Ford가 만든 용어로, 객체 지향 패러다임에서 함수 프로그래밍 패러다임으로의인지 적 전환을 나타냅니다. 이 튜토리얼에서 보았 듯이 기능적 기술을 사용하여 객체 지향 코드를 다시 작성함으로써 기능적 프로그래밍에 대해 많은 것을 배울 수 있습니다.

Listing 2의 Sort 애플리케이션을 다시 살펴보면서 지금까지 배운 내용을 정리하십시오.이 빠른 팁 에서는 먼저 Java 8 이전 기술을 사용한 다음 Java 8을 사용하여 순전히 기능적인 Bubble Sort작성하는 방법을 보여 드리겠습니다. 기능적 특징.

이 이야기 "Java 개발자를위한 기능적 프로그래밍, Part 2"는 원래 JavaWorld에 의해 출판되었습니다.