[자바] 람다와 메서드체이닝(feat. FunctionalInterface)

tech_bae·2025년 3월 17일

Java

목록 보기
7/10
post-thumbnail

람다(Lambda)

  • 람다는 익명 함수라고도 한다.
  • 함수를 간결하게 표현하는 방법
  • 코드의 가독성이 좋아짐

한 번만 사용할 메소드를 일일히 정의하면 정의할 메소드가 너무 많아지지 않겠나!

그럴때 사용하기 좋은 것이 람다이다. 본인은 일회용 메소드 비슷하게 이해하고 있다.

익명 클래스 사용

  • 익명클래스란 이름이 없는 클래스
  • 클래스를 정의함과 동시에 인스턴스를 생성할 때 사용

람다가 없으면 있는 선택지는 익명클래스를 사용하는 겁니다.

Consumer<String> c = new Consumer<String>() {

    @Override
    public void accept(String s) {
        System.out.println("Hello, " + s);
    }
};

c.accept("Lambda!");

이렇게 일회용 메소드를 익명클래스를 이용해서도 사용할순 있다만, 너무 복잡하고 무엇보다 쓰기가 너무 귀찮습니다.

단, 익명클래스는 여러개의 메서드를 가질 수 있다는 장점?이 있습니다.(이럴 거면 그냥 클래스를 하나 만드는게..)

이런 귀찮음을 해결 할 수 있는게 람다입니다

람다의 구성

일단 람다가 어떻게 생겨먹었는지부터 보자


(매개변수,매개변수){
            표현식
        }
        
//매개변수 하나일때 () 생략가능        
매개변수 -> {
    표현식
}

//한 줄이면 {}도 생략가능, return쓰면 안됌(암시적 반환)
매개변수 -> 표현식

람다는 연산자를 사용한다.

좌측에 있는 매개변수가 우축에 있는 명령문에 들어가 연산수행 가능

이 람다는 메소드와 동일하게 반환값을 변수에 할당할 수 있다.

추가로 람다식이 한줄일때 반환값이 있더라도 return을 명시하면 문법 오류 발생함.

(여러줄 일땐 {}과 함께 return 필수)

Function<Integer, Integer> square = x -> return x * x; - ❌

Function<Integer, Integer> square = x -> x * x; - ⭕

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

  • 함수형 인터페이스는 하나의 추상 메서드만을 가지는 인터페이스
  • 람다는 함수형 인터페이스의 추상 메서드를 구현하면서 사용합니다. ⇒ 람다 표현식으로 간단하게 이 하나의 추상 메서드를 구현!!
  • 함수형 인터페이스에는 Consumer, Supplier, Function, Runnable 등이 있다.
    • 물론 우리가 직접 함수형 인터페이스를 만들 수도 있다.(@FunctionalInterface)

Consumer

@FunctionalInterface
public interface Consumer<T> {

    void accept(T t);
  • 함수형 인터페이스 Consumeraccept()라는 추상 메소드를 가진다.
  • accept() 는 입력값만 있고 반환을 하지 않는다. 즉, 소비만 한다.
Consumer<String> AnonymousConsumer  = new Consumer<String>() {
    @Override
    public void accept(String s) {
        System.out.println("Hello, " + s);
    }
};
AnonymousConsumer.accept("익명클라스~!");

Consumer<String> MultiLambda = (s) -> {
    System.out.println("Hello, " + s);
};

MultiLambda.accept("여러줄 람다~");

Consumer<String> SingleLambda = s -> System.out.println("Hello, " + s);
SingleLambda.accept("한 줄 람다~");

/*
출력
Hello, 익명클라스~!
Hello, 여러줄 람다~
Hello, 한 줄 람다~
*/

Supplier

public interface Supplier<T> {

    T get();
}
  • Supplierget()이라는 추상 메소드를 가짐
  • get()은 매개변수를 받지 않고 반환만 한다. 즉, 공급만 한다.
Supplier<String> justStringOut = () -> "Supplying..";
String str = justStringOut.get();
System.out.println(str);

Supplier<Integer> randomNumberSupplier = () -> (int) (Math.random() * 100);
for (int i = 0; i <= 5; i++){
    System.out.println(randomNumberSupplier.get());
}
/*
출력
Supplying..
31
84
64
16
50
43
*/

Function<T, R>

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
  • apply() 라는 추상 메서드 가짐
  • 이름처럼 매개변수를 받아서 적용시키고 반환함 → <T, R> 모두 지정해야 한다.
Function<Integer, Integer> square = x -> x * x;
Integer applied = square.apply(5);
System.out.println(applied);

Function<String, String> upperCase = x -> x.toUpperCase();
String Uppercase = upperCase.apply("lambda");
System.out.println(Uppercase);

/*
출력
25
LAMBDA
*/

Runnable

@FunctionalInterface
public interface Runnable {

