본문 링크 : http://blog.hartveld.com/2013/03/jdk-8-23-lambda-expressions.html
게시자 : David Hurtled
게시일 : 2013/03/22
번역자 : 조인석(isi.cho@gmail.com)
번역일 : 2014/07/12
번역을 하다가 한글로 변경하기 애매 한 경우는 의역하거나 내버려 두었습니다. 참고하세요..
조언이나 교정은 언제든지 감사한 마음으로 받겠습니다. :)
=====================================================================
JDK 8 2/3 - Lambda expressions
Posted 22nd March 2013 by David Hartveld
이 포스트는 JDK 8를 위한 짧은 시리즈 중에 두 번째이다 - 나의 이전 포스트인 default methods for interfaces (번역), 그리고 다음 포스트인 Stream API도 참고하기 바란다.
JDK 8이 Java에 제공 할 신규 기능 중 하나는 익명 함수(anonymous function)를 정의 할 수 있는 가능성을 열어 놓은 것이다. 보통 자바 월드에서는 람다(lambda) 표현식이라고 부른다. 기본적으로, 이 기능은 익명 내부 클래스 정의를 적은 코드로 간편하게 해준다. - AIC(역자주 : Architecture-Independent checkpointing의 약자로 모든 변수 타입을 명시적으로 선언해야 한다는 차원에서 나온 용어로 보여지며, 플랫폼 종속적이지 않은 Java 언어의 특징 중 하나 임(참고))s 의 장황성과 비교되는 군더더기 없는 코드는 새로운 타입의 API를 만드는 것을 가능케 한다. 이번 포스트에서는 람다 표현식의 설계, 구현 방식 그리고 사용법에 대해 조금 더 자세히 들여다 보겠다.
반면에 어떤 경우에는 명확하게 매개 변수 타입을 명시해야 하는 경우도 있다. 한 가지 예를 들자면, 함수형 인터페이스의 매개 변수 변경에 의한 오버로딩된 함수를 들 수 있겠다. 이러한 경우에는 API 설계시 람다 매개 변수의 타입을 명시한 폼(위 예제 중 concat1) 에 의해 명확성을 요구하는 API 를 사용하여 코드가 작성되었는 지를 기술 해야만 한다. Stram.flatMap 오버로딩의 모호함(Ambiguity of Stream.flatMap overloads)은 람다 개발자 메일링 리스트(lambda-dev)에서 논의 되어지고 있다. 이러한 함수 같은 경우는 오버로딩 하지 말고, 오버로딩된 함수의 이름을 변경하기를 권장한다.
이번 시리즈 중 세 번째 포스트에서는 스트림 API에 대해서 더 상세하게 알아 볼 수 있다.
Java 8 은 타 함수형 프로그래밍 언어에서 제공하는 부분적 사용 함수(partially apply functions)는 제공하지 않고 있다. Java 8은 여전히 완전한 객체 지향적이며, 명령형 프로그래밍 언어이다.
위 소스를 위한 테스트 케이스는 여기(here)를 참고하기 바란다. JUnit 과 Mockito 를 클래스패스에 추가한 뒤 실행 해야 한다.
Lambda expression types: functional interfaces
(람다 표현식 타입: 함수형 인터페이스)
람다 표현식의 타입은 단 하나의 추상 메소드를 가지고 있는 함수형 인터페이스(functional interface)라 할 수 있다. (이러한 이유로 이전에는 SAM(Single Abstract Method) 인터페이스로 불려짐) 실제 JDK 인터페이스 기반의 함수형 인터페이스의 예시를 살펴 보자.// Existing interfaces that are now considered functional interfaces:
interface java.lang.Runnable { void run(); } interface java.util.concurrent.Executor { void execute(java.lang.Runnable command); } interface java.lang.Iterable<T> { java.util.Iterator<T> iterator(); } interface java.awt.event.ActionListener { void actionPerformed(java.awt.event.ActionEvent e); } // New functional interfaces: interface java.util.function.Consumer<T> { void accept(T t); } interface java.util.function.Function<T, R> { R applly(T t); } interface java.util.function.Predicate<T> { boolean test(T t); }Runnable, Executor, Iterable, ActionListener 와 같은 일부 인터페이스들은 단 하나의 추상 메소드를 지니고 있는(함수형 인터페이스의 규칙을 따르고 있는) 기존의 인터페이스들이다. 나머지 Consumer, Function, Predicate와 같은 인터페이스들은 람다 표현식의 편리한 사용을 위하여 java.util.function 라는 이름의 신규 패키지 안에서 찾아 볼 수가 있다.
Lambda expressions (람다 표현식)
Java 7에서는 함수형 인터페이스(혹은 SAM 인터페이스)의 구현체는 익명 내부 클래스 정의를 위하여 5줄 정도의 코드로 작성 해야 한다. (내부 클래스 밖의 함수 본체 라인은 세지 않았음) - 이 거추장스러운 구문을 보자:java.util.function.BiFunction<String, String, String> concat = new java.util.function.BiFunction<String, String, String>() { @Override public String apply(String t, String u) { return t + u; } };
람다 표현식(혹은 익명 함수)은 프로그래머가 같은 구문을 무척 간결하게 작성할 수 있게 해준다. 이는 타 언어들(other programming languages)과도 무척 닮았다. 위 구문과 같은 내용의 람다 표현식을 아래의 예제를 통해 확인 해 보자. - 두 개 String 변수 연결하기(concatenation)
java.util.function.BiFunction<String, String, String> concat1 = (String s, String t) -> { return s + t; }; java.util.function.BiFunction<String, String, String> concat2 = (s, t) -> s + t;
첫 번째(concat1) 예제는 Java 8 람다 표현식의 장황한 버전이다. 대개 매개 변수 타입의 정의를 생략하여 더욱 간결하게 표현한 두 번째(concat2) 예제 역시 유효하다. 또한, 함수가 단 하나의 반환 구문을 가지는 경우, 중괄호({}) / return 키워드 / 끝 부분의 세미클론(;)의 제거가 가능하다.
반면에 어떤 경우에는 명확하게 매개 변수 타입을 명시해야 하는 경우도 있다. 한 가지 예를 들자면, 함수형 인터페이스의 매개 변수 변경에 의한 오버로딩된 함수를 들 수 있겠다. 이러한 경우에는 API 설계시 람다 매개 변수의 타입을 명시한 폼(위 예제 중 concat1) 에 의해 명확성을 요구하는 API 를 사용하여 코드가 작성되었는 지를 기술 해야만 한다. Stram.flatMap 오버로딩의 모호함(Ambiguity of Stream.flatMap overloads)은 람다 개발자 메일링 리스트(lambda-dev)에서 논의 되어지고 있다. 이러한 함수 같은 경우는 오버로딩 하지 말고, 오버로딩된 함수의 이름을 변경하기를 권장한다.
Functional interfaces and default methods (함수형 인터페이스와 기본 함수)
함수형 인터페이스는 기본 함수(default methods)를 지닐수 있다. 해서 함수형 함수는 단 하나의 추상 메소드를 가질 수 밖에 없다는 제약사항에서 벗어 날 수 있다.(기본 함수는 추상 함수가 아니기 때문이다.) 좋은 예로 Iterable는 함수형 함수이며 람다 표현식과 함께 구현되어 질 수 있다.interface java.lang.Iterable<T> { abstract Iterator<T> iterator(); default void forEach(Consumer<? super T> consumer) { for (T t : this) { consumer.accept(t); } } } java.lang.Iterable<Object> i = () -> java.util.Collection.emptyList().iterator();
Method references (함수 참조)
익명 함수로 함수형 인터페이스를 정의하는 것 대신에, 함수의 참조로써 정의 하는 것도 가능하다. 함수형 인터페이스 명세를 따르기만 한다면, 아래의 예제도 가능하다.public class MethodReferences { public void f() { } public void g() { Runnable r = this::f; r.run(); // f() is called. } }
Higher-order functions in Java (Java내의 고순위 함수)
함수형 함수의 명세를 정의한 다양한 타입과 (익명) 함수 정의를 허용함으로써, Java는 고순위 함수를 제공하기 위하여 확장되어졌다. 이는 더욱 간결한 코드 작성을 위해 여러 가지 타입의 신규 프로그램 생성자를 작성 가능 하게 하며, 무척 흥미로운 부분이다. JDK 8의 이러한 신규 기능들은 데이터스트림 중심의 프로그래밍 파라다임을 적용할 수 있게 하는 예시를 Stream API를 통하여 제공하며, 이는 .NET의 interactive extensions 혹은 스칼라의 collections API 와 닮았다 :List<String> ss = new ArrayList<>(); ss.add("x,y,z"); ss.add("x.y.z"); ss.add("a,b,c"); ss.add("i,j,k"); String result = ss.stream() .map(s -> s.replace(",", ".")) .filter(s -> !s.contains("b")) .distinct() .sorted() .collect(Collectors.toStringJoiner(",")).toString(); System.out.println("Result: " + result); == Output == Result: i.j.k,x.y.z
Java 8 은 타 함수형 프로그래밍 언어에서 제공하는 부분적 사용 함수(partially apply functions)는 제공하지 않고 있다. Java 8은 여전히 완전한 객체 지향적이며, 명령형 프로그래밍 언어이다.
Advanced example (고급 예제)
Java 8 언어 기능들로 이루어진 조금 더 복잡해 보이는 아래 예제는 이번 포스트의 최종 예제이다. 이는 IObservable 인터페이스의 무척 단순한 구현체이며, Microsoft's Reactive Extensions for .NET에서 사용하는 표준 쿼리 연산자(standard query operators)의 일부를 포함하고 있다. - 기본 함수와 많은 람다 표현식을 가지고 있는 인터페이스 :package com.hartveld.examples; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; public interface Observable<T> { void subscribe(Consumer<T> onNext); default <R> Observable<R> map(Function<T, R> mapper) { return onNext -> { subscribe(e -> { onNext.accept(mapper.apply(e)); }); }; } default Observable<T> filter(Predicate<T> filter) { return onNext -> { subscribe(e -> { if (filter.test(e)) { onNext.accept(e); } }); }; } default <R> Observable<R> bind(Function<T, Observable<R>> binder) { return onNext -> { subscribe(e -> { binder.apply(e).subscribe(onNext); }); }; } // Alternative filter implementation based on bind. default Observable<T> filter2(Predicate<T> filter) { return bind(t -> { return onNext -> { if (filter.test(t)) { onNext.accept(t); } }; }); } }
Conclusion (결론)
람다 표현식의 추가로 인해 Java 프로그래머의 도구함에는 강력하고 표현력이 강한 신규 언어 기능이 추가 되었다. 비록 위 Observable 예제와 같이 남용하게 되면 복잡성이 증가하게 되겠지만(여전히 표현성은 높다), 이 언어 기능은 많은 기존의 프로그램들을 개선하는 데 사용될 수 있다. 많은 익명 내부 클래스 초기화 구문을 제거하고, Stream API 기반의 전통적인 for 루프를 전환하는 것 만으로도 해당 코드의 가독성을 증대시키는 데 기여 할 수 있다.
Posted 22nd March 2013 by David Hartveld
=======================================================================
댓글 없음 :
댓글 쓰기