식(expression)
으로 표현한 것이다. 람다식은 함수를 간략하면서도 명확한 식으로 표현할 수 있게 해준다. 익명 함수(anonymous function)
이라고도 한다.int sum(int a, int b) {
return a + b;
}
int sum
을 제거하고 매개변수 선언부 (int a, int b)
와 몸통 {}
사이에 ->
을 넣어준다.(int a, int b) -> { return a + b; }
;
을 붙이지 않는다.(int a, int b) -> a + b
(a, b) -> a + b
()
를 생략할 수 있지만, 매개변수의 타입이 명시되어 있으면 생략할 수 없다.a -> a * a // OK
int a -> a * a // Error!
{}
안의 문장이 하나일 때엔 중괄호도 생략할 수 있다. 이 때엔 마지막에 ;
을 붙이지 않는다.return
문이라면 생략할 수 없다. @FunctionalInterface // 이걸 붙이면 컴파일러가 함수형 인터페이스를 올바르게 정의했는지 확인해준다.
interface MyFunction {
public abstract int max(int a, int b);
}
java.util.function
패키지에 자주 쓰이는 메서드 형태를 함수형 인터페이스로 정의해 놓았기 때문이다. 함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
java.lang.Runnable | void run() | 매개변수도 없고 반환값도 없음 |
Supplier | T get() | 매개변수는 없고 반환값만 있음 |
Consumer | void accept(T t) | 매개변수만 있고 반환값이 없음 |
Function<T, R> | R apply(T t) | 일반적인 함수. 하나의 매개변수를 받아서 결과 반환 |
Predicate | boolean test(T t) | 조건식을 표현하는데 사용됨. 매개변수는 하나이고 반환 타입은 boolean |
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
BiConsumer<T, U> | void accept(T t, U u) | 두 개의 매개변수만 있고 반환값이 없음 |
BiPredicate<T, U> | boolean test(T t, U u) | 조건식을 표현하는데 사용됨. 매개변수는 둘이고 반환 타입은 boolean |
BiFunction<T, U, R> | R apply(T t, U u) | 두 개의 매개변수를 받아서 하나의 결과를 반환 |
함수형 인터페이스 | 메서드 | 설명 |
---|---|---|
UnaryOperator와 | T apply(T t) | Function의 자손으로 매개변수와 결과의 타입이 같다. |
BinaryOperator | T apply(T t, T t) | BiFunction의 자손으로 매개변수와 결과의 타입이 같다. |
메서드 참조
라는 방법으로 람다식을 더 간결하게 할 수 있다.Function<String, Integer> f = (String s) -> Integer.parseInt(s);
Function<String, Integer> f = Integer::parseInt;
parseInt
메서드의 선언부로부터, 또는 좌변의 Function
인터페이스에 지정된 제네릭 타입으로부터 알아낼 수 있다.하나의 메서드만 호출하는 람다식은
클래스이름::메서드이름
또는참조변수::메서드이름
으로 바꿀 수 있다.
Supplier<MyClass> s = () -> new MyClass(); // 람다식
Supplier<MyClass> s = MyClass::new; // 메서드 참조
for문
으로 반복문을 사용하는 경우가 많다. 편하게 쓸 수 있지만 이게 사용할 일이 많아지면 전체적으로 코드의 가독성이 떨어지고 재사용성도 떨어진다. 어떨 땐 굉장히 기계적으로 작성하고 있는 나를 발견하곤 한다.List
를 정렬해야 할 때는 Collections.sort()
를 사용하고, 배열을 정렬할 때는 Arrays.sort()
를 사용해야 한다. 헷갈린다.스트림(Stream)
이다. 스트림은 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메서드들을 정의해 놓았다.데이터 소스를 추상화하였다는 것은 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게 되었다는 것과 코드의 재사용성이 높아졌다는 것을 의미한다.
데이터 소스를 변경하지 않는다.
일회용이다.
작업을 내부 반복으로 처리한다.
forEach()
를 호출하면 그 메서드 내부 안에 반복문이 작성되어 있어 반복하고자 하는 작업을 인자값으로 넣어주기만 하면 반복 작업이 내부적으로 수행되는 것이다. 그래서 간결한 코드를 작성할 수 있다.스트림의 연산
병렬 스트림
parallel()
메서드만 호출하면 된다.Collection
에 stream()
이 정의되어 있다. 그래서 Collection
의 자손인 List
와 Set
을 구현한 컬렉션 클래스들은 모두 이 메서드로 스트림을 생성할 수 있다.Stream<T> Collection.stream()
List
로부터 스트림을 생성하려면 다음과 같이 작성한다.List<Integer> list = Arrays.asList(1,2,3,4,5);
Stream<Integer> intStream = list.stream(); // 스트림 생성 완료
Stream
과 Arrays
에 static
메서드로 정의되어 있다.Stream<T> Stream.of(T... values) // 가변인자
Stream<T> Stream.of(T[])
Stream<T> Arrays.stream(T[])
Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive)
Stream<String> strStream = Stream.of("a","b","c");
Stream<String> strStream = Stream.of(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"});
Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"}, 0, 3);
스트림이 제공하는 연산은 중간연산과 최종연산으로 분류할 수 있다.
중간연산 : 연산 결과가 스트림인 연산. 중간 연산을 연속해서 연결할 수 있다.
최종연산 : 연산 결과가 스트림이 아닌 연산. 스트림의 요소를 소모하므로 단 한번만 가능
연산 메서드는 여러 종류가 있지만 자주 사용되는 것들만 언급하자면 map()
, forEach()
, reduce()
, collect()
, sorted()
, filter()
등이 있다.
Stream<R> map(Function<? super T, ? extends R> mapper)
Stream<T> sorted()
Stream<T> sorted(Comparator<? super T> comparator)
boolean
인 람다식을 사용하면 된다.Stream<T> filter(Predicate<? super T> predicate)
for
문처럼 스트림의 요소를 하나씩 순회하며 원하는 작업을 할 수 있다. 주로 스트림의 요소를 출력하는 용도로 많이 사용된다.void forEach(Consumer<? super T> action)
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator)
U reduce(U identity, BiFunction<U, T, U> accumulator, BinaryOperator<U> combiner)
Object collect(Collector collector) // Collector를 구현한 클래스의 객체를 매개변수로 가짐
Object collect(Supplier supplier, BiConsumer accumulator, BiConsumer combiner)
T 타입의 객체
를 감싸는 래퍼 클래스이다. 그래서 Optional
타입의 객체에는 모든 타입의 참조변수를 담을 수 있다. public final class Optional<T> {
private final T value; // T 타입의 참조변수
...
}
Optional
객체에 담아서 반환한다. Optional
객체에 담아서 반환하면, 반환된 객체가 null
인지 매번 if
문으로 체크하는 대신 Optional
에 정의된 메서드를 통해서 간단히 처리할 수 있다.Optional
을 사용하면 null
체크를 위한 if
문 없이, isPresent()
, ifPresent()
와 같은 메서드를 사용해서 NullPointerException
이 발생하지 않는 보다 간결하고 안전한 코드를 작성하는 것이 가능하다.