자바에서 람다 식 시작하기

Java SE 8 이전에는 익명 클래스가 일반적으로 메서드에 기능을 전달하는 데 사용되었습니다. 이 방법은 소스 코드를 난독 화하여 이해하기 어렵게 만듭니다. Java 8은 람다를 도입하여이 문제를 해결했습니다. 이 자습서에서는 먼저 람다 언어 기능을 소개 한 다음 대상 유형과 함께 람다 식을 사용하는 함수형 프로그래밍에 대한 자세한 소개를 제공합니다. 또한 람다가 범위, 지역 변수, thissuper키워드, Java 예외와 상호 작용하는 방법을 배웁니다 . 

이 자습서의 코드 예제는 JDK 12와 호환됩니다.

자신을위한 유형 발견

이 자습서에서는 이전에 배운 적이없는 람다가 아닌 언어 기능을 소개하지 않겠지 만이 시리즈에서 이전에 논의하지 않은 유형을 통해 람다를 시연 할 것입니다. 한 가지 예가 java.lang.Math수업입니다. 향후 Java 101 자습서에서 이러한 유형을 소개 할 것입니다. 지금은 JDK 12 API 문서를 읽고 이에 대해 자세히 알아 보는 것이 좋습니다.

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

Lambda : 입문서

람다 식 (람다)의 후속 실행 또는 생성자 메소드로 전달 될 수있는 코드 블록 (익명의 기능)을 설명한다. 생성자 또는 메서드는 람다를 인수로받습니다. 다음 예를 고려하십시오.

() -> System.out.println("Hello")

이 예에서는 표준 출력 스트림에 메시지를 출력하기위한 람다를 식별합니다. 왼쪽에서 오른쪽으로 ()람다의 형식 매개 변수 목록 (예제에는 매개 변수가 없음 )을 식별하고 ->표현식이 람다이며 System.out.println("Hello")실행할 코드 임을 나타냅니다 .

Lambda 는 각각 정확히 하나의 추상 메서드를 선언하는 주석이 달린 인터페이스 인 기능 인터페이스 의 사용을 단순화합니다 (기본, 정적 및 프라이빗 메서드의 모든 조합을 선언 할 수도 있음). 예를 들어, 표준 클래스 라이브러리는 java.lang.Runnable단일 추상 void run()메서드가 있는 인터페이스를 제공합니다 . 이 기능적 인터페이스의 선언은 다음과 같습니다.

@FunctionalInterface public interface Runnable { public abstract void run(); }

클래스 라이브러리는 주석 Runnable@FunctionalInterface의 인스턴스가되는, java.lang.FunctionalInterface주석 형. FunctionalInterface람다 컨텍스트에서 사용할 인터페이스에 주석을 추가하는 데 사용됩니다.

람다는 명시 적 인터페이스 유형이 없습니다. 대신 컴파일러는 주변 컨텍스트를 사용하여 람다가 지정 될 때 인스턴스화 할 기능 인터페이스를 추론합니다. 즉, 람다는 해당 인터페이스에 바인딩 됩니다. 예를 들어, 이전 람다를 java.lang.Thread클래스 Thread(Runnable target)생성자에 대한 인수로 전달하는 다음 코드 조각을 지정했다고 가정합니다 .

new Thread(() -> System.out.println("Hello"));

컴파일러는 람다 Thread(Runnable r)를 충족하는 유일한 생성자이기 때문에 람다가 전달되고 있다고 결정합니다. Runnable기능 인터페이스이고 람다의 빈 형식 매개 변수 목록이의 빈 매개 변수 목록 ()과 일치 run()하며 반환 유형 ( void)도 동의합니다. 람다는 Runnable.

목록 1은이 예제를 사용할 수있는 작은 애플리케이션에 대한 소스 코드를 보여줍니다.

목록 1. LambdaDemo.java (버전 1)

