[Java] Lambda (람다)

6 minute read

Lambda

메서드로 전달할 수 있는 익명함수를 단순화한 것

How to Express

(람다 파라미터) → 람다 바디

(파라미터) → 표현식(expression) (파라미터) → { 구문(statement); }

1) (Type Parameter) -> {};

CalculatorParamAll cal4 = (int num1, int num2) -> { return num1 + num2; };

2) (Parameter) -> {};

모든 매개변수의 Type이 같으면 생략 가능

CalculatorParamAll cal5 = (num1, num2) -> { return num1 + num2; };

3) () -> {};

Method에 매개변수가 없다면 생략 가능

CalculatorParamNull cal6 = () -> { System.out.println("cal6.cal() = 매개변수가 없는 경우입니다."); };

execute(() -> {});

4) () -> ;

실행할 문장이 1개이면 중괄호 생략 가능, return도 생략 필수

CalculatorParamAll cal7 = (num1, num2) -> num1 + num2;

return () -> "Tricky example ;-)";

5) Paramter -> ;

매개변수가 1개이고, 실행할 문장이 1개이면 괄호, 중괄호 생략 가능

CalculatorParamOne cal8 = num1 -> System.out.println("cal8.cal(num1) = " + num1);

Anonymous class → lambda

ApplePredicate

시그니처 : methodName=test, type=Apple

global

public interface ApplePredicate {
    // predicate : true or false를 반환하는 함수

    boolean test(Apple apple);
}
public List<Apple> filterApples(List<Apple> inventory, ApplePredicate p) { // 파라미터로 Interface가 들어간다.
        List<Apple> result = new ArrayList<>();

        for (Apple apple : inventory) {
            if ( p.test(apple)) {
                result.add(apple);
            }
        }

        return result;
    }

as-is

List<Apple> resultAnonymousLambda = filterApples(inventory, new ApplePredicate() {
            @Override
            public boolean test(Apple apple) {
                return RED.toString().equals(apple.getColor());
            }
});

to-be

List<Apple> result = filterApples(inventory, (Apple apple) -> RED.toString().equals(apple.getColor()));

Functional Interface (함수형 인터페이스)

추상 메서드가 1개인 인터페이스₩ 디폴트 메서드 상관 X

