JAVA8 을 눈여겨봐야 하는 이유1 (메서드에 코드를 전달하는 기법)
포스트의 제목은 현재 읽고 있는 책인 "JAVA8 IN ACTION"의 첫 목차입니다.
자바8의 업데이트가 가장 빛나는 이유 중 하나는 함수형프로그래밍을 할 수 있기 때문이라 할 수 있을 것 같습니다.
이를 지원하기 위해 여러 문법이 추가되었고, 향후 해당 코드들이 많이 쓰일 수 있기 때문에 봐야할 것이라는 생각이 드네요.
안드로이드 (안드로이드 N은 된다고 합니다. ㅡㅡ^) 나, 현재 회사 내에서 사용하는 spring 프레임워크에서는 JAVA8을 사용하고 있지 않지만 향후 몇년 안에 소스 상에 있는 함수형 문법을 많이 보게 될지도 모르죠 ㅎㅎ .
(벌써 자바스크립트는 많이 나왔죠..)
그러나 해당 문법들이 단순히 알고있던 JAVA 문법으로만 보고 해석하기엔 난해합니다. ㅡㅡ^ (쌩뚱맞는 문법들이 많네용.... 모르면 뭐하는 놈인지 몰라용)
다른 언어들에는 이미 함수형 프로그래밍과 관련된 것들이 나왔는데 (파이썬의 lambda 등...) 자바에서 이번 버전에 이를 지원해주니 한번 주의 깊게 봐야할 필요가 있습니다.
크게 봐야할 내용 중 첫번째는 "메소드에 코드를 전달하는 것"인데요.
이 개념이 목차만 봤지만 함수형 프로그래밍의 가장 중요한 요소인 것 같아 먼저 적어봤습니다.
"메소드에 코드를 전달한다"는 내용은 생각보다 큽니다. 이는 단순히 코드의 간결성뿐만 아니라, 아랫단(스레드라던지, 메모리라던지...)의 문제를 쉽게 해결할 수 있는 것 처럼 보입니다.
일단 "메소드에 코드를 전달한다" 는 내용은 무슨말인지도 모르겠네요.
조금 풀어서 설명하면, 여태까지 넘기던 파라미터, 즉 원시값(int, double)이나 참조값(인스턴스에의 참조 값) 외에 함수라는 자체 역시 코드화하여 넘기자는 이야기인데요.
이러한 기능을 통해 우리는 동작 파라미터화(behavior parameterization) 를 쉽게 처리 할 수 있습니다.
말로하면 어려우니, 자바8의 예제를 조금보자면
1 2 3 4 5 6 7 | public class Apple { private String color; public String getColor(){ return color; } } | cs |
다음과 같이 Apple 클래스를 정의하고, 이를 분류하기 위하여 GetFilterApple 이라는 함수를 정의했다고 합시당.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * 사과 객체를 분류하기 위한 함수 * * @param appleList * @return */ public static List<Apple> GetFilterApple(List<Apple> appleList) { ArrayList<Apple> resultList = new ArrayList<>(); for (Apple apple : appleList) { if ("green".equals(apple.getColor())) { resultList.add(apple); } } return resultList; } | cs |
보기에는 단순해 보이는 코드이지만 여러 요구사항에 따라 필터로 하자는 조건이 늘어날 수 있습니다.
(ㅡㅡ^ 변화를 사랑하는 개발자가 됩시다..)
그때마다 우리는 분류조건을 늘릴 것인가요?
(선택지가 두개정도 보이네요...)
1. copy&paste
잘하는 것...
각 메소드의 존재 이유는 해당 역할을 하기 위한 것이니, 위의 GetFilterApple 이라 하지말고,
GetFilterAppleForColor, GetFilterAppleForHeavy 등을 만들고 조건에 맞는 것을 Set으로 넘기면 되지 않나요?
아래와 같이요..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * 사과 객체를 분류하기 위한 함수 * * @param appleList * @param wannaColor * @return */ public static List<Apple> GetFilterApple(List<Apple> appleList, Set<String> wannaColor) { ArrayList<Apple> resultList = new ArrayList<>(); for (Apple apple : appleList) { if (wannaColor.contain(apple.getColor())) { resultList.add(apple); } } return resultList; } | cs |
명확한 해결일 수 있지만, copy&paste의 단점이라 할 수 있는, 한곳에 문제가 생기면 모두 변경해야한다. 아니 좋은 것 같네요....
또한 이를 위해 끊임없는 메소드 를 정의해야할 것 같아 보입니다.
2. 일을 조금 더 추상화해보자.
일단 GetFilterApple이 filter라는 역할을 한다는 것에 중점을 맞출 때 변경될만한 부분은 필터 조건입니다. 이를테면 이 놈 -> "green".equals(apple.getColor())
이 부분을 추상화하고, 필요할 때마다 구체화시켜주면 해결될 문제자나요.
OOP 에 많이 나오는 건데..
변하는 부분과 변하지 않는 부분을 분리하자.
맞습니다. 저 원리는 객체지향 개발론 책 한번이라도 봤으면 있는 원리 중 하나입니다..
이를 테면 다음과 같이 작업해 볼 수 있을 것 같네요.
사과를 분류하기 위한 인터페이스를 정의하고,
1 2 3 4 5 6 7 8 9 10 | /** * 사과를 분류하기 위한 인터페이스 * * @author Doohyun * */ public interface AppleFilterAble { public boolean isContain(Apple apple); } | cs |
위에서 작성한 GetFilterApple이 해당 인터페이스를 사용하도록 하게 할게요. 이렇게 말이죠..
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * 사과 객체를 분류하기 위한 함수 * * @param appleList * @param appleFilter * @return */ public static List<Apple> GetFilterApple(List<Apple> appleList, AppleFilterAble appleFilter) { ArrayList<Apple> resultList = new ArrayList<>(); for (Apple apple : appleList) { if (appleFilter.isContain(apple)) { resultList.add(apple); } } return resultList; } | cs |
해당 메소드의 호출은 다음과 같이 익명 클래스를 정의하면 변화에 유연할 수 있을 것 같아 보이는 군요. 아래처럼 말이죠.
1 2 3 4 5 6 | GetFilterApple(inventory, new AppleFilterAble(){ @Override public boolean isContain(Apple apple){ return "green".equals(apple.getColor()); } }); | cs |
좋은 방법입니다.
하지만 이를 위해 인터페이스를 만들어야하고, 인스턴스를 생성해야합니다.
귀차니즘......
3. 함수형 프로그래밍을 해보자
함수형프로그래밍에서 언급하는 메서드에 코드를 전달하는 기법의 대표적인 것은 함수 자체도 값으로써, 넘기자는 것입니다.
여태까지 자바에서는 이를 지원하지 않았고, 자바8에서 비로소 지원을 할 수 있게 되었는데 이렇게 사용이 가능합니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | public class Apple { private String color; public String getColor(){ return color; } /** * 초록색 사과인가를 분류하고자 함. * * @param apple * @return */ public static boolean IsGreenApple(Apple apple) { return "green".equals(apple.getColor()); } } | cs |
다음과 같이 IsGreenApple이라는 static 메소드를 선언 후,
GetFilterApple을 JAVA8에서 새롭게 추가된 Predicate interface를 이용하여 다음과 같이 작성해줍니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | /** * 사과 객체를 분류하기 위한 함수 * * @param appleList * @param p * @return */ public static List<Apple> GetFilterApple(List<Apple> appleList, Predicate<Apple> p) { ArrayList<Apple> resultList = new ArrayList<>(); for (Apple apple : appleList) { if (p.test(apple)) { resultList.add(apple); } } return resultList; } | cs |
"JAVA8 IN ACTION"에 따르면,
Predicate 란 수학에서 true나 false를 반환하는 함수로써,
이를 사용하는 것이 표준적인 방식이라 합니다. 또한 boolean을 Boolean으로 변환하지 않으니 효율적입니다.
호출은 아래와 같이 진행을 합니다.
1 | GetFilterApple(inventory, Apple::IsGreenApple); | cs |
매우 simple해졌네요. filter 역할을 해주는 static 메소드를 정의해 사용하면 됩니다.
그런데, 함수형 프로그래밍을 하려면 static 메소드를 정의해야 하나요? 그렇다면 일일이 static 메소드를 또 만들어야하는데.. 1번의 copy&paste와 같은 문제가 있지 않나요?
4. 람다표현식
변화에 유연하게 대처하기 위해, 2번에서는 익명 클래스를 선언하여 해야할 일을 정의했습니다. 함수형에서는 이를 위해 존재하는 것이 람다 표현식이라 할 수 있을 것 같네요.
다음과 같이 말이죠.
1 | GetFilterApple(inventory, (Apple a) -> "green".equals(a.getColor())) | cs |
즉 한번만 사용될 메소드를 위해 함수를 정의하지 않고, 간단히 람다로 처리할 수 있습니다. 그러나 식 자체가 복잡해 진다면 함수를 따로 선언하고 넘기는 것을 권장합니다.
* 이로써 나오는 효과들은?
1. 코드가 간결해지고, interface 등을 따로 만들지 않아도 됩니다. (귀찮은 일이 줄었다..)
이 것은 객체로 메소드를 감지 않고, 자바8의 메소드 레퍼런스 문법을 이용하여 직접 보낼 수 있음을 의미합니다.
2. 함수를 값으로써, 넘기겠다는 말은 프로그램이 실행되는 동안 컴포넌트 간 상호작용이 일어나지 않는다는 뜻이 될 수 있다고 합니다.
이는 후에 stream에서 언급할 주제로, 병렬성에서 이득을 볼 수 있는 여지를 줄 수 있는 것 처럼 보이네요.
(자바8에서의 병렬성에 대한 이야기는 매우 중요합니다.)
|
'개발이야기 > 함수형 프로그래밍' 카테고리의 다른 글
동작 파라미터화와 함수형 프로그래밍 (2) (0) | 2016.07.25 |
---|---|
동작 파라미터화와 함수형 프로그래밍 (1) (0) | 2016.07.25 |
JAVA8 을 눈여겨봐야 하는 이유3 (기타 볼 내용) (0) | 2016.07.22 |
JAVA8 을 눈여겨봐야 하는 이유2 (스트림 API 등장) (0) | 2016.07.21 |
함수형 프로그래밍 시작 (0) | 2016.07.21 |