[윤성우의 열혈 Java 프로그래밍] Chapter 27 - 람다와 함수형 인터페이스

Update:     Updated:

카테고리:

태그:

27-1. 람다와 함수형 인터페이스

- 인스턴스보다 기능 하나가 필요한 상황을 위한 람다

기능 하나를 정의해서 전달해야 하는 상황

자바는 객체지향언어이기 때문에 인스턴스를 전달하는 형태이지만 내용을 보면 메소드, 즉 기능을 전달하는 상황이 생기곤 한다.
이러한 상황에서 람다가 유용하게 쓰일 수 있다.

- 매개변수가 있고 반환하지 않는 람다식

interface LambdaExample {
    void print(String s);
}

public class OneParamNoReturn {
    public static void main(String[] args) {
        LambdaExample l;

        // 줄임 없는 표현
        l = (String s) -> { System.out.println(s); };

        // 중괄호 생략
        l = (String s) -> System.out.println(s);

        // 매개변수 형 생략
        l = (s) -> System.out.println(s);

        // 매개변수 소괄호 생략
        l = s -> System.out.println(s);

        l.print("Lambda example!");
    }
}
interface Calculate {
    void cal(int a, int b);
}

public class TwoParamNoReturn {
    public static void main(String[] args) {
        Calculate c;    // 매개변수가 둘 이상인 경우, 괄호를 생략하는 것은 불가능하다.

        // 덧셈 진행
        c = (a, b) -> System.out.println(a + b);
        c.cal(4, 3);

        // 뺄셈 진행
        c = (a, b) -> System.out.println(a - b);
        c.cal(4,3);

        // 곱셈 진행
        c = (a, b) -> System.out.println(a * b);
        c.cal(4, 3);
    }
}

- 매개변수가 있고 반환하는 람다식

interface RCalculate {
    int cal(int a, int b);
}

public class TwoParamAndReturn {
    public static void main(String[] args) {
        RCalculate c;

        // 몸체에 return이 들어가면 중괄호 생략 불가
        c = (a, b) -> { return a + b; };    // c = (a, b) -> { a + b; }; 불가능
        System.out.println(c.cal(4,3));

        // return 문이 메소드의 몸체를 이루는 유일한 경우
        c = (a, b) -> a + b;
        System.out.println(c.cal(4,3));
    }
}

- 매개변수가 없는 람다식

import java.util.Random;

interface Generator {
    int rand(); // 매개변수 없는 메소드
}

public class NoParamAndReturn {
    public static void main(String[] args) {
        Generator g;

        g = () -> {
            Random random = new Random();
            return random.nextInt(50);
        };

        System.out.println(g.rand());
    }
}

- 함수형 인터페이스와 어노테이션

함수형 인터페이스: 추상 메소드가 딱 하나만 존재하는 인터페이스

@FunctionsalInterface 어노테이션 타입을 통해 함수형 인터페이스에 부합하는지를 확인할 수 있다.
인터페이스에 둘 이상의 추상 메소드가 존재하면, 컴파일 오류로 이어진다.
그러나 static, default 선언이 붙은 메소드의 정의는 함수형 인터페이스의 정의에 아무런 영향을 미치지 않는다.

@FunctionalInterface
interface Calculate {
    int cal(int a, int b);
    default int add(int a, int b) { return a + b; }
    static int sub(int a, int b) { return a - b; }
}

- 람다식과 제네릭

인터페이스는 제네릭으로 정의하는 것이 가능하다.
따라서 제네릭으로 정의된 함수형 인터페이스를 대상으로 람다식을 작성할 수도 있다.

@FunctionalInterface
interface Calculator <T> {
    T cal(T a, T b);
}

public class LambdaGeneric {
    public static void main(String[] args) {
        // 참조변수의 형을 지정해서 문장을 구성하면 된다.
        Calculator<Integer> intCalculator = (a, b) -> a + b;
        System.out.println(intCalculator.cal(3, 4));

        Calculator<Double> doubleCalculator = (a, b) -> a + b;
        System.out.println(doubleCalculator.cal(4.32, 3.45));
    }
}

27-2. 정의되어 있는 함수형 인터페이스

표준 함수형 인터페이스의 추상 메소드 정의에 해당하는 람다식을 작성해서 전달해야한다.

- 미리 정의되어 있는 함수형 인터페이스

image

‘미리 정의해 놓은’ 함수형 인터페이스들이 있다.
메소드의 반환형과 매개변수 선언에 차이를 둔 다양한 함수형 인터페이스들을 표준으로 정의하고 있다.
위의 표준 인터페이스들을 알아야 removeIf 같은 메소드도 사용할 수 있다.