public class LambdaDemo { public static void main(String[] args) { new Thread(() -> System.out.println("Hello")).start(); } }

목록 1 ( javac LambdaDemo.java)을 컴파일 하고 애플리케이션 ( java LambdaDemo)을 실행합니다 . 다음 출력을 관찰해야합니다.

Hello

Lambda는 작성해야하는 소스 코드의 양을 크게 단순화 할 수 있으며 소스 코드를 훨씬 더 이해하기 쉽게 만들 수 있습니다. 예를 들어, 람다가 없으면 아마도 Runnable.

목록 2. LambdaDemo.java (버전 2)

public class LambdaDemo { public static void main(String[] args) { Runnable r = new Runnable() { @Override public void run() { System.out.println("Hello"); } }; new Thread(r).start(); } }

이 소스 코드를 컴파일 한 후 애플리케이션을 실행하십시오. 이전에 표시된 것과 동일한 출력을 볼 수 있습니다.

Lambda 및 Streams API

소스 코드를 단순화 할뿐만 아니라 람다는 Java의 기능 지향적 인 Streams API에서 중요한 역할을합니다. 다양한 API 메서드에 전달되는 기능 단위를 설명합니다.

자바 람다 심화

람다를 효과적으로 사용하려면 대상 유형의 개념과 함께 람다 식의 구문을 이해해야합니다. 또한 람다가 범위, 지역 변수, thissuper키워드 및 예외 와 상호 작용하는 방식을 이해해야합니다 . 이어지는 섹션에서 이러한 모든 주제를 다룰 것입니다.

람다 구현 방법

Lambda는 Java 가상 머신의 invokedynamic지침과 java.lang.invokeAPI 측면에서 구현됩니다 . Lambda 아키텍처에 대해 알아 보려면 Lambda : A Peek Under the Hood 비디오를 시청하십시오.

Lambda 구문

모든 람다는 다음 구문을 따릅니다.

( formal-parameter-list ) -> { expression-or-statements }

formal-parameter-list런타임에 기능적인 인터페이스의 하나의 추상 메소드의 매개 변수와 일치해야합니다 형식 매개 변수의 쉼표로 구분 된 목록입니다. 해당 형식을 생략하면 컴파일러는 람다가 사용되는 컨텍스트에서 이러한 형식을 유추합니다. 다음 예를 고려하십시오.

(double a, double b) // types explicitly specified (a, b) // types inferred by compiler

람다 및 변수

Java SE 11부터 유형 이름을 var. 예를 들어 (var a, var b).

형식 매개 변수가 여러 개이거나없는 경우 괄호를 지정해야합니다. 그러나 단일 형식 매개 변수를 지정할 때 괄호를 생략 할 수 있습니다 (그럴 필요는 없음). (이는 매개 변수 이름에만 적용되며 유형도 지정된 경우 괄호가 필요합니다.) 다음 추가 예를 고려하십시오.

x // parentheses omitted due to single formal parameter (double x) // parentheses required because type is also present () // parentheses required when no formal parameters (x, y) // parentheses required because of multiple formal parameters

