<aside> 💡 참조 블로그: https://mangkyu.tistory.com/112
</aside>
Stream API 특징
Stream API 연산 종류
Stream 연산은 크게 생성 → 가공 → 결과 만들기로 구성
생성
가공
결과
예시 코드
List<String> myList = Arrays.asList({"a1", "a2", "b1", "c2", "c1"});
myList
.stream() // 생성
.filter(s -> s.startsWith("C")) // 가공
.map(String::toUpperCase) // 가공
.sorted() // 가공
.count(); // 결과
람다식이란
함수를 람다식으로 표현하면 함수 이름이 필요없어지기 때문에 익명 함수의 한 종류
익명함수들은 모두 일급 객체
일반 함수 익명 함수 코드
// 기존의 일반 함수
public String hello() {
return "Hello World";
}
// 람다 방식(익명 함수)
() -> System.out.println("Hello World!");
람다식의 특징
함수형 인터페이스
함수형 인터페이스는 함수를 1급 객체처럼 다룰 수 있게 해주는 어노테이션
인터페이스를 선언하여 단 하나의 추상 메서드를 갖도록 제한
함수형 인터페이스를 사용하는 이유 → 람다식이 함수형 인터페이스를 반환
코드 예제
@FuntionalInterface
interface MyLambdaFunction {
int max(int a, int b);
}
public class Lambda {
public static void main(String[] args) {
MyLambdaFunction lambdaFunction = (int a, int b) -> a >= b ? a : b;
System.out.println(lambdaFunction.max(3, 5));
}
}
Java에서 제공하는 함수형 인터페이스
Supplier<T>
Consumer<T>
Function<T, R>
Predicate<T>
Supplier<T>
매개변수 없이 반환값만 가지는 함수형 인터페이스
Supplier<T>는 T get()를 추상 메서드로 가짐
@FunctionalInterface
public interface Supplier<T> {
T get();
}
// 사용 예시
Supplier<String> supplier = () -> "Hello World";
System.out.println(supplier.get());
Consumer<T>
객체 T를 매개변수로 받아서 사용하며, 반환값은 함수형 인터페이스
void accept(T t)를 추상 메서드로 가짐
andThen()이라는 함수를 추가로 제공하는데 이를 통해 함수가 끝난 후 다음 Consumer를 연쇄적으로 이용 가능
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); }
}
}
// 사용 예시
Consumer<String> consumer = (str) -> System.out.println(str.split(" ")[0]);
consumer.andThen(System.out::println).accept("Hello World");
// 출력
Hello
Hello World
Function<T, R>
Function은 객체 T를 매개변수로 받아서 처리한 후 R로 반환하는 함수형 인터페이스
Function은 R apply(T t)를 추상 메서드로 가짐
Consumer와 마찬가지로 andThen() 메서드 제공, 추가적으로 compose() 제공
andThen()은 연쇄 실행을 위해 사용, cmopose()는 첫 번째 함수 실행 이전에 먼저 함수를 실행하여 연쇄적으로 연결
또한 identity() 메서드는 자기 자신을 반환하는 static 함수
@FunctionalInterface
public interface Function<T, R> {
R apply(T t);
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static <T> Function<T, T> identity() {
return t -> t;
}
}
// 예시, 메소드 참조로 간소화 가능(String::length;)
Function<String, Integer> function = str -> str.length();
function.apply("Hello World");
Predicate <T>
객체 T를 받아 처리한 후 Boolean 반환
Boolean test(T t) 추상 메서드로 가짐
// 정의
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) && other.test(t);
}
default Predicate<T> negate() {
return (t) -> !test(t);
}
default Predicate<T> or(Predicate<? super T> other) {
Objects.requireNonNull(other);
return (t) -> test(t) || other.test(t);
}
static <T> Predicate<T> isEqual(Object targetRef) {
return (null == targetRef)
? Objects::isNull
: object -> targetRef.equals(object);
}
}
// 예시
Predicate<String> predicate = (str) -> str.equals("Hello World");
predicate.test("Hello World");
메서드 참조
함수형 인터페이스를 람다식이 아닌 일반 메서드를 참조시켜 선언하는 방법
일반 메서드를 참조하기 위한 조건 3가지
참조 가능한 메서드는 일반, static 메서드, 생성자가 있으며 클래스이름::메서드이름 으로 참조
일반 메서드 참조
예를 들어 Function에 메서드 참조 적용
사용 예시
// 기존의 람다식
Function<String,Integer> function = (str) -> str.length();
function.apply("Hello World");
// 메서드 참조로 변경
Function<String, Integer> function = String::length;
function.apply("Hello World");
// 일반 메소드를 참조하여 Consumer를 선언한다.
Consumer<String> consumer = System.out::println;
consumer.accept("Hello World!!");
// 메소드 참조를 통해 Consumer를 매개변수로 받는 forEach를 쉽게 사용할 수 있다.
List<String> list = Arrays.asList("red", "orange", "yellow", "green", "blue");
list.forEach(System.out::println);
//interface Iterable<T>
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
static 메서드 참조
생성자 참조
Stream 생성하기
Collection의 Stream 생성
Stream 가공(중간 연산)
Map(데이터 변환)
Sorted(정렬)
Distinct(중복 제거)
Peek(특정 연산 수행)
Stream → 원시 Stream
Stream 결과 만들기(최종 연산)
Max/Min/Sum/Average/Count
Collect(데이터 수집)
FlatMap을 통한 중첩 제거