이 글은 Java의 정석 3rd Edition을 읽고 정리한 내용입니다.
Chapter 14

람다와 스트림
1. 람다식
1.1 람다식이란?
람다식은 간단히 말해서 메서드를 하나의 식으로 표현한 것이다, 람다식은 함수를 간략하면서도 명확한 식으로 펴현할 수 있게한다.
메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로 람다식을 익명 함수이라고도 한다.
1.2 람다식 작성하기
람다식은 메스드에서 이름과 반환타입을 제거하고 매개변수 선언부와 몸통 사이에 ->를 추가한다.
반환값이 있는 메서드의 경우 식의 연산결과가 자동으로 반환값이 된다.
메서드
반환타입 메서드이름(매개변수 선언) {
}
람다식
(매개변수 선언) -> {
}
1.3 함수형 인터페이스
함수형 인터페이스는 자바 8에서 도입된 개념으로, 하나의 추상 메서드만을 가진 인터페이스를 의미합니다. 이 인터페이스는 다수의 디폴트 또는 정적 메서드를 가질 수 있지만, 추상 메서드는 반드시 하나만 있어야 한다.
람다 표현식은 또한 자바 8에서 도입되었으며, 이름 없는 메서드를 정의하는 간단하고 간결한 방법을 제공합니다. 람다 표현식은 함수형 인터페이스의 인스턴스를 생성하는 데 사용됩니다.
함수형 인터페이스 타입의 매겨변수와 반환타입
람다식을 참조변수로 다룰 수 있는데 이는 메서드를 통해 람다식을 주고받을 수 있다는 것이다. 즉 변수처럼 메서드를 주고받느 것이 가능해진 것인다. 사실상 메서드가 아닌 함수형 인터페이스인 객체를 주고 받는것이다.
람다식의 타입과 형변환
람다시은 익명 객체이고 타입이 없다 그래서 대입 연산자의 양변을 타입을 일치시켜주어야 된다.
MyFunction f = (MyFunction) {() -> {}); 양 변의 타입이 다르므로 형변환이 필요
1.4 java.util,function 패키지
java.util.function 패키지에 일반적으로 자주 쓰이는 형식의 메서드를 함수형 인터페이스로 미리 정의해 두어서 매번 새로운 함수형 인터페이스를 정의하지 말고, 가능하면 이 패키지의 인터페이스를 활용하는 것이 좋다
매개변수가 두 개인 함수형 인터페이스
매개변수가 두 개인 함수형 인터페이스는 접두사 'Bi'가 붙는다
컬렉션 프레임 웍과 함수형 인터페이스
컬렉션 프레임웍의 인터페이스에 다수의 디폴트 메서드가 푸가되었는데, 그중 일부는 함수형 인터페이스를 사용한다.
기본형을 사용하는 함수형 인터페이스
지금까지 소개한 함수형 인터페이스는 기본형 타입의 값을 처리할 때도 래퍼 클래스를 사용해왔다. 그러나 기본형 대신 래퍼클래스를 사용하는 것은 비효율 적이기 때문에 아래와 같은 기본형을 사용하는 인터페이스들이 제공된다.
1.5 Fuction의 합성과 Predicate의 결합
Function의 합성
두 람다식을 합성해서 새로운 람다식을 만들 수 있다
함수 f,g 가 있을때 f.andThen(g)는 함수 f를 먼저 적용하고 g를 적용한다. 그리고 f.compose(g)는 반대로 g를 먼저 적용하고 f를 적용한다.
Fuction<String, Integer> f = (s) -> Integer.parseInt(s,16);
Fuction<Integer, String> g = (i) -> Integer.toBinaryString(i);
Fuction<String,String> h = f.andThen(g);
Fuction<String, Integer> f = (s) -> Integer.parseInt(s,16);
Fuction<Integer, String> g = (i) -> Integer.toBinaryString(i);
Fuction<String,String> h = f.compose(g);
Predicate의 결합
여러 조건식을 논리 연산자로 연결해서 하나의 식을 구성 할 수 있는 것처럼, 여러 Predicate를 and(),or(), negate()로 연결해서 새로운 Predicate로 결합할 수 있다.
Predicate<Integer> p = i -> i < 100;
Predicate<Integer> q = i -> i < 200;
Predicate<Integer> r = i -> i % 2 == 0;
Predicate<Integer> notP = p.negate ();
Predicate<Integer> all = notP.and(q.or(r));
System.out.println(all.test(150));
1.6 메소드 참조
자바 8에서 도입된 문법으로 람다 표현식을 더 간결하게 표현할 수 있는 방법이다.. 메소드 참조는 이미 정의된 메소드를 재사용할 때 유용하며, 람다 표현식의 축약형이라고 볼 수 있다.
일반적인 람다식
Fuction<String, Integer> f = (String s) -> Integer.parseInt(s);
메서드 참조
Fuction<String,Integer> f = Integer::ParseInt;
종류 | 람다 | 메서드 참조 |
static메서드 참조 | (x) -> ClassName.method | ClassName::method |
인스턴스메서드 참조 | (obj, x) -> obj.method(x) | ClassName::method |
특정 객체 인스턴스메서드 참조 | (x) -> obj.method(x) | obj::method |
생성자의 메소드
생성자를 호출하는 람다식도 메서드 참조로 변환이 가능하다.
매개변수가 있는 생성자라면, 매개변수의 개수에 따라 알맞은 함수형 인터페이스를 사용하면 된다.
Supplier<Myclass> s = () -> new MycClass(); //람다식
Supplier<MyClass> s = MyClass::new; 메서드 참조
2. 스트림
2.1 스트림이란
기존에 컬렉션이나 배열에 데이터를 담고 원하는 결과를 얻기 위해 for문과 Iterator를 이용해서 코드를 작성했는데. 이러한 방식은 가독성과 재사용성이 떨어뜨리고 데이터 소스마다 다른 방식으로 다뤄야 한다는 단점이 있다. Stream은 이러한 점을 해결하고자 만들어 졌는데 스트림은 데잍 소스를 추상화하고 자주 사용되는 메서드들을 정의해 놓았다.
스트림은 데이터 소스를 변경하지 않는다.
스트림은 데이터 소스로 부터 데이터를 읽기만 할뿐 변경하지 않는다. 필요하면 정렬된 결과를 컬렉션에 담아서 반환할 수도 있다.
스트림은 일회용이다.
스트림은 Iterator와 같이 일회성이다. 스트림은 한번 사용하면 닫혀서 다시 사용할 수 없다. 다시 생성해야 다시 사용 가능하다.
스트림은 작업을 내부 반복을 처리한다.
반복문을 메서드의 내부로 숨겨 간경하게 한다.forEach()는 스트림에 정의된 메서드로 매개변수에 대입된 람디식을 모든 요소에 적용한다.
for문을 이용한 출력
for(String str : strList)
System.out.println(str);
스트림을 이용한 출력
stream.forEach(System.out::println);
스트림의 연산
중간 연산 : 연산 결과가 스트림인 연산, 스트림에 연속해서 중간 연산을 할 수 있음
최종 연산 : 연산 결과가 스트림이 아닌 연산 스트림의 요초를 소모하므로 단 한번만 가능
지연된 연산
스트림 연산에선 최종 연산이 수행되기 전까지 중간 연산이 수행 되지 않는다 스트림에 대해 distinct()나 sort()와 같은 중간 연산을 호출해도 최종 연산을 수행하지 않으면 수행되지 않는다.
Stream<Integer>와 IntStream
오토박싱&언박싱으로 인한 비효율을 줄이기 위한 데이터 소스의 기본형으로 다루는 스트림 IntStream,LongStream,DoubleStream이 제공된다.
병렬 스트림
스트림의 장점 중 하나는 병렬 처리가 쉽다는 것이다. 스트림에 parallel()이라는 메서드를 호출해서 병렬로 연산을 수행하도록 지시하면 된다. 반대로 병렬로 처리되지 않게 하려면 sequntial()을 호출하면 된다.
2.2 스트림 만들기
컬렉션
컬렉션의 최고 조상 Collection에 stream()이 정의되어 있다. 컬렉션 메서드는 스트림을 생성할 수 있다.
Stream<T> Collection.stream()
배열
배열을 소스로 하는 스트림을 생성하는 메서드는 Stream과 Arrays에 static 메서드로 정의되어 있다.
특정 범위의 정수
IntStream과 LongStream은 다음과 같이 지정된 범위의 연속된 정수를 스트림ㅇ로 생성해서 반환하는 range()와 rangeClosed()를 가지고 있다.
IntStream.range(int begin, int end)
IntStream.rangeClosed(int begin, int end)
임의의 수
Random클래스에는 아래와 같은 인스턴스 메서드들이 포함되어 있다. 해당 타입의 난수들로 이루어진 스트림을 반환한다.
IntStream ints()
LongStream longs()
DoubleStream doubles()
람다식
Stream클래스의 iterate()와 generate()는 람다식을 매개변수로 받아 람다식에 의해 계산되는 값들을 요소로 하는 무한 스트림을 생성한다.
static<T> Stream<T> iterate(T seed, UnaryOperator<T> f)
static<T> Stream<T> generate(Supplier<T> s)
파일
java.nio.file.File의 list()는 지정된 디렉토리에 있는 파일의 목록을 소스로 하는 스트림을생성해 반환한다.
Stream<Path> Files.list(Path dir)
빈스트림
스트림연산을 수행한 결과가 하나도 없을 때 null보다 빈 스트림을 반환하는 것이 낫다.
두 스트림의 연결
Stream이 static메서드인 concat()을 사용하면 두 스트림을 하나로 연결할 수 있다. 물론 연결하려는 두 스트림의 요소는 같은 타입이어야 한다.
2.3 스트림의 중간연산
skip()과 limit()
스트림의 일부를 잘라낼때 사용한다.
filter()와 distinct()
filter는 조건에 맞지않는 요소를 걸러내고 distinct는 중복된 요소를 제거한다.
sorted()
지정된 Comparator로스트림을 정렬한다. 람다식을 이용하는 것도 가능하다.
map()
스트림의 요서에 저장된 값 중 원하는 필드만 뽑아내거나 특정 형태로 변환한다.
peek()
연산과 연산 사이에서 올바르게 처리되었는디 확인한다.
mapToInt(),mapToLong(),mapToDouble()
map()연산은 Stream<T>타입의 스트림을 반환하는데 스트림의 요소를 숫자로 반환하는 경우 사용한다,
2.4 Optional<T>와 OptionalInt
Optional<T>은 지네릭 클래스로 T타입의 객체를 감싸는 클래스로 optional타입의 객체에는 모든 타입의 참조 변수를 담을 수 있다,
반환된 결과가 null인지 매번 체그하는 대신 Optional에 정의된 메서드를 통해 간단하게 처리할 수 있다.
Optional객체 생성하기
Optional객체를 생성할 때는 of() 또는 ofNullable()dmf 사용한다.
참조변수의 값이 null 일 가능성이 있으면 ofNullable()을 사용해야 한다.
Optional객체의 값 가져오기
Optional 객체에 저장된 값을 가져올 때는 get()을 사용한다. 값이 null 일땐는 NoSuchElementException이 발생하며 이를 대비해서 orElse() ,orelsethrow() 메소드를 사용할 수 있다.
String str = Optional.orElse("")
람다를 이용한 null 방지방법
String str = Optinal.orElseGet(String::new);
String str = Optinal.orElseThrow(NullPointerException::new);
2.5 스트림의 최종 연산
forEach()
스트림의 각 요소에 지정한 작업을 수행하게 한다.
allMatch(),anyMatch(),nonMatch(),findFirst(),findAny()
스트림의 요서에 대해 지정된 조건에 모든 요소가 일치하는 지 일부가 일치하지 아니면 어떤 요소도 일치하지 않는지 확인한다
count(),sum(),average(),max(),min()
스트림의 요소들에대한 통계를 얻을수 있다.
reduce()
스트림의 요소를 줄여나가면서 연산을 수행하고 최종결과를 반환한다.
2.6 collection
스트림의 최종 연산중 가장 복잡하면서 유용하게 활용될 수 있는것이 collection이다
collect() 스트림의 최종연산, 매개변수로 컬렉터를 필요로 한다.
collector 인터페이스, 컬렉터는 이 인터페이스를 구현해야한다.
collectors 클래스,static메소드로 미리 작성된 컬렉터를 제공한다.
스트림을 컬렉션과 배열로 변환- toList(), toSet(), toMap(), toCollection(), toArray()
통계 - counting(), summingInt(), averagingInt(), maxBy(), minBy()
리듀싱 - reducing()
문자열 결합 - joining()
그룹화 분할 - groupingBy(), partitioningBy()