    void run();
  • 매개변수도 없고, 반환도 안함
  • 멀티쓰레드를 구현할 수 있게함(Thread클래스가 Runnable구현)
    • Thread의 start()를 호출 → 새로운 쓰레드가 생성 → run()이 백그라운드에서 실행
    • 사용법: Thread t = new Thread(runnable); t.start(); ⇒ 멀티코어 CPU에서는 실제로 동시에 병렬 실행! (싱글코어에서는 그런 것처럼 보임)
Runnable runnable = () -> System.out.println("단지 실행할 뿐..");
runnable.run();

//출력 : 단지 실행할 뿐..
Runnable task = () -> {
    for (int i = 0; i < 5; i++) {
        System.out.println(Thread.currentThread().getName() + ": " + i);
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
};

Thread thread1 = new Thread(task);
Thread thread2 = new Thread(task);

thread1.start();
thread2.start();

/*
출력
Thread-0: 0
Thread-1: 0
Thread-0: 1
Thread-1: 1
Thread-0: 2
Thread-1: 2
Thread-0: 3
Thread-1: 3
Thread-0: 4
Thread-1: 4
*/

이처럼 RunnableThread에 전달하여 멀티쓰레드가 실행된다.

메서드 체이닝

  • 객체에서 여러 개의 메서드를 연속적으로 호출 하는 것
  • 점(.)을 이용해 연속호출

밑에서 대표적인 메서드 체이닝 방법 두개를 소개해 보겠다.

객체 자신을 반환 (this 반환)

public class MethodChaining {
    public String name;
    public int age;
    public boolean isGay;
    public String gay;

    public MethodChaining setName(String name) {
        this.name = name;
        return this;
    }

    public MethodChaining setAge(int age) {
        this.age = age;
        return this;
    }

    public MethodChaining setGay(boolean isGay) {
        this.isGay = isGay;
        gay = (isGay) ? "게이" : "게이아님";
        return this;
    }

    public MethodChaining show() {
        System.out.println("이름: " + name + " 나이: " + age + " 게이?: " + gay);
        return this;
    }
}
public class LambdaTest {
    public static void main(String[] args) {

        new MethodChaining()
                .setName("홍석천")
                .setAge(54)
                .setGay(true)
                .show();
    }
}
/*
출력
이름: 홍석천 나이: 54 게이?: 게이
*/

이런식으로 같은 객체에서 this를 반환하는 메소드들을 연속해서 호출할 수 있다.

andThen()

ConsumerFunction함수형 인테페이스에는 사실 accept()말고도 하나의 메소드가 더 있다.

public interface Consumer<T> {

   
    void accept(T t);

    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
@FunctionalInterface
public interface Function<T, R> {

    R apply(T t);

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

두 함수형 인터페이스의 andThen을 잘보라!

모두 자기 자신을 반환하고 있다!(Function의 compose도 마찬가지다.)

즉, 이 두 함수형 인터페이스는 andThencompose(Function)를 통해서 메서드 체이닝이 가능하다.

//첫번째 방법
Consumer<String> consumer1 = (s) -> System.out.println("첫 번째 람다!: " + s);
Consumer<String> consumer2 = consumer1.andThen((s) -> System.out.println("두 번째 람다!: " + s));

consumer2.accept("메서드 체이닝!");

//두번때 방법
Consumer<String> consumer1 = (s) -> System.out.println("첫 번째 람다!: " + s);
Consumer<String> consumer2 = (s) -> System.out.println("두 번째 람다!: " + s);

Consumer<String> chaining = consumer1.andThen(consumer2);
chaining.accept("메서드 체이닝!");
/*출력
첫 번째 람다!: 메서드 체이닝!
두 번째 람다!: 메서드 체이닝!
*/
Function<Integer, Integer> function1 = i -> i * 2;
Function<Integer, Integer> function2 = i -> i + 10;

Function<Integer, Integer> answer = function1.andThen(function2);
Function<Integer, Integer> composed = function1.compose(function2);

System.out.println(answer.apply(3));
System.out.println(composed.apply(3));
/*
출력
16
26
*/

위와 같이 Functioncompose()andThen()실행순서가 반대이다.

andThen : 왼쪽실행 후 오른쪽실행

compose: 오른쪽 실행 후 왼쪽 실행

번외: 나의 착각

사실 위에 this를 반환하는 메소드들이 연속으로 호출이 가능한건 점(.)왼쪽의 메소드의 반환타입과 점(.) 오른쪽의 메소드의 매개변수 타입이 같기 때문에 메소드 체이닝이 가능하다고 생각해서

같은 원리로 this를 반환하지 않아도 반환타입가 매개변수 타입만 만족한다면 메소드 체이닝은 가능한게 아닌가..? 라고 생각했는데 뭐.. 되긴되는데 이건 메서드체이닝이 아니었다.

⇒ 걍 메서드를 중첩 호출하는 거임

public class Main {
    public static String methodA() {
        return "hello";
    }

    public static int methodB(String input) {
        return input.length();
    }

    public static void main(String[] args) {
        int result = methodB(methodA()); //이건 그냥 메서드 중첩 호출이다...
        System.out.println(result);
    }
}
profile
전 아무고토 몰루고 아무고토 못해여

0개의 댓글