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

JDK를 깊이 있게 분석8새 기능의 Lambda 표현식

처음 Lambda 표현식을 접한 것은 TypeScript(JavaScript의 확장)에서였습니다. 그때는 TypeScript의 this 메서드가 현재 메서드 내보다 외부에서 사용되도록 했습니다. 사용한 후 갑자기 Lambda가 JDK가 아니라는 생각이 들었습니다.8중요한 새로운 기능이 있을까요? 그래서 관련 자료를 검색하고 기록했습니다:

1. 행위 파라미터화

행위 파라미터화는 간단히 말해서 함수의 주체가 템플릿 클래스의 일반 코드만 포함되어 있고, 업무 시나리오에 따라 변하는 로직은 함수에 파라미터로 전달되는 방식입니다. 행위 파라미터화를 통해 프로그램이 더 일반적이고, 빈번한 변경 요구에 대응할 수 있게 합니다.

업무 시나리오를 고려해 보겠습니다. 예를 들어, 우리가 프로그램을 통해 사과를 필터링해야 하는 경우, 먼저 사과의 엔티티를 정의해 보겠습니다:

public class Apple {
/** 编号 */
private long id;
/** 색상 */
private Color color;
/** 중량 */
private float weight;
/** 출처 */
private String origin;
public Apple() {
}
public Apple(long id, Color color, float weight, String origin) {
this.id = id;
this.color = color;
this.weight = weight;
this.origin = origin;
}
// getter와 setter를 생략합니다
}

사용자의 초기 요구는 녹색의 사과를 프로그램을 통해 필터링할 수 있는 것이었을 가능성이 큽니다. 따라서 프로그램을 통해 빠르게 구현할 수 있습니다:

public static List<Apple> filterGreenApples(List<Apple> apples) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (Color.GREEN.equals(apple.getColor())) {
filterApples.add(apple);
}
}
return filterApples;
}

이 코드는 매우 간단하며, 특별히 말할 것이 없습니다. 하지만 사용자 요구가 녹색으로 변경되면, 코드를 수정하는 것도 매우 간단합니다. 녹색 판단 조건을 빨간색으로 변경하는 것뿐입니다. 하지만 또 다른 문제를 고려해야 합니다. 변화 조건이 자주 변경되면 어떻게 합니까?63; 색상 변경만이 필요하다면, 사용자가 색상 판단 조건을 직접 전달하면 됩니다. 판단 메서드의 파라미터는 '판단할 집합 및 필터링할 색상'입니다. 하지만 사용자가 색상뿐만 아니라 무게, 크기 등을 판단하고 싶다면 어떻게 해야 합니까? 여러 가지 파라미터를 순차적으로 추가하여 판단을 완료할 수 있을까요? 하지만 파라미터가 많아지면 조합 모델이 복잡해지고, 모든 경우를 고려하고 각 경우에 대한 대응 전략을 마련해야 할까요?63; 이때 행동을 파라미터화하고, 필터 조건을 파라미터로 전달할 수 있습니다. 이 때는 판단 인터페이스를 포장할 수 있습니다:

