아직 익숙한 개념은 아니지만 Java 8에서 새롭게 추가된 기능 중 큰 비중을 차지하는 람다 표현식에 대해 이야기 해보려고 합니다.
람다란 무엇인가?
람다는 어떠한 행위를 나타내는 축약된 단일 메소드 클래스이며 변수 처럼 어딘가에 할당시키거나 다른 메소드들에 변수를 이용해 값을 전달 하는 것과 동일한 형태로 전달 될 수 있습니다. 솔식히.. 말로는 잘 와닿지 않습니다.. 그렇기 때문에 진행하면서 코드로 이야기를 하도록 하죠..
람다 구문
1 | input arguments -> body |
람다의 타입 : Functional Interface
자바는 형식화된 언어(Typed Language)로 필수적으로 타입을 선언하는 것이 일반적입니다. 그럼 람다의 타입은 대체 무엇인가? 라는 질문을 하지 않을 수 없는데 람다는 다음과 같이 설계 되었다고 합니다.
람다는 기존의 익명 클레스를 통해서 익숙한 익명 메소드 전략을 재활용한 것으로 새롭게 타입이 추가된 것은 아니라고 합니다. 대신 특별한 인터페이스인 Functional Interface를 갖습니다. 이는 일반적인 인터페이스와 동일하지만 다음의 추가적인 2개의 특징을 갖는다고 합니다.
- 단 하나의 추상 메소드를 갖음.
- 선택적 이지만 @FunctionalInterface 주석을 추가하여 람다 표현식으로 사용 될 수 있음(이 방식이 강력 추천 됨).
조건에 맞춰 다음과 같은 인터페이스를 정의해 보았습니다.
1 2 3 4 5 6 7 | package lambda.works; @FunctionalInterface public interface TestFunctionalInterface<T> { public T doSomething(T t1, T t2); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | package lambda.works; public class CargoWorks { private int boxQty; public CargoWorks( int boxQty) { this .boxQty = boxQty; } public int getBoxQty() { return boxQty; } public void setBoxQty( int boxQty) { this .boxQty = boxQty; } } |
1 2 3 4 5 6 7 8 9 10 11 12 | //두 스트링의 연결 TestFunctionalInterface<String> stringAdder = (String s1, String s2) -> s1 + s2; //두 수의 곱 TestFunctionalInterface<Integer> multipleNumbers = (Integer i1, Integer i2) -> i1 * i2; //두 박스의 합 TestFunctionalInterface<CargoWorks> quantityAdder = (CargoWorks c1, CargoWorks c2) -> { c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty()); return c1; }; |
TestFunctionalInterface가 generic type 인터페이스이기 때문에 서로 다른 타입으로 구현이 가능한 것을 알 수 있습니다. 결국 람다 표현식의 타입은 Functional Interface라고 볼 수 있습니다.
이렇게 정의된 것은 다음과 같이 사용 될 수 있습니다.
1 2 3 4 5 6 7 8 9 10 11 12 | package lambda.works; public class ImplFunctions { //두 스트링의 연결 TestFunctionalInterface<String> stringAdder = (String s1, String s2) -> s1 + s2; private void concatnateStrings(String s1, String s2) { System.out.println( "Concatenated result : " + stringAdder.doSomething(s1, s2)); } } |
Java 8 이전과의 차이
1 2 3 4 5 6 7 8 9 10 11 12 13 | package lambda.works; public class TestPreJava8 { TestFunctionalInterface<CargoWorks> quantityMerger = new TestFunctionalInterface<CargoWorks>() { @Override public CargoWorks doSomething(CargoWorks c1, CargoWorks c2) { c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty()); return c1; } }; } |
1 2 3 4 5 6 7 8 9 | public void preJava8Method() { TestFunctionalInterface<CargoWorks> quantityMerger = ...; CargoWorks c1 = new CargoWorks( 1000 ); CargoWorks c2 = new CargoWorks( 2000 ); CargoWorks mergedQunatiy = quantityMerger.doSomething(c1, c2); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //두 박스의 합 TestFunctionalInterface<CargoWorks> quantityAdder = (CargoWorks c1, CargoWorks c2) -> { c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty()); return c1; }; //수가 많은 박스 TestFunctionalInterface<CargoWorks> compareBoxes = (CargoWorks c1, CargoWorks c2) -> { if (c1.getBoxQty() > c2.getBoxQty()) { return c1; } else { return c2; } }; //또 다른 박스관련 작업(람다에서는 기존에 존재하는 다른 메소드(여기에선 thatThingYouDo)도 사용이 가능합니다) TestFunctionalInterface<CargoWorks> otherJobs = (CargoWorks c1, CargoWorks c2) -> thatThingYouDo(c1, c2); |
1 2 3 | private void applyBehavior(TestFunctionalInterface<CargoWorks> applySomething, CargoWorks c1, CargoWorks c2) { applySomething.doSomething(c1, c2); } |
Functional Interface인 Runnable
단일 메소드를 갖는 인터페이스 중 가장 있기 있는 걸 꼽으라면 아마도 Runnable일 것 입니다. 아무것도 반환하지 않는 run 메소드를 갖고 있는데 보통 쓰레드를 이용해서 프로그램의 성능을 향상시키기 위해 많이 사용되는 인터페이스 입니다.
기존에 익명클래스 방식을 사용한 코드는 다음과 같습니다.
1 2 3 4 5 6 7 | new Thread( new Runnable() { @Override public void run() { doSomething(); } }).start(); |
이것을 람다 표현식을 사용하면 다음과 같이 사용할 수 있습니다.
1 | new Thread(() -> doSomething()).start(); |
이것을 조금 더 응용해본다면 다음과 같은 코드도 작성할 수 있습니다.
1 2 3 4 5 6 7 | package lambda.works; public class AsyncManager { public void runAsync(Runnable r) { new Thread(r).start(); } } |
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 32 33 34 35 36 37 | package lambda.works; public class TestFunctions { private AsyncManager manager = new AsyncManager(); //두 스트링의 연결 TestFunctionalInterface<String> stringAdder = (String s1, String s2) -> s1 + s2; //두 수의 곱 TestFunctionalInterface<Integer> multipleNumbers = (Integer i1, Integer i2) -> i1 * i2; //두 박스의 합 TestFunctionalInterface<CargoWorks> quantityAdder = (CargoWorks c1, CargoWorks c2) -> { c1.setBoxQty(c1.getBoxQty() + c2.getBoxQty()); return c1; }; private void takeResults() { CargoWorks cargoNoOne = new CargoWorks( 1000 ); CargoWorks cargoNoTwo = new CargoWorks( 2000 ); manager.runAsync(() -> System.out.println( "Running in Async mode" )); manager.runAsync(() -> { for ( int i = 0 ; i < 100 ; i++) { System.out.println( "just counting : " + i); } }); manager.runAsync(() -> System.out.println( "String : " + stringAdder.doSomething( "A" , "b" ))); manager.runAsync(() -> System.out.println( "Number : " + multipleNumbers.doSomething( 1 , 2 ))); manager.runAsync(() -> System.out.println( "Qty : " + quantityAdder.doSomething(cargoNoOne, cargoNoTwo).getBoxQty())); } public static void main(String[] args) throws Exception { new TestFunctions().takeResults(); } } |
마치며..
개인적으로 람다 표현식은 기존의 Java 프로그래밍에 꽤 많은 변화를 가져오지 않을까 생각됩니다. 아마도 병렬 프로그래밍과 비동기 프로그래밍이 보편화된 시대의 도래를 촉진하겠죠.. 아직 람다에 대해 많은 것을 알지는 못하지만 Java 8에서 가장 핫한 녀석인것 같다는 생각입니다.