Notice
Recent Posts
Recent Comments
Link
«   2025/03   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31
Tags
more
Archives
Today
Total
관리 메뉴

랩터

[Java] 람다 본문

공부/JAVA

[Java] 람다

raptorhs 2024. 7. 5. 10:43

람다식(Lambda Expression)은 함수형 프로그래밍 기법을 지원하는 자바의 문법요소입니다.

람다식은 간단히 말해서 메서드를 하나의 ‘식(expression)’으로 표현한 것으로, 코드를 매우 간결하면서 명확하게 표현할 수 있다는 큰 장점이 있습니다.

 

학습 목표

  • 람다식이 무엇이고, 어떻게 사용할 수 있는지 이해할 수 있다.
  • 함수형 인터페이스를 통해 람다를 다루는 방법을 이해하고 설명할 수 있다.
  • 람다식을 메서드 참조 방식으로 변환할 수 있다.
  • 스트림(Stream)은 배열, 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자입니다.

 

람다식의 기본 문법

//기존 메서드 표현 방식
void sayhello() {
	System.out.println("HELLO!")
}

//위의 코드를 람다식으로 표현한 식
() -> System.out.println("HELLO!")

 

람다식에서는 기본적으로 반환타입과 이름을 생략할 수 있습니다.

 

// 기존 방식
int sum(int num1, int num2) {
	return num1 + num2;
}

// 람다식
(int num1, int num2) -> {
	num1 + num2
}

이 sum메서드는 내용을 좀더 축약할 수 있습니다.

 

  먼저, 메서드 바디에 문장이 실행문이 하나만 존재할 때 우리는 중괄호와 return 문을 생략할 수 있습니다. 이 경우, 세미콜론까지 생략해야 합니다.

  두 번째로, 매개변수 타입을 함수형 인터페이스를 통해 유추할 수 있는 경우에는 매개변수의 타입을 생략할 수 있습니다. 함수형 인터페이스에 대해서는 다음 콘텐츠에서 학습합니다.

(num1, num2) -> num1 + num2

 

함수형 인터페이스

자바에서 함수는 반드시 클래스 안에서 정의되어야 하므로 메서드가 독립적으로 있을 수 없고 반드시 클래스 객체를 먼저 생성한 후 생성한 객체로 메서드를 호출해야 합니다. 메서드와 동일시 여겼던 람다식 또한 사실은 객체입니다. 이름이 없기 때문에 익명 객체라 할 수 있습니다.

익명 객체는 익명 클래스를 통해 만들 수 있는데, 익명 클래스란 객체의 선언과 생성을 동시에 하여 오직 하나의 객체를 생성하고, 단 한번만 사용되는 일회용 클래스입니다.

new Object() {
	int sum(int num1, int num2) { // 생성과 선언 한번에 가능
		return num1 + num2;
	}
}

 

기존에 객체를 생성할 때 만들었던 Object 클래스에는 sum이라는 메서드가 없으므로, Object 타입의 참조변수에 담는다고 하더라도 sum 메서드를 사용할 수 없습니다.

public class LamdaExample1 {
    public static void main(String[] args) {

        // 람다식 Object obj = (num1, num2) -> num1 + num2; 로 대체 가능
        Object obj = new Object() {
            int sum(int num1, int num2) {
                return num1 + num1;
            }
        };

        obj.sum(1, 2);
    }
}

출력 결과
java: cannot find symbol
  symbol:   method sum(int,int)
  location: variable obj of type java.lang.Object

이러한 문제를 해결하기 위해 사용하는게 바로 함수형 인터페이스라 할 수 있습니다.

 

함수형 인터페이스에는 단 하나의 추상 메서드만 선언될 수 있는데, 이는 람다식과 인터페이스의 메서드가 1:1로 매칭되어야 하기 때문입니다. 

 

public class LamdaExample1 {
    public static void main(String[] args) {
        /* Object obj = new Object() {
            int sum(int num1, int num2) {
                return num1 + num1;
            }
        };
        */
        ExampleFunction exampleFunction = (num1, num2) -> num1 + num2;
        System.out.println(exampleFunction.sum(10,15));
}

@FunctionalInterface // 컴파일러가 인터페이스가 바르게 정의되었는지 확인하도록 합니다.
interface ExampleFunction {
		int sum(int num1, int num2);
}

// 출력값
25

 

매개변수와 리턴값이 없는 람다식

다음과 같이 매개변수와 리턴값이 없는 추상 메서드를 가진 함수형 인터페이스가 있다고 가정해 보겠습니다.

@FunctionalInterface
public interface MyFunctionalInterface {
    void accept();
}

 

람다식에서 매개변수가 없는 이유는 accept()가 매개변수를 가지지 않기 때문입니다. 람다식이 대입된 인터페이스의 참조 변수는 위의 주석과 같이 accept() 를 호출할 수 있습니다. accept() 의 호출은 람다식의 중괄호 {}를 실행시킵니다.

 

@FunctionalInterface
interface MyFunctionalInterface {
    void accept();
}

public class MyFunctionalInterfaceExample {
    public static void main(String[] args) throws Exception {
        MyFunctionalInterface example = () -> System.out.println("accept() 호출");
        example.accept();
    }
}

// 출력값
accept() 호출

 

매개변수가 있는 람다식

다음과 같이 매개 변수가 있고 리턴값이 없는 추상 메서드를 가진 함수형 인터페이스가 있다고 가정해 보겠습니다.

@FunctionalInterface
public interface MyFunctionalInterface {
    void accept(int x);
}
public class MyFunctionalInterfaceExample {

