익명 함수. Anonymous function. 이름없이 사용되는 형태.
특징으로는 메서드의 인수로 전달될 수 있고, 변수로 저장될 수 있음.
기본적인 표현의 구성
(People p1, People p2) -> p1.getAg().compareTo(p2.getAge());
즉, 아래와 동일
(parameters) → expression
(parameters) → {statements}
어떤 동작을 parameter로 만들 수 있음. 즉, 함수의 인자로 어떤 동작을 하는 함수를 받을 수 있음.
ArrayList<Product> filteredByName =
filter(products, (Product product) -> product.getName().equals("새우깡"));
ArrayList<Product> filteredByComplex =
filter(products, (Product product) -> {
return product.getName().equals("새우깡")
&& product.getStore().equals("이마트");
});
products.sort(Product proc1, Product proc2)
-> proc1.getPrice()-proc2.getPrice());
/* java 8 부터는 List<T> interface에 sort()가 default method로 추가되어 있음 */
우리는 람다식으로 순수 함수를 선언할 수 있게 되었지만 java는 기본적으로 객체지향 언어이기 때문에 순수 함수와 일반 함수를 다르게 취급하고 있으며 java에서는 이를 구분하기 위해서 함수형 인터페이스가 등장하게 됨.
함수형 인터페이스란 함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션으로, 인터페이스에 선언하여 단 하나의 추상 메소드만을 갖도록 제한하는 역할을 함. 함수형 인터페이스를 사용하는 이유는 java의 람다식이 함수형 인터페이스를 반환하기 때문임.
위의 표에 따르면 아래처럼 변수로 취급되어 사용되어, 해당 interface를 구현한게 됨
Predicate<String> isEmptyString = (String s) -> s.isEmpty();
Consumer<Integer> printInt = (int i) -> System.out.println("" + i);
Function<String, Integer> strCount = (String s) -> s.length();
Supplier<People> makeObject = () -> new People();
UnaryOperator<Integer, Integer> sum = (int i) -> i+i;
Framework에서 제공하는 Functional interface는 Generic을 사용하기 때문에 Primitive type을 사용할 수 없음. Primitive type을 사용하면 자동으로 auto boxing 됨.
하지만 다량의 배치작업 수행 시, auto boxing으로 인한 오버헤드가 발생하는 것을 무시할 수 없음.
List<Integer> list = new ArrayList<>();
for (int i=0; i<1000; i++) {
list.add(i); //auto boxing
}
만약 primitive type을 사용하여 functional interface를 써야 한다면, 각 interface의 특화형을 사용하는 것이 좋음.
public interface IntPredicate {
boolean test(int i);
}
/* boxing 없음 */
IntPredicate evenNumber = (int i) -> i%2==0;
/* boxing 됨 */
Predicate<Integer> evenNumber = (int i) -> i%2==0;
Lambda body에 표현식이 있으면 void를 반환하는 method descriptor와 호환 됨.
즉, void를 반환하는 signature의 경우 다른 타입도 받을 수 있음.
Predicate<String> p = s -> list.add(s);
Consumer<String> c = s -> list.add(s);
list.add()는 return으로 boolean을 반환하지만 Consumer<T>: (T) → void
에서도 사용할 수 있음.
Comparator<Person> p = (Person p1, Person p2) -> p1.getAge() - p1.getAge();
/* 인자의 추론형식 */
Comparator<Person> p = (p1, p2) -> p1.getAge() - p2.getAge();
/* 인자가 한개인 경우 인자의 "()" 생략 가능 */
Predicate<String> s = s -> s.length();
대부분 람다 사용 시 람다의 body 안에서 주어진 인자만 사용하지만, 익명 클래스처럼 외부의 변수(free variable)도 활용할 수 있으며, 이런 동작을 Lambda capturing이라고 함.
int baseValue = 1000;
Function<Integer, Integer> sum = input -> input + baseValue;
단, 외부에서 정의된 free variable은 final or final 속성을 띄어야 함.
아래 경우는 baseValue를 재정의하여 compile error 발생.
int baseValue = 1000;
Function<Integer, Integer> sum = input -> input + baseValue; //compile error
baseValue = 123; //재정의
Lambda 내부에서 접근 가능한 free variable은 세가지임.
이 중에서 지역변수만 변경 불가하고 나머지 변수들은 람다 내부에서 읽거나 쓰기가 가능. 왜냐면 java의 메모리와 관련이 있음. 지역변수는 stack 영역에 저장되는데, 람다식 외부에 정의된 지역변수는 해당 thread가 종료되면서 사라지기 때문에 람다 내부에서 수정이 불가능해지는 상황이 올 수 있음. 따라서 람다 내부에서는 해당 함수를 참조만 할 수 있도록 해당 값을 capturing 해서 사용함.
함수형 인터페이스를 람다식이 아닌 일반 메소드를 참조시켜 선언하는 방법. 아래 3가지 조건을 만족해야 함.
참조 가능한 메소드는 일반 메소드, static 메소드, 생성자가 있으며 클래스 이름::메소드이름 으로 참조할 수 있음.
//기존의 람다식
Function<String, Integer> function = (str) -> str.length();
function.apply("Hello world");
//method reference
Function<String, Integer> function = String::length;
function.apply("Hello world");
//일반 메소드를 참조하여 Consumer를 선언함
Consumer<String> consumer = System.out::println;
consumer.accept("Hello world");
//method reference를 통해 Consumer를 매개변수로 받는 forEach를 쉽게 사용할 수 있음
List<String> list = Arrays.asList("red", "orange", "yellow", "green", "blue");
list.forEach(System.out::println);
//inteface Iterable<T>
default void forEach(Consumer<? super T> action) {
Objects.requrieNonNull(action);
for(T t: this) {
action.accept(t);
}
}
Predicate<Boolean> predicate = Objects::isNull;
//isNull function
public static boolean isNull(Object obj) {
return obj == null;
}
Supplier는 매개변수 없이 반환값만을 갖는 인터페이스이기 때문에 매개변수 없이 String 객체를 새롭게 생성하는 String의 생성자를 참조하여 Supplier로 선언할 수 있음.
Supplier<String> supplier = String::new;
생성자 fererence를 사용할 경우 객체의 생성이 delay 됨. 즉, lazy initialize가 가능.
실제 객체는 호출하는 순간에 생성됨.