5 분 소요

Java 8버전 부터 지원하는 기능으로 코드를 간결하고 효율적으로 작성할 수 있게 도와주는 기능이다.

기존 익명 클래스의 단점으로 메서드가 하나만 포함되는 것처럼 간단한 인터페이스를 익명 클래스의 구문을 이용해 다루기에는 비효율적이고 복잡하다는 것이다.

이런 비효율적인 인스턴스 생성을 간단하게 표현할 수 있는 것이 Lambda이며 익명 함수를 의미한다.

메서드를 작성하기 위해서는 클래스가 있어야하고, 이를 인스턴스화 해야 메소드를 사용할 수 있다.

하지만 람다는 이런 과정을 간소화 시켜주는 역할이기 때문에 람다식 자체가 메서드의 역할을 대신할 수 있다.

함수형 프로그래밍의 개념을 Java에 도입한 케이스라 할 수 있다.

작성 하기

일단 람다의 기본 구조는 아래와 같다.

(매개 변수) -> 실행 코드

매개 변수는 입력으로 받는 값을 맵핑할 수 있고, 이 Input 된 값을 표현식에서 가공하여 사용할 수 있다.

매개 변수는 ,를 기준으로 여러개를 받아올 수 있다.

만약 매개 변수를 하나도 받지 않는다면

() -> 실행 코드

로 표현하면 된다.

이제 기존의 선언부와 람다를 비교해보자


[기존 선언 방법]

[Return type] [Method name] (Parameters) {
	[Statements]
}

[Lambda식 사용]

(Parameters) -> {
	[Statements]
}

기존 선언 방법에서 [Return type] [Method name] 가 지워지고 (Parameters) 뒤에 ->가 붙었다.

익명 함수라고 소개한 것 처럼 일단 이름은 필요 없다.

이를 토대로 최고 값을 return하는 max라는 method를 만들어보자.

int max(int a, int b){
	return a > b ? a : b;
}

이를 람다식으로 만들어보자

(int a, int b) -> { return a > b ? a : b;}

끝이다.

간결하지 않은가?

하지만 여기서 몇 가지 규칙을 적용하여 같은 동작이지만 다르게 표현할 수도 있다.

return은 생략 가능하다.

동작 수행 후 return 되는 값을 return 문장 없이 람다식을 작성할 수 있다.

이 때는 { ;}과 동시에 return 이 움직인다 생각하고 return과 동시에 { ;}도 빼고 작성한다.

(int a, int b) -> a > b ? a : b

타입 추론으로 Parameter type은 생략 가능하다.

일단 타입 추론의 개념은 타입을 명시적으로 선언하지 않아도 컴파일러가 리터럴 값이나 식을 기반으로하여 필요한 타입을 자동으로 추론하는 것을 얘기한다.

이 개념을 적용시킨다면

(a, b) -> a > b ? a : b

가 된다.

매개변수가 1개인 경우 괄호는 생략 가능하다.

(int a) -> { return a*a; }

의 코드가 있다하면 위의 return 생략과 타입 추론으로 Parameter tyep을 걷어낼 수 있다.

(a) -> a*a

이렇게 말이다.

여기서 parameter가 하나이고, parameter type을 타입 추론으로 표현한 경우에는 () 또한 생략 가능하다.

a -> a*a

그럼 이제 실제 코드에서 활용해보자.

활용

Interface와 interface의 method를 익명 클래스와 익명 함수(람다식)을 이용해 구현해보겠다.

paramter가 없는 경우

public class Lambda {  
  
    //구현 되지 않은 interface    
    interface Drawable{
      public void draw();  
    }  
  
