java Lambda 식에 대해 알아보자!

LSM ·2022년 2월 8일
1

1. 작성 목적

사실 람다식에 대해 처음 배운건 3-1 학기 Swift 수업을 들었을 때 였다. 배우면서 swift는 정말 간결함을 위한 언어 인가? 라는 생각이 들었다^^ 그때만 해도 자바에도 람다식이 있다고는 생각지도 못했던 시절이었다 ㅎㅎㅎ
(지금 생각해보니 swift 가 함수형 프로그래밍 패러다임을 지향하여 그렇다고 생각 해본다..)

요즘 프로젝트를 진행하면서 stackoverflow를 자주 참조 하는 편인데 많은 개발자 분들께서 자바 코드를 람다식 or 메소드 참조를 이용하여 개발한 코드들을 굉장히 많이 보았다.. 더 이상은 미룰 수 없다는 마음으로 자바의 람다식에 대해 정리하고자 한다!!


2. 배경 지식

Java는 Java 8부터 람다식(Lambda Expression)을 지원하기 시작했다. 람다식은 함수적 프로그래밍 기법이라고 할 수 있으며, 익명 함수(anonymous function)을 생성하는 식이다.

Java8 부터 람다 외에도 스트림이 추가되었다. 이를 배우기 전에 이것들이 왜 도래했는가를 따져 볼 필요가 있어보였다.

자바가 기존에 없던 문법까지 만들어가면서 이런것들을 추가한건 함수형 프로그래밍을 받아들이기 위해서다.

그래서 Java8을 공부하고자하는 사람이라면 단순히 '람다랑 스트림공부해야지'가 아니라 함수형 프로그래밍을 공부해야한다고 한다!

이는 프로그래밍 자체의 패러다임이 변하기때문에 그저 추가된 문법, 추가된 API만 공부하면 되는 수준이 아니라 프로그래밍 방식 자체, 문제 해결을 위한 사고방식 자체를 기존에서 탈피해야함을 의미한다..

실제로 대용량 데이터를 처리해야 하는 경우 객체를 만드는 시간이 걸리기 때문에 객체 지향 프로그래밍 기법 보다는 함수적 프로그래밍 기법을 사용한다.

따라서, 현대적 프로그래밍은 객체 지향 프로그래밍 + 함수적 프로그래밍이라 할 수 있다.

즉 정리하자면, 자바 역시 이를 따라가기 위해 함수적 프로그래밍 패러다임을 따라가기 위해 람다식을 만들었다고 한다!

인용 출처: https://multifrontgarden.tistory.com/124


3. 자바의 람다식

인터페이스를 구현하는 방식 3가지를 통해 람다식을 이해해보자.

코드 출처 : https://velog.io/@zayson/Java-람다식Lambda-Expression

1. 람다 이전의 코드

//MaxNumber Interface
public interface MaxNumber {
    int getMaxNumber(int x, int y);
}
//MaxNumber Interface 구현 클래스
public class MaxNumberImpl implements MaxNumber {
    @Override
    public int getMaxNumber(int x, int y) {
        return x >= y ? x : y;
    }
}
public class Main {
    public static void main(String[] args) {
        //1. 인터페이스를 직접 클래스로 구현 후 메인 메소드에서 생성 후 호출
        MaxNumber maxNumber = new MaxNumberImpl();

        System.out.println(maxNumber.getMaxNumber(3,1));
    }
}
//출력 결과 : 3

위와 같은 코드 구조는 자바를 배운 사람이라면 충분히 이해할 수 있는 코드라 생각한다!

그림 출처 : https://galid1.tistory.com/509

2. 인터페이스를 익명 함수로 구현해서 사용하는 방법

public class Main {
    public static void main(String[] args) {
        //2. 익명함수로 메인 클래스 내에서 구현하여 호출 
        MaxNumber maxNumber = new MaxNumber() {
            @Override
            public int getMaxNumber(int x, int y) {
                return x >= y ? x : y;
            }
        };
        System.out.println(maxNumber.getMaxNumber(3,1));
    }
}

기존의 코드를 인터페이스를 익명 함수로 구현해서 사용하는 방법이다. 이렇게 구현 할 시 문제점은 MaxNumber 의 인스턴스를 자주 사용할 시, 매번 오버로드하여 작성하기에 코드가 굉장히 복잡해 질 수 있다고 생각할 수 있다!

그렇다면 이를 람다식으로 구현 한 것을 보자!

3. 람다식 구현

public class Main {
    public static void main(String[] args) {
        //3. 람다식을 이용하여 호출 방식
        MaxNumber maxNumber = (x, y) -> x >= y ? x : y;
        System.out.println(maxNumber.getMaxNumber(3,1));
    }
}

보이는 것 처럼 굉장히 코드가 간결 해 진 것을 확인할 수 있다!