public interface AppleFilter {
/**
* 필터 조건 추상
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}
/**
* 색상 필터 조건을 인터페이스로 포장합니다
*
* @param apples
* @param filter
* @return
*/
public static List<Apple> filterApplesByAppleFilter(List<Apple> apples, AppleFilter filter) {
List<Apple> filterApples = new ArrayList<>();
for (final Apple apple : apples) {
if (filter.accept(apple)) {
filterApples.add(apple);
}
}
return filterApples;
}

위의 행위를 추상화한 후, 호출 지점에서筛选条件을 설정하고 메서드에 조건을 전달할 수 있습니다. 이 경우 익명 내부 클래스 메서드를 사용합니다:

public static void main(String[] args) {
List<Apple> apples = new ArrayList<>();
// 사과 필터링
List<Apple> filterApples = filterApplesByAppleFilter(apples, new AppleFilter() {
@Override
public boolean accept(Apple apple) {
// 무게가 더 큰100g의 빨간 사과
return Color.RED.equals(apple.getColor()) && apple.getWeight() > 100;
}
});
}

이 설계는 JDK 내부에서도 자주 사용됩니다. 예를 들어 Java.util.Comparator, java.util.concurrent.Callable 등. 이러한 인터페이스를 사용할 때는 특정 호출 지점에서 익명 클래스를 사용하여 함수의 구체적인 실행 로직을 지정할 수 있습니다. 하지만 위의 코드 블록에서 보면, 매우 지식스러운 것 같지만 간결하지 않습니다. java8우리는 lambda를 통해 이를 간소화할 수 있습니다:

// 사과 필터링
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
//()->xxx ()안에 메서드 파라미터가 있습니다. xxx는 메서드 구현입니다

2. lambda 표현식 정의

lambda 표현식을 간결하고 전달 가능한 익명 함수로 정의할 수 있습니다. 먼저 lambda 표현식은 본질적으로 함수이며, 특정 클래스에 속하지 않지만 파라미터 목록, 함수 본체, 반환 타입, 예외를 던질 수 있는 기능을 가지고 있습니다. 또한 익명이며, lambda 표현식은 명확한 함수 이름을 가지지 않습니다. lambda 표현식은 파라미터처럼 전달될 수 있어 코드 작성을 대폭 간소화할 수 있습니다. 형식 정의는 다음과 같습니다:

형식 1: 파라미터 목록 -> 표현식

형식 2: 파라미터 목록 -> {표현식 집합}

주의해야 할 것은, lambda 표현식은 은닉된 return 키워드를 가지고 있기 때문에, 단일 표현식에서는 return 키워드를 명시적으로 작성하지 않아도 됩니다. 그러나 표현식이 문장 집합이 되는 경우에는 명시적으로 return을 추가하고, 여러 표현식을 중괄호로 감싸야 합니다. 몇 가지 예제를 보겠습니다:

//주어진 문자열의 길이를 반환합니다. 은닉된 return 문
(String s) -> s.length() 
// 항상 반환42의 비파라미터 메서드
() -> 42 
// 다중 행 표현식이 포함되면 괄호로 감싸줍니다
(int x, int y) -> {
int z = x * y;
return x + z;
}

3. 함수형 인터페이스를 사용하여 lambda 표현식을 기반으로 합니다

lambda 표현식의 사용은 함수형 인터페이스를 통해 가능합니다. 따라서 함수형 인터페이스가 있는 곳에만 lambda 표현식을 간소화할 수 있습니다.

사용자 정의 함수형 인터페이스:

함수형 인터페이스는 단 하나의 추상 메서드를 가진 인터페이스로 정의됩니다. java8인터페이스 정의에서의 개선은 기본 메서드를 도입하는 것입니다. 이를 통해 인터페이스에서 메서드에 기본적인 구현을 제공할 수 있습니다. 하지만 기본 메서드가 많이 있더라도 추상 메서드가 하나만 있으면 그 인터페이스는 함수형 인터페이스입니다. 예를 들어 (위의 AppleFilter를 참조하세요):

/**
* 사과 필터 인터페이스
*/
@FunctionalInterface
public interface AppleFilter {
/**
* 필터 조건 추상
*
* @param apple
* @return
*/
boolean accept(Apple apple);
}

AppleFilter는 단 하나의 추상 메서드만 포함하고 있으며, 정의에 따르면 함수형 인터페이스로 간주할 수 있습니다. 정의 시에 @FunctionalInterface 애노테이션을 추가하여 이 인터페이스가 함수형 인터페이스임을 표시했습니다. 그러나 이 인터페이스는 선택적입니다. 이 인터페이스를 추가하면 컴파일러는 이 인터페이스가 단 하나의 추상 메서드만 가질 수 있도록 제한하고, 그렇지 않으면 오류를 반환합니다. 따라서 함수형 인터페이스에 대해 이 애노테이션을 추가하는 것을 추천합니다.

jdk의 기본적인 함수형 인터페이스:

jdk는 lambda 표현식에 대해 기본적으로 다양한 함수형 인터페이스를 제공합니다. 아래에서 Predicate<T>、Consumer<T>、Function<T, R>의 사용 예제를 설명합니다.

Predicate:

@FunctionalInterface
public interface Predicate<T> {
/**
* 주어진 매개변수에 대해 이 predicate를 평가합니다.
*
* @param t 입력 매개변수
* @return 입력 매개변수가 predicate와 일치하면 @code{true}
* 다른 경우는 @code{false}
*/
boolean test(T t);
}

Predicate의 기능은 위의 AppleFilter와 유사하며, 외부에서 설정한 조건을 통해 입력된 매개변수를 검증하고 검증 결과 boolean을 반환합니다. 아래에서 Predicate를 사용하여 List集合의 요소를 필터링합니다:

/**
*
* @param list
* @param predicate
* @param <T>
* @return
*/
public <T> List<T> filter(List<T> list, Predicate<T> predicate) {
List<T> newList = new ArrayList<T>();
for (final T t : list) {
if (predicate.test(t)) {
newList.add(t);
}
}
return newList;
}

사용:

demo.filter(list, (String str -> null != str && !str.isEmpty());

Consumer

@FunctionalInterface
public interface Consumer<T> {
/**
* 주어진 매개변수에 이 작업을 수행합니다.
*
* @param t 입력 매개변수
*/
void accept(T t);
}

Consumer는 accept 추상 함수를 제공하며, 이 함수는 매개변수를 받지만 반환 값이 없습니다. 아래에서 Consumer를 사용하여 셋을 순회합니다.

/**
* 집합을 순회하며 사용자 정의 행동을 수행합니다
*
* @param list
* @param consumer
* @param <T>
*/
public <T> void filter(List<T> list, Consumer<T> consumer) {
for (final T t : list) {
consumer.accept(t);
}
}

위의 함수형 인터페이스를 사용하여 문자열 집합을 순회하며 비어있지 않은 문자열을 출력합니다:

demo.filter(list, (String str -> {
if (StringUtils.isNotBlank(str)) {
System.out.println(str);
}
});

Function

@FunctionalInterface
public interface Function<T, R> {
/**
* 주어진 인자에 이 함수를 적용합니다.
*
* @param t 함수 인자
* @return 함수 결과
*/
R apply(T t);
}

Funcation이 변환 작업을 수행하며, 입력은 타입 T의 데이터이며, 반환은 R 타입의 데이터입니다. 아래에서 Function을 사용하여 셋을 변환합니다:

public <T, R> List<R> filter(List<T> list, Function<T, R> function) {
List<R> newList = new ArrayList<R>();
for (final T t : list) {
newList.add(function.apply(t));
}
return newList;
}

其他:

demo.filter(list, (String str -> Integer.parseInt(str));

上面这些函数式接口还提供了一些逻辑操作的默认实现,留到后面介绍java8接口的默认方法时再讲吧~

使用过程中需要注意的一些事情:

类型推断:

编码过程中,有时候可能会疑惑我们的调用代码会去具体匹配哪个函数式接口,实际上编译器会根据参数、返回类型、异常类型(如果存在)等做正确的判定。
특정 호출 시, 매개변수 타입을 생략하여 코드를 더욱 간결하게 만들 수 있습니다:

// 사과 필터링
List<Apple> filterApples = filterApplesByAppleFilter(apples,
(Apple apple) -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= 100);
// 때로는 매개변수 타입을 생략할 수도 있습니다. 컴파일러는 상황에 따라 올바르게 판단합니다
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.R
> ED.equals(apple.getColor()) && apple.getWeight() >= 100);

지역 변수

위의 모든 예제에서는 람다 표현식이 주체 매개변수를 사용했지만, 람다 내에서 지역 변수도 사용할 수 있습니다. 예를 들어:

int weight = 100;
List<Apple> filterApples = filterApplesByAppleFilter(apples,
apple -> Color.RED.equals(apple.getColor()) && apple.getWeight() >= weight);

이 예제에서는 람다 내에서 지역 변수 weight를 사용했지만, 람다 내에서 지역 변수를 사용하려면 해당 변수가 명시적으로 final이나 실질적으로 final이어야 합니다. 이는 지역 변수가 스택에 저장되고 람다 표현식이 다른 스레드에서 실행되기 때문입니다. 이 스레드가 해당 지역 변수에 접근할 때, 변수가 변경되거나 회수될 가능성이 있기 때문에, final로修饰하면 스레드 안전성 문제가 발생하지 않습니다.

4. 메서드 참조

메서드 참조를 사용하면 코드를 더욱 간결하게 만들 수 있습니다. 때로는 이러한 간결함이 코드를 더 직관적으로 보이게 합니다. 예를 들어:

/* ... apples의 초기화 작업을 생략합니다 */
// 람다 표현식을 사용합니다
apples.sort((Apple a, Apple b)}) -> Float.compare(a.getWeight(), b.getWeight()));
// 메서드 참조 사용
apples.sort(Comparator.comparing(Apple::getWeight));

메서드 참조는 ::를 통해 메서드 소속과 메서드 자체를 연결합니다. 주로 세 가지 유형으로 나뉩니다:

정적 메서드

(args) -> ClassName.staticMethod(args)

변환하여

ClassName::staticMethod

파라미터의 인스턴스 메서드

(args) -args.instanceMethod()

변환하여

> ClassName::instanceMethod // ClassName은 args의 타입입니다

외부의 인스턴스 메서드

(args) -> ext.instanceMethod(args)

변환하여

ext::instanceMethod(args)

참조:

http://www.codeceo.com/article/lambda-of-java-8.html

위에 설명한 것은 편집자가 여러분께 소개한 JDK입니다.8신기한 기능 Lambda 표현식, 여러분께 도움이 되길 바랍니다. 여러분이 어떤 질문이나 의문이 있으시면, 댓글을 남겨 주시면, 편집자가 즉시 답변 드리겠습니다. 또한, 여러분의呐喊 교본 사이트에 대한 지지에 깊이 감사드립니다!

언급: 본 문서는 인터넷에서 수집되었으며, 저작권자는 본사입니다. 내용은 인터넷 사용자가 자발적으로 기여하고 업로드한 것이며, 사이트는 소유권을 가지지 않으며, 인공적인 편집을 거치지 않았으며, 관련 법적 책임도 부담하지 않습니다. 저작권 침해가 의심되는 내용이 있으시면, 이메일을 notice#w로 보내 주시기 바랍니다.3codebox.com에 이메일을 보내서 (#을 @으로 변경하십시오) 신고하시고 관련 증거를 제공하시면, 사이트가 즉시 저작권 침해 내용을 삭제합니다.

좋아하는 것