- Predicate<T>

Predicate<T> 인터페이스에는 다음 추상 메소드가 존재한다.

boolean test(T t);  // 전달된 인자를 대상으로 true, false 판단할 때
import java.util.List;
import java.util.Arrays;
import java.util.function.Predicate;

class PredicateDemo {
    public static int sum(Predicate<Integer> p, List<Integer> lst) {
        int s = 0;
        
        for(int n : lst) {
            if(p.test(n))
                s += n;
        }       
        
        return s;
    }
    
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 5, 7, 9, 11, 12);
    
        int s;
        s = sum(n -> n%2 == 0, list);
        System.out.println("짝수 합: " + s);

        s = sum(n -> n%2 != 0, list);
        System.out.println("홀수 합: " + s);

    }
}

- Supplier<T>

Supplier<T> 인터페이스에는 다음 추상 메소드가 존재한다.

T get();    // 단순히 무엇인가 반환할 때
import java.util.Random;
import java.util.List;
import java.util.ArrayList;
import java.util.function.Supplier;

class SupplierDemo {
    public static List<Integer> makeIntList(Supplier<Integer> s, int n) {
        List<Integer> list = new ArrayList<>();    
        for(int i = 0; i < n; i++)
            list.add(s.get());  // 난수를 생성해 담는다.
        return list;
    }
    
    public static void main(String[] args) {
        Supplier<Integer> spr = () -> {
            Random rand = new Random();
            return rand.nextInt(50);
        };

        List<Integer> list = makeIntList(spr, 5);
        System.out.println(list);

        list = makeIntList(spr, 10);
        System.out.println(list);
    }
}

- Consumer<T>

Consumer<T>에는 다음 추상 메소드가 존재한다.

void accept(T t);   // 전달된 인자 기반으로 '반환' 이외의 다른 결과를 보일 때
import java.util.function.Consumer;

class ConsumerDemo {
    public static void main(String[] args) {
        Consumer<String> c = s -> System.out.println(s);
        
        c.accept("Pineapple");    // 출력이라는 결과를 보임
        c.accept("Strawberry");
    }
}

- Function<T, R>

Consumer<T>에는 다음 추상 메소드가 존재한다.

// T: 매개변수형, R: 반환형
R apply(T t);   // 전달 인자와 반환 값이 모두 존재할 때
import java.util.function.Function;

class FunctionDemo {
    public static void main(String[] args) {
        Function<String, Integer> f = s -> s.length();

        System.out.println(f.apply("Robot"));
        System.out.println(f.apply("System"));
    }
}
import java.util.function.Function;

class FunctionDemo2 {
    public static void main(String[] args) {
        Function<Double, Double> cti = d -> d * 0.393701;
        Function<Double, Double> itc = d -> d * 2.54;

        System.out.println("1cm = " + cti.apply(1.0) + "inch");
        System.out.println("1inch = " + itc.apply(1.0) + "cm");
    }
}

- Predicate<T>, Supplier<T>, Consumer<T>, Function<T, R>을 구체화하고 다양화 한 인터페이스들

T를 기본 자료형으로 결정하여 정의한 인터페이스들도 존재한다.
이렇게 할 경우 박싱, 언박싱 과정이 필요없어진다.
다양하게 있으니 필요한 순간에 찾아서 써보도록 하자.

- removeIf 메소드를 사용해 보자.

Collection<E> 인터페이스에 정의되어 있는 다음 디폴트 메소드를 살펴보자.

default boolean removeIf(Predicate<? super E> filter)

위 메소드의 기능에 대한 자바 문서의 설명은 다음과 같다.
“Removes all of the elements of this collection that satisfy the given predicate”

import java.util.Arrays;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Predicate;

class RemoveIfDemo {
    public static void main(String[] args) {
        List<Integer> ls1 = new ArrayList<>(Arrays.asList(1, -2, 3, -4, 5));
        List<Double> ls2 = new ArrayList<>(Arrays.asList(-1.1, 2.2, 3.3, -4.4, 5.5));

        // Predicate의 정의에는 와일드카드를 이용한 하한제한이 걸려있기 때문에 Double과 Integer 모두 적용하기 위해 Number로 E를 결정함
        Predicate<Number> p = n -> n.doubleValue() < 0.0;

        ls1.removeIf(p);
        ls2.removeIf(p);

        System.out.println(ls1);
        System.out.println(ls2);
    }
}

Java lang 카테고리 내 다른 글 보러가기

댓글 남기기