    public static void main(String[] args) {  
  
        int width=10;  
  
        // Lambda 식 없이 사용할 경우 익명 클래스로 Drawable을 구현해준다.  
        Drawable d=new Drawable(){  
            public void draw(){System.out.println("Drawing "+width);}  
        };  
        d.draw();  
  
  
        //람다식을 사용할 경우  
        //Drawable의 draw는 parameter를 받지 않으니 () -> { } 의 타입으로 구현한다.  
        Drawable d2=()->{  
            System.out.println("Drawing "+width);  
        };  
        d2.draw();  
  
    }  
  
}

[output]
Drawing 10
Drawing 10

Parameter가 여러개

public class Lambda {  
  
    interface Addable{  
        int add(int a,int b);  
    }  
  
    public static void main(String[] args) {  
  
        // 여러개의 parameter가 있을 때 (타입 추론)  
        Addable ad1=(a,b)->(a+b);  
        System.out.println(ad1.add(10,20));  
  
        // 여러개의 parameter가 있을 때 (타입 선언)  
        Addable ad2=(int a,int b)->(a+b);  
        System.out.println(ad2.add(100,200));  
  
    }  
  
}

[output]
30
300

와 같이 작성할 수 있고 여기선 타입 추론과 return 생략의 개념도 사용되었다.

Multiple Statements

return 문이 여러줄일 때 사용하는 방법이다.

public class Lambda {  
  
    @FunctionalInterface  
    interface Sayable{  
        String say(String message);  
    }  
  
    public static void main(String[] args) {  
  
        Sayable person = (message)-> {  
            String str1 = "I would like to say, ";  
            String str2 = str1 + message;  
            return str2;  
        };  
  
        System.out.println(person.say("time is precious."));  
    }  
}

여기서 @FunctionalInterface이 사용되었는데 이또한 Java 8에서 도입되었다.

함수형 인터페이스라는 것을 표시하기 위해 사용되며, 함수형 인터페이스는 추상 메서드가 하나만 정의 된 인터페이스를 의미한다.

사용함으로써 추상 메서드가 하나만 정의 되었는지 컴파일러가 규칙에 잘 지켜졌는지 검증해주는 역할을 부여한다.

Creating Thread

람다식으로 보통 Thread와 함께 활용되는 Runnable 인터페이스를 구현하는 코드를 많이 활용된다.

public static void main(String[] args) {  
  
    //익명 함수로 구현.  
    Runnable r1=new Runnable(){  
        public void run(){  
            System.out.println("Thread1 is running...");  
        }  
    };  
    Thread t1=new Thread(r1);  
    t1.start();  
      
    //람다식으로 구현  
    Runnable r2=()->{  
        System.out.println("Thread2 is running...");  
    };  
    Thread t2=new Thread(r2);  
    t2.start();  
}

[output]
Thread1 is running...
Thread2 is running...

Foreach Loop

Collection Framework의 순회와 필터링을 효율적이고 간결하게 표현하는데 사용 가능하다.

public static void main(String[] args) {  
  
    List<String> list = new ArrayList<String>();  
    list.add("ankit");  
    list.add("mayank");  
    list.add("irfan");  
    list.add("jai");  
  
    list.forEach(  
            (n) -> System.out.println(n)  
    );  
  
}

[output]
ankit
mayank
irfan
jai

위와 같이 수행되는 이유는…

ArrayList는 Collection을, Collection은 Iterable을 extends한다. forEach는 Iterable 인터페이스에 정의된 메서드이며 Parameter로 Consumer<? super T> action 를 받아오는데 Consumer는 단일 매개 변수를 받아서 return하지 않아도 되는 객체를 받아서 구현한다.

ArrayList 선언 시 String 을 제공하였고, list.add로 Collection에 담긴 단일 객체가 Cosumer로 구현되고 (n) 을 통해서 하나하나 for문을 돌며 출력이 된다…

Collection Framework는 이외에도 Comparator, Filter 등에 활용하는 방법이 있긴하지만 여기선 따로 언급하지 않겠다.


Ref.
https://www.javatpoint.com/java-lambda-expressions

태그:

카테고리:

업데이트: