Java/Java

람다(Lambda)

마손리 2023. 3. 9. 19:28

람다(Lambda)

람다식(Lambda Expression)은 함수형 프로그래밍 기법을 지원하는 자바의 문법요소이다. 간단히 말해서 메서드를 하나의 ‘식(expression)’으로 표현한 것으로, 코드를 매우 간결하면서 명확하게 표현할 수 있다는 큰 장점이 있다.

(공부하면서 느낀것은 자바스크립트의 콜백(callback)함수와 굉장히 비슷했지만 자바에서는 함수형 인터페이스를 만들어주는 등 별도의 작업이 더 필요했다.)

 

비교 예제

함수형 인터페이스를 사용한 람다식 표현

public class Main {
    public static void main(String[] args) {
        int firstNum = 1;
        int secNum = 2;

        ExampleFunction example = (a, b) -> a - b; //람다식 표현
		
        //연산이후의 리턴값으로 결과값출력
        if (example.comparable(firstNum, secNum) > 0) System.out.println("첫번째 숫자가 더 큽니다.");
        else if (example.comparable(firstNum, secNum) < 0) System.out.println("두번째 숫자가 더 큽니다.");
        else System.out.println("둘의 값은 같습니다.");
    }
}
@FunctionalInterface
interface ExampleFunction {
    int comparable(int a,int b);
}

 

 

일반적인 인터페이스를 클래스로 구현

public class Main {
    public static void main(String[] args) {
        int firstNum = 1;
        int secNum = 2;

        ExampleClass example = new ExampleClass();

        if (example.comparable(firstNum, secNum) > 0) System.out.println("첫번째 숫자가 더 큽니다.");
        else if (example.comparable(firstNum, secNum) < 0) System.out.println("두번째 숫자가 더 큽니다.");
        else System.out.println("둘의 값은 같습니다.");
    }
}

interface ExampleFunction {
    int comparable(int a,int b);
}
class ExampleClass implements ExampleFunction {
    @Override
    public int comparable(int a,int b){
        return a-b;
    }
}

둘의 실행 결과는 같다. 그저 일반적인 방식으로 클래스를 하나 생성하여 인터페이스를 생성된 클래스로 구현하는 행위를 람다식으로 생략해 준것일 뿐이다.

 

 

예제) boolean타입을 이용하여 저장된 데이터와 비교

public class Main {
    public static void main(String[] args) {
        ExampleClass.compare((value)->value>2);//comparable의 반환타입인 boolean에 맞게 연산식 작성
       //1. ExampleClass의 compare 메서드 호출함
       //4. 메서드 compare에서 받은 숫자형 변수 value를 이용하여 식을 연산한뒤 boolean타입 반환
    }
}
interface ExampleFunction {
    boolean comparable(int num);
}
class ExampleClass {
    private static int[] array = new int[]{1,2,3,4,5}; //저장된 데이터
    
    	//아래의 메서드 compare와 같이, 함수형 인터페이스를 매개변수로 받게되면 처음 호출시에(현재 main메서드) 
   	//그의 메서드의 반환타입과 매개변수의 타입에 마춰 람다식을 필수로 작성해주어야한다.
    public static void compare(ExampleFunction exampleFunction){ 
   	//2. 호출받은 메서드로 이동
                        
        System.out.printf("등록된 배열과 비교하여 입력하신 식에 부합되는 값은\n [");
        for(int val:array){
            if(exampleFunction.comparable(val)){ //comparable의 매개변수타입 int
            //3. 받아온 인터페이스의 함수의 comparable을 매개변수 숫자형 데이터와 함께호출
            //5. 반환된 값을 조건으로 if문 실행
            	System.out.printf(" %s ",val);
            }
        }
        System.out.printf("] \n입니다.");
    }
}
//출력
등록된 배열과 비교하여 입력하신 식에 부합되는 값은
[ 3 4 5 ]
입니다.

람다식을 이용하여 사용자가 원하는 연산식을 작성한뒤 저장된 데이터와 연산식이 일치하는 데이터를 구하는 코드이다. 

 

진행 순서는 다음과 같다.

  1. Main클래스에서 ExampleClass의 compare메서드 호출
  2. 호출받은 메서드 ExampleClass의 compare메서드로 이동
  3. 이동된뒤 코드를 순서대로 실행하며 매개변수로 받은 ExampleFunction 함수형 인터페이스의 comparable메서드를 그의 매개변수 타입인 int에 맞는 데이터와 함께 호출
  4. 인터페이스 ExampleFunction를 거처 다시 Main클래스로 돌아가 compare메서드에서 받은 int형 데이터를 이용하여 인터페이스의 comparable메서드의 반환타입인 boolean에 맞게 연산식을 수행
  5. 다시 인터페이스 ExampleFunction을 거처 두번째 클래스의 compare메서드로 이동후 boolean타입의 반환된 값을 이용해 나머지 코드수행.
  6. for문의 반복수 만큼 3,4,5번 반복실행후 for문을 마침

위의 코드의 두번째 클래스의 compare메서드와 같이 매개변수로 함수형 인터페이스를 받게되면 처음 해당메서드를 호출한 곳에서 그 함수형 인터페이스에 작성된 메서드의 반환타입과 매개변수의 타입에 마춰 람다식을 필수로 작성해야된다.

 

 

메서드 레퍼런스

메서드 참조는 람다식에서 불필요한 매개변수를 제거하여 람다식을 더욱더 간략화할수 있다.

 

    public static void main(String[] args) {
        int[] array = new int[]{1,2,3,4,5};
        
        IntStream.of(array).forEach(System.out::println);
        IntStream.of(array).forEach((eachValue)->System.out.println(eachValue));
        // 두 표현식 모두 같은 역활을 함
    }

위의 코드처럼 입력값과 출력값의 반환타입을 쉽게 유추할수 있는경우 입력값과 출력값을 일일이 적어주지 않아도 된다.

 

public class Main {
    public static void main(String[] args) {
        int result = ExampleClass.compare(Max::max);
        ExampleClass.compare((num1,num2)->Max.max(num1,num2));
        		// 두표현식 모두 같다.
        
        System.out.println(result);
    }
}

@FunctionalInterface // 함수형 인터페이스
interface Lambda{
    int LambdaFn(int a,int b);
} // 람다식의 매개변수는 int타입으로된 2개의 데이터를 받으며 밑의 Max.max메서드도 동일,
  // 반환타입의 경우 람다식과 밑의 두 메서드모두 동일해야 메서드 레퍼런스 가능
  

class ExampleClass {
    private static int[] array = new int[]{5,6,7,8,9};
	public static int result = (int) Double.NEGATIVE_INFINITY;
    
    //Lambda를 호출할 메서드는 반환타입 및 매개변수가 그와 달라도 상관없다.
    public static void compare(Lambda lambda){ //메서드의 매개변수는 함수형 인터페이스
        for(int i =0; i<array.length-1;i++){ //내부 로직
            int compared = lambda.LambdaFn(array[i],array[i+1]); 
            //함수형 인터페이스의 메서드를 호출후 매개변수전달,
            //이후 Max.max를 호출하여 연산한뒤 int형 리턴값을 받음
            if(compared>result) result = compared;
        } 
    }
}

class Max{ //Math.max()와 동일한 기능의 메서드
    public static int max(int a, int b){  //메서드의 매개변수와 반환타입이 함수형 인터페이스와 같음
        if(a>b)return a;
        else if(a<b)return b;
        return (int) Double.NEGATIVE_INFINITY;
    }
}

//결과
9

 

 

메서드 레퍼런스를 사용하기 위해선 람다식에 사용될 두수를 비교하는메서드(Max.max)의 반환타입과 매개변수의 수와 타입이 해당 함수형 인터페이스에 정의된 메서드(Lambda.LambdaFn)의 것들과 완전히 동일해야된다. 

 

ExampleClass.compare의 경우 람다식을 호출해야하므로 매개변수는 람다식의 함수형 인터페이스(Lambda)로 고정이며 반환타입 또한 두 메서드들과 달라도 상관없다.

 

 

생성자 래퍼런스

public class Main {
    public static void main(String[] args) {
            ExampleClass.addObj(Human::new);
            // 생성자 레퍼런스
    }
}

@FunctionalInterface
interface Lambda{
    Human lambdaFn(String name, int age);
}//람다식의 매개변수와 반환타입은 같아야한다. 

class ExampleClass {
    public static ArrayList<Human> arrayList = new ArrayList<>();
    private static String[] names = new String[]{"test1","test2","test3","test4","test5"};
    private static int[] ages = new int[]{1,2,3,4,5};
    
    // 람다식을 호출하는 메서드의 반환타입이 해당 람다식과 다르다?????
    public static void addObj(Lambda lambda){
        for(int i =0; i<ages.length;i++){
            Human human = lambda.lambdaFn(names[i],ages[i]);
            arrayList.add(human);
        }
    }
}

class Human{
    String name;
    int age;
    public Human(String name, int age) {
        this.name = name;
        this.age = age;
    }// 객체생성시 자신을 반환하므로 람다식의 매개변수와 반환타입은 동일
}

매서드 래퍼런스와 마찬가지로 생성자 래퍼런스도 가능하다. 함수형 인터페이스의 메서드(Lambda.lambdaFn) 매개변수를 생성할 객체(Human)의 생성자 변수와 동일하게 해준뒤 람다식의 반환타입을 해당 생성자 객체로 정의해준다.

 

함수형 인터페이스를 호출하는 중간 클래스의 메서드(ExampleClass.addObj)를 정의해준뒤 매개변수로 람다식을 호출해주면 된다.