그림 출처 : https://galid1.tistory.com/509

위에서 기존에 구현했던 구조와 위 람다식의 구조를 보며 조금 더 이해가 잘 되었으면 한다!!


그런데!! 그렇다면 람다식은 클래스 생성 없이 실행되나???

  • 익명 객체를 생성하는 람다식

자바에서는 객체 없이 메서드가 호출 될 수 없다!!!

람다식을 구현하면 익명 내부 클래스가 만들어지고, 이를 통해 익명 객체가 생성됨

이 역시 이해를 돕기 위해 코드로 설명하겠다!


@FunctionalInterface
public interface StringConcat {
	
	public void makeString(String s1, String s2);

}

 public class TestStringConcat {

	public static void main(String[] args) {

		StringConcat concat2 = (s, v)->System.out.println(s + "," + v ); 
		concat2.makeString(s1, s2);
    }
}

문자열을 연결 시키는 함수를 만든 것이 makeString 람다식이다.

그런데 사실 자바에서는 람다식을 익명 내부 클래스로 전환하여 이를 실행한다.

아래 코드를 보자!

StringConcat concat3 = new StringConcat() {
			
	@Override
	public void makeString(String s1, String s2) {
			
		System.out.println( s1 + "," + s2 );
	}
};
		
concat3.makeString(s1, s2);

람다식 관련 부분이 익명 내부 클래스로 전환되어 사용됨을 알 수 있다 ㅎㅎ

"자바에서는 객체 없이 메서드가 호출 될 수 없다" 를 다시 한번 상기 시키도록 하자 !


4. 람다식 사용 조건

  • 람다식은 함수적 인터페이스인 경우에만 사용이 가능하다.

함수적 인터페이스란?
인터페이스가 단 한개의 추상 메소드를 정의하고 있는 인터페이스를 말한다.

이는 두개의 추상 메소드를 가지고 있다면, 람다식을 사용할 수 없다는 말이다.

이때 @FunctionalInterface 어노테이션을 인터페이스에 사용하면 해당 인터페이스는 하나의 메소드만 정의 할 수 있으면 두개 이상의 메소드를 정의하면 인터페이스에서 에러를 발생시킨다.

추가적인 사용 조건은 해당링크를 통해 확인해보자

그림 출처 : https://galid1.tistory.com/509

5. 람다식 표현방법

라다식은 매개변수 + 실행문으로 구성된다.

즉 접근지정자와 반환형 모두 생략되는 구조이다.

() -> {};

() : 인터페이스의 추상메소드에 대한 매개변수
{} : 인터페이스의 추상메소드에 대한 구현체

예제를 통해 확인 해 보겠다.

예제에 사용 될 코드


public interface Calculator{
	public int cal(int num1,int num2);
}

기본 사용법 : (매개변수 타입)->{};


public static void main (String[] args){
	Calculator cal = (int num1, int num2) -> {return num1+num2;}
	System.out.println(cal.cal(1,2));
}

매개변수 타입 생략 : (매개변수)->{};


public static void main (String[] args){
	Calculator cal = (num1,num2) -> {return num1+num2;}
	System.out.println(cal.cal(1,2));
}

매개변수가 없는 경우 : ()->{};


public static void main (String[] args){
	Calculator cal = () -> {System.out.println("매개변수가 없는 경우 입니다.");}
	cal.cal();
}

매개 변수가 없다면 위와 같이 매개변수를 생략 가능하다

중괄호 생략 : ()->;


public static void main (String[] args){
	Calculator cal = (num1,num2) -> num1+num2;
	System.out.println(cal.cal(1,2));
}

실행 할 문장이 1개일 때에는 {} 을 생략 가능하다. 이때는 return 역시 생략 하여야만 한다!!

소괄호 생략 : 매개변수 ->;


public static void main (String[] args){
	Calculator cal = num1 -> num1-1;
	System.out.println(cal.cal(1));
}

매개변수가 하나이고, 실행할 문장도 하나라면 () , {} 둘다 생략 가능하다.


6. 정리

람다식이 탄생한 배경에 대해서 조금이나마 이해할 수 있었다...

함수적 프로그래밍의 목적이 이었다니!!

함수적 프로그래밍의 개념에 대해서도 알아 볼 필요가 있다고 생각한다 ㅎㅎ

추가적으로 자주 사용하는 Stream에 대해서도 추가적으로 정릴 할 계획이다!

Stream 정리가 끝나면 메서드참조 역시 공부할 필요가 있음을 느꼈다.


참고자료

  • 참고자료는 본문 내부에 삽입한 링크를 통해 확인할 수 있습니다.
  • 해당 글은 참고자료의 내용을 기반으로 작성을 진행하였습니다!
profile
개발 및 취준 일지

0개의 댓글