    public static void main(String[] args) throws Exception {

        MyFunctionalInterface example;
        example = (x) -> {
            int result = x * 5;
            System.out.println(result);
        };
        example.accept(2);

        example = (x) -> System.out.println(x * 5);
        example.accept(2);
    }
}

// 출력값
10
10

람다식에서 매개변수가 한 개인 이유는 추상메서드 accept()가 매개변수를 하나만 가지기 때문입니다.

 

리턴값이 있는 람다식

다음과 같이 매개 변수와 리턴값을 가지는 추상 메서드를 포함하는 함수형 인터페이스가 있습니다.

@FunctionalInterface
public interface MyFunctionalInterface {
    int accept(int x, int y);
}

람다식에서 매개 변수가 두 개인 이유는 accept()가 매개변수를 두 개 가지기 때문입니다.

또한, accept()가 리턴 타입이 있기 때문에 중괄호 { }에는 return 문이 있어야 합니다.

 

package Lambda;

public class MyFunctionalInterfaceExample {
    public static void main(String[] args) throws Exception{
        MyFunctionalInterface example;

        example = (x,y) -> {
            int result = x + y;
            return result;
        };
        int result1 = example.accept(2, 5);
        System.out.println(result1);

        example = (x, y) -> {return x + y;};
        int result2 = example.accept(2, 5);
        System.out.println(result2);

        example = (x,y) -> x + y;
        //return문만 있으면, 중괄호 {}와 return문 생략 가능
        int result3 = example.accept(2, 5);
        System.out.println(result3);

        example = (x,y) -> sum(x,y);
        //return문만 있으면, 중괄호 {}와 return문 생략 가능
        int result4 = example.accept(2, 5);
        System.out.println(result3);
    }
    public static int sum(int x, int y){
        return x + y;
    }
}

 

메서드 레퍼런스

메서드 참조는 람다식에서 불필요한 매개변수를 제거할 때 주로 사용합니다.

메서드 참조는 정적 혹은 인스턴스 메서드를 참조할 수 있고 생성자 참조도 가능합니다.

정적 메서드와 인스턴스 메서드 참조

정적 메서드를 참조할 때는 클래스 이름 뒤에 :: 기호를 붙이고 정적 메서드 이름을 기술하면 됩니다.

//Calculator.java
public class Calculator {
  public static int staticMethod(int x, int y) {
                        return x + y;
  }

  public int instanceMethod(int x, int y) {
   return x * y;
  }
}
import java.util.function.IntBinaryOperator;

public class MethodReferences {
  public static void main(String[] args) throws Exception {
    IntBinaryOperator operator;

    /*정적 메서드
		클래스이름::메서드이름
		*/
    operator = Calculator::staticMethod;
    System.out.println("정적메서드 결과 : " + operator.applyAsInt(3, 5));

    /*인스턴스 메서드
		인스턴스명::메서드명
		*/

    Calculator calculator = new Calculator();
    operator = calculator::instanceMethod;
    System.out.println("인스턴스 메서드 결과 : "+ operator.applyAsInt(3, 5));
  }
}
/*
정적메서드 결과 : 8
인스턴스 메서드 결과 : 15
*/

 

생성자 참조

메서드 참조는 생성자 참조도 포함합니다.

생성자를 참조한다는 것은 객체 생성을 의미합니다

 

생성자 참조로 표현하면 다음과 같습니다. 클래스 이름 뒤에 ::기호를 붙이고 new 연산자를 기술하면 됩니다.

클래스 :: new

생성자가 오버로딩되어 여러 개가 있으면 컴파일러는 함수형 인터페이스의 추상 메서드와 동일한 매개 변수 타입과 개수가 있는 생성자를 찾아 실행합니다.

//Member.java
public class Member {
  private String name;
  private String id;

  public Member() {
    System.out.println("Member() 실행");
  }

  public Member(String id) {
    System.out.println("Member(String id) 실행");
    this.id = id;
  }

  public Member(String name, String id) {
    System.out.println("Member(String name, String id) 실행");
    this.id = id;
    this.name = name;
  }

  public String getName() {
    return name;
  }

public String getId() {
    return id;
  }
}

 

package Lambda;

import java.util.function.BiFunction;
import java.util.function.Function;

public class ConstructorRef {
    public static void main(String[] args) throws Exception{
        Function<String, Member> function1 = Member::new;
        Member member1 = function1.apply("kimcoding");

        BiFunction<String, String, Member> function2 = Member::new;
        Member member2 = function2.apply("kimcoding", "김코딩");
    }
}
/*
Member(String id) 실행
Member(String name, String id) 실행
*/

 

Function<String, Member> 함수형 인터페이스의 Member apply(String) 메서드를 이용해서 Member 객체를 생성하고, 다른 하나는 BiFunction<String, String, Member> 함수형 인터페이스의 Member 객체를 생성합니다.

'공부 > JAVA' 카테고리의 다른 글

[Java] 스트림 - 스트림의 생성  (0) 2024.07.08
[Java] 스트림 - 핵심 개념과 특징  (0) 2024.07.08
[Java] 컬렉션 프레임워크 (Collection Framework)  (0) 2024.07.04
[Java] 예외 처리  (0) 2024.07.03
[Java] 제네릭  (0) 2024.07.03