The formal-parameter-list is followed by a -> token, which is followed by expression-or-statements--an expression or a block of statements (either is known as the lambda's body). Unlike expression-based bodies, statement-based bodies must be placed between open ({) and close (}) brace characters:

(double radius) -> Math.PI * radius * radius radius -> { return Math.PI * radius * radius; } radius -> { System.out.println(radius); return Math.PI * radius * radius; }

The first example's expression-based lambda body doesn't have to be placed between braces. The second example converts the expression-based body to a statement-based body, in which return must be specified to return the expression's value. The final example demonstrates multiple statements and cannot be expressed without the braces.

Lambda bodies and semicolons

Note the absence or presence of semicolons (;) in the previous examples. In each case, the lambda body isn't terminated with a semicolon because the lambda isn't a statement. However, within a statement-based lambda body, each statement must be terminated with a semicolon.

Listing 3 presents a simple application that demonstrates lambda syntax; note that this listing builds on the previous two code examples.

Listing 3. LambdaDemo.java (version 3)

@FunctionalInterface interface BinaryCalculator { double calculate(double value1, double value2); } @FunctionalInterface interface UnaryCalculator { double calculate(double value); } public class LambdaDemo { public static void main(String[] args) { System.out.printf("18 + 36.5 = %f%n", calculate((double v1, double v2) -> v1 + v2, 18, 36.5)); System.out.printf("89 / 2.9 = %f%n", calculate((v1, v2) -> v1 / v2, 89, 2.9)); System.out.printf("-89 = %f%n", calculate(v -> -v, 89)); System.out.printf("18 * 18 = %f%n", calculate((double v) -> v * v, 18)); } static double calculate(BinaryCalculator calc, double v1, double v2) { return calc.calculate(v1, v2); } static double calculate(UnaryCalculator calc, double v) { return calc.calculate(v); } }

Listing 3 first introduces the BinaryCalculator and UnaryCalculator functional interfaces whose calculate() methods perform calculations on two input arguments or on a single input argument, respectively. This listing also introduces a LambdaDemo class whose main() method demonstrates these functional interfaces.

The functional interfaces are demonstrated in the static double calculate(BinaryCalculator calc, double v1, double v2) and static double calculate(UnaryCalculator calc, double v) methods. The lambdas pass code as data to these methods, which are received as BinaryCalculator or UnaryCalculator instances.

Compile Listing 3 and run the application. You should observe the following output:

18 + 36.5 = 54.500000 89 / 2.9 = 30.689655 -89 = -89.000000 18 * 18 = 324.000000

Target types

A lambda is associated with an implicit target type, which identifies the type of object to which a lambda is bound. The target type must be a functional interface that's inferred from the context, which limits lambdas to appearing in the following contexts:

  • Variable declaration
  • Assignment
  • Return statement
  • Array initializer
  • Method or constructor arguments
  • Lambda body
  • Ternary conditional expression
  • Cast expression

Listing 4 presents an application that demonstrates these target type contexts.

목록 4. LambdaDemo.java (버전 4)

import java.io.File; import java.io.FileFilter; import java.nio.file.Files; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.FileVisitor; import java.nio.file.FileVisitResult; import java.nio.file.Path; import java.nio.file.PathMatcher; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import java.util.concurrent.Callable; public class LambdaDemo { public static void main(String[] args) throws Exception { // Target type #1: variable declaration Runnable r = () -> { System.out.println("running"); }; r.run(); // Target type #2: assignment r = () -> System.out.println("running"); r.run(); // Target type #3: return statement (in getFilter()) File[] files = new File(".").listFiles(getFilter("txt")); for (int i = 0; i  path.toString().endsWith("txt"), (path) -> path.toString().endsWith("java") }; FileVisitor visitor; visitor = new SimpleFileVisitor() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attribs) { Path name = file.getFileName(); for (int i = 0; i  System.out.println("running")).start(); // Target type #6: lambda body (a nested lambda) Callable callable = () -> () -> System.out.println("called"); callable.call().run(); // Target type #7: ternary conditional expression boolean ascendingSort = false; Comparator cmp; cmp = (ascendingSort) ? (s1, s2) -> s1.compareTo(s2) : (s1, s2) -> s2.compareTo(s1); List cities = Arrays.asList("Washington", "London", "Rome", "Berlin", "Jerusalem", "Ottawa", "Sydney", "Moscow"); Collections.sort(cities, cmp); for (int i = 0; i < cities.size(); i++) System.out.println(cities.get(i)); // Target type #8: cast expression String user = AccessController.doPrivileged((PrivilegedAction) () -> System.getProperty("user.name")); System.out.println(user); } static FileFilter getFilter(String ext) { return (pathname) -> pathname.toString().endsWith(ext); } }