@FunctionalInterface
public interface Runnable {
    /**
     * When an object implementing interface {@code Runnable} is used
     * to create a thread, starting the thread causes the object's
     * {@code run} method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method {@code run} is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

Java에서 제공하는 함수형 인터페이스 종류

https://docs.oracle.com/javase/8/docs/api/java/util/function/package-summary.html

@FunctionalInterface

함수형 인터페이스를 가리키는 어노테이션 람다식으로 표현하기 위해 사용되는 Interface위에 해당 어노테이션을 붙이면 컴파일 타임에 에러를 잡아 준다. 추상메서드가 1개 이상이면 에러 발생

@FunctionalInterface
public interface CalculatorParamAll {

    // lambda를 사용하기 위해서는 메소드가 하나여야 한디.
    public int cal(int num1, int num2);

    public int cal2(); // 에러 발생
}

Function Descriptor (함수 디스크립터)

함수형 인터페이스의 추상메서드 시그니처

람다 표현식의 시그니처를 서술하는 메서드

Interface Function Descriptor Abstract method
Predicate (T) -> boolean boolean test(T t);
Consumer (T) -> void void accept(T t);
Function<T,R> (T) -> R R apply(T t);
Supplier () -> T T get();
UnaryOperator (T) -> T T apply(T t);

시그니처(signature)

메서드명(타입, 파라미터 순서, 개수)

같은 시그니처를 가지는 메서드

int doSomething(int y) 
String doSomething(int x)
int doSomething(int z) throws java.lang.Exception

다른 시그니처를 가지는 메서드

doSomething(String[] y);
doSomething(String y);

Execute Around Pattern

초기화/준비 코드 → 작업 → 정리/마무리 코드 형식의 코드

@Component
public class BufferedReaderMain {

    ClassPathResource resource = new ClassPathResource("static/data.txt");

    @EventListener(ApplicationStartedEvent.class)
    public void main() throws IOException {
        System.out.println("=====ExecuteAroundPattern / Lambda =====");
        String twoLines = processFile((BufferedReader br) -> br.readLine() + " " + br.readLine());

        System.out.println("twoLines = " + twoLines);

    }

    public String processFile(BufferedReaderProcessor b) throws IOException {

        try (BufferedReader br = new BufferedReader(new FileReader(resource.getFile().getAbsolutePath()))) {
            return b.process(br);
        }
    }

}
@FunctionalInterface
public interface BufferedReaderProcessor {
    String process(BufferedReader b) throws IOException;
}

형식 검사 / 추론 / 제약

형식 검사

Lambda가 사용되는 Context를 이용해서 람다의 Type을 추론할 수 있다.

형식 확인 과정

  1. filter 메서드 선언 확인

     List<Apple> heaviorThan150g = filter(inventory, (Apple apple) -> apple.getWeight() > 150);
    
  2. filter 메서드는 두번 째 파라미터로 Predicate(Apple) 형식을 기대한다. (대상 형식, target type)

     public List<Apple> filter(List<Apple> inventory,  Predicate<Apple> p) {
        
     }
    
  3. Predicate(Apple)은 test라는 한 개의 추상 메서드를 정의하는 함수형 인터페이스이다.

     public interface Predicate<T> {
     	boolean test(Apple apple)
     }
    
  4. test 메서드는 Apple을 받아 boolean을 반환하는 함수 디스크립터를 묘사한다.

     Apple -> boolean
    
  5. filter 메서드로 전달된 인수는 이와 같은 요구사항을 만족해야 한다.

     (동일)
        
     Functional Descripter : Apple -> boolean
     Lambda Signature : Apple -> boolean
    

같은 람다, 다른 함수형 인터페이스

같은 람다 표현식이더라도 호환되는 추상메서드를 가진 다른 함수형 인터페이스로 사용될 수 있다.

Comparator<Apple>                 c1 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
ToINtBiFunction<Apple, Apple>     c2 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());
BiFunction<Apple, Apple, Integer> c3 = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

형식 추론

람다 표현식이 사용된 콘텍스트(대상 형식)을 사용하여 람다 표현식과 관련된 함수형 인터페이스를 추론한다.

상황에따라 사용하거나 말거나~

람다식 (람다 시그니처 : 타입, 파라미터 순서, 개수)

List<Apple> greenApples = filter(inventory, apple -> GREEN.equals(apple.getColor())); // 대상 형식을 이용해서 함수 디스크립터를 알 수 있으므로 컴파일러가 람다의 시그니처를 추론할 수 있다.

메서드 (대상형식)

public List<Apple> filter(List<Apple> inventory,  Predicate<Apple> p) { // 대상 형식 : Predicate<Apple>

}

함수형 인터페이스

public interface Predicate<T> {
	boolean test(Apple apple)
}

지역 변수 사용 (Capturing Lambda)

파라미터로 넘겨진 변수 이외에 자유 변수를 활용할 수 있음

  • final로 선언된 변수 만 가능(한 번만 할당 할 수 있음)
int portNumber = 1337;

Runnerble r = () -> System.out.prinln(portNumber);

메서드 참조 (Method Reference)

특정 메서드만을 호출하는 람다의 축약형

기존 메서드 정의를 재활용해서 람다처럼 전달 가능

as-is

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight()));

Method Referernce

// Apple 클래스에 정의된 getWeight의 메서드 참조
inventory.sort(comparing(Apple::getWeight));

Lambda & Method Reference

Lambda Method Reference
(Apple apple) -> apple.getWeight() Apple::getWeight
Thread.currentThread().dumpStack() Thread.currentTread()::dumpStack
(str, i) → str.substring(i) String::substring
(String s) → System.out.println(s) System.out::println
(String s) → this.isValidName(s) this::isValidName

생성 방법

  1. 정적 메서드 참조 : Integer::parseInt
  2. 다양한 형식의 인스턴스 메서드 참조 : String::length
  3. 기존 객체의 인스턴스 메서드 참조 : expensiveTransaction::getValue
@Component
public class MethodReference {

    @EventListener(ApplicationStartedEvent.class)
    public void main() {
        System.out.println("===== Method Reference =====");

        List<String> str = Arrays.asList("a", "b", "A", "D");
        str.sort(String::compareToIgnoreCase);

        System.out.println("str = " + str);

        ToIntFunction<String> stringToInt = Integer::parseInt;

        int number = stringToInt.applyAsInt("0") + 1;
        System.out.println("number + 1 = " + number);

        BiPredicate<List<String>, String> contains = List::contains;
        boolean existA = contains.test(str, "a");
        System.out.println("existA = " + existA);

        Predicate<String> startsWithNumber = this::startsWithNumber;
        boolean isStartedNumber = startsWithNumber.test("1sdasd");
        System.out.println("isStartedNumber = " + isStartedNumber);

    }

    public Boolean startsWithNumber(String s) {
        return Character.isDigit(s.charAt(0));
    }
}

생성자 참조 (Constructer)

ClassName::new 와 같이 new 키워드를 통해 기존 생성자의 참조를 만들 수 있다.

@Component
public class ConstructorReference {

    @EventListener(ApplicationStartedEvent.class)
    public void main() {
        System.out.println("===== Constructor Reference =====");

        
        // Supplier<BlueApple> b1 = () -> new BlueApple();
        Supplier<BlueApple> ba = BlueApple::new;
        BlueApple ba1 = ba.get();
        System.out.println("ba1.getClass() = " + ba1.getClass());

        
        // Function<Integer, GreenApple> a2 = (weight) -> new GreenApple(weight);
        Function<Integer, GreenApple> ga = GreenApple::new;
        GreenApple ga1 = ga.apply(100);
        System.out.println("ga1.getWeight() = " + ga1.getWeight());

        
        List<Integer> weights = Arrays.asList(7, 3, 4, 10);
        // List<GreenApple> greenApples = map(weights, weight -> new GreenApple(weight));
        List<GreenApple> greenApples = map(weights, GreenApple::new);
        for (GreenApple gas: greenApples) {
            System.out.println("weight = " + gas.getWeight());
        }
        
        
        // BiFunction<Integer, String, Apple> a1 = (weight, color) -> new Apple(weight, color);
        BiFunction<Integer, String, Apple> a = Apple::new;
        Apple a1 = a.apply(200, "GREEN");
        System.out.println("a1.getWeight() = " + a1.getWeight());
    }

    public List<GreenApple> map(List<Integer> list, Function<Integer, GreenApple> f) {
        List<GreenApple> result = new ArrayList<>();

        for (Integer i : list) {
            result.add(f.apply(i));
        }

        return result;
    }
}
@NoArgsConstructor
public class BlueApple {
}
@AllArgsConstructor
@Getter
public class GreenApple {
    public Integer weight;
}

람다, 메서드 참조 활용 예제

as-is

public class AppleComparator implements Comparator<Apple> {
	public int compare(Apple a1, Apple a2) {
		return a1.getWeight().compareTo(a2.getWeight());
	}
}

// void sort(Comparator<? super E> c)
inventory.sort(new AppleComparator());

anonymous class

inventory.sort(new Comparator<Apple>() {
	public int compare(Apple a1, Apple a2) {
		return a1.getWeight().compareTo(a2.getWeight());
	}
});

lambda

inventory.sort((Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

inventory.sort((a1, a2) -> a1.getWeight().compareTo(a2.getWeight());

static method

import static java.util.CoAAmparator.comparing;
inventory.sort(comparing(apple -> apple.getWeight());

람다 표현식 조합을 위한 메서드

Utility Method : Comparator, Function, Predicate 가 있었다.

조합을 위해 추가적으로 메서드를 제공한다.

Default Method : 추상 메서드가 아니므로 함수형 인터페이스의 정의를 벗어나지 않음

Comparator

reversed()

inventory.sort(comparing(Apple::getWeight).reversed());

thenComparing()

inventory.sort(comparing(Apple::getWeight)
	.reversed()
	.tehnComparing(Apple::getCountry));

Predicate

netgate()

Predicate<Apple> notRedApple = redApple.netgate();

and()

Predicate<Apple> redAndHeavyApple = redApple.and(apple -> apple.getWeight() > 150);

or()

Predicate<Apple> redAndHeavyAppleOrGreen = redApple.and(apple -> apple.getWeight() > 150).or(apple -> GREEN.equals(a.getColor()));

Function

andThen()

Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.andThen(g); //g(f(x))
int result = h.apply(1) // 4

compose()

Function<Integer, Integer> f = x -> x + 1;
Function<Integer, Integer> g = x -> x * 2;
Function<Integer, Integer> h = f.compose(g); //f(g(x))
int result = h.apply(1) // 3

Categories:

Updated:

Leave a comment