교재 목차
Chapter14. 람다와 스트림
Chapter14. 람다와 스트림
1. 람다식
1-1. 람다식 개념
메서드를 하나의 식으로 표현한 것.
메서드를 람다식으로 표현하면 메서드의 이름과 반환값이 없어지므로, 람다식을 익명함수라고도 한다.
1-2. 람다식 작성하기
- 메서드에서 이름과 반환타입 제거
- 매개변수 선언부와 몸통{} 사이에 -> 추가
//기존
반환타입 메서드이름 (매개변수 선언) {
...
}
//람다식
(매개변수 선언) -> {
...
}
1-3. 람다식 사용 시 주의
- 반환값이 있는 메서드는 return 대신 식(expression)으로 대신할 수 있다.(연산 결과가 자동으로 반환값이 되고 ; 생략)
- 매개변수의 타입은 추론가능하면 생략 가능 (대부분 생략 가능)
- 두 매개변수 중 하나의 타입만 생략하는 것은 불가능
- 매개변수가 하나뿐이면 괄호() 생략 가능
- 중괄호{} 안의 문장이 하나일 때는 중괄호{} 생략 가능 (문장 끝에 ; 생략)
- 중괄호{} 안의 문장이 return문일경우 중괄호{} 생략 불가능
1-4. 함수형 인터페이스 (Functional Interface)
람다식은 메서드와 동등한 것이 아니라 익명클래스의 객체와 동등하다.
1-5. java.util.function 패키지
함수형 인터페이스
|
메서드
|
설명
|
java.lang.Runnable
|
void run()
|
매개변수도 없고, 반환값도 없음
|
Supplier
|
T get()
|
매개변수는 없고, 반환값만 있음
|
Consumer
|
void accept(T t)
|
Supplier와 반대로 매개변수만 있고, 반환값이 없음
|
Function
|
R apply(T t)
|
일반적인 함수. 하나의 매개변수를 받아서 결과를 반환
|
Predicate
|
boolean test(T t)
|
조건식을 표현하는데 사용. 매개변수는 하나, 반환 타입은 boolean
|
BiConsumer
|
void accept(T t, U u)
|
두개의 매개변수만 있고, 반환값이 없음
|
BiPredicate
|
boolean test(T t, U u)
|
조건식을 표현하는데 사용됨. 매개변수는 둘, 반환값은 boolean
|
BiFunction
|
R apply(T t, U u)
|
두개의 매개변수를 받아서 하나의 결과를 반환
|
하나의 메서드만 호출하는 람다식은 '클래스 이름:: 메서드 이름' 또는 '참조변수::메서드 이름'으로 바꿀 수 있다.
2. 스트림
2-1. 스트림이란?
일련의 데이터를 함수형 연산을 통해 표준화된 방법으로 쉽게 가공 및 처리하는 기능.
스트림 클래스는 함수형 연산(람다함수)을 지원.
스트림의 특징
- 스트림은 데이터 소스를 변경하지 않는다.
- 스트림은 일회용이다.
- 스트림은 작업을 내부 반복으로 처리한다.
스트림의 연산
- 중간 연산 : 연산 결과가 스트림인 연산, 스트림에 연속해서 중간 연산 할 수 있음, 리턴값 없음
- filter, map, limit, sorted, distinct 등
- 최종 연산 : 연산 결과가 스트림이 아닌 연산, 스트림의 요소를 소모하므로 단 한 번만 가능
- forEach, collect, count 등
스트림의 특징
- 지연된 연산
최종 연산이 수행되기 전까지는 중간 연산이 수행되지 않는다.
스트림 클래스 메서드
2-2. 스트림 생성
스트림
1. 컬렉션
2. 배열
3. 가변 매개변수
4. 지정된 범위의 연속된 정수
5. 특정 타입의 난수들
6. 람다 표현식
7. 파일
8. 빈 스트림
중간연산
1. Stream 필터링 : filter(), distinct()
2. Stream 변환 : map(), flatMap()
3. Stream 제한 : limit(), skip()
4. Stream 정렬 ; sorted()
5. Stream 연산 결과 확인 : peek()
중간 연산
|
반환 타입
|
설 명
|
distinct()
|
Stream<T>
|
중복을 제거
|
filter(Predicate<T> predicate)
|
조건에 안 맞는 요소 제외
|
|
limit(long maxSize)
|
스트림의 일부를 잘라낸다.
|
|
skip(long n)
|
스트림의 일부를 건너뛴다.
|
|
peek(Cosumer<T> action)
|
스트림의 요소에 작업수행
|
|
sorted()
|
스트림의 요소를 정렬
|
|
sorted(Comparator<T> comparator)
|
구분자를 이용하여 스트림의 요소를 정렬
|
|
map(Fuction<T, R> mapper)
|
Stream<R>
|
스트림의 요소를 변환
|
flatMap(Fuction<T, Stream<R>> mapper)
|
||
mapToInt(ToIntFunction mapper)
|
IntStream
|
|
flatMapToInt(Function<t, intstream=""> m)</t,>
|
||
mapToDouble(ToDoubleFunction<T> mapper)
|
DoubleStream
|
|
flatMapToDouble(Function<T, DoubleStream> m)
|
||
MapToLong(ToLongFunction mapper)
|
LongStream
|
|
flatMapToLong(Function<t, LongStream> m)
|
출처: https://hstory0208.tistory.com/entry/Java자바-Stream스트림이란 [< Hyun / Log >:티스토리]
최종연산
1. 요소의 출력 : forEach()
2. 요소의 소모 : reduce()
3. 요소의 검색 : findFirst(), findAny()
4. 요소의 검사 : anyMatch(), allMatch(), noneMatch()
5. 요소의 통계 : count(), min(), max()
6. 요소의 연산 : sum(), average()
7. 요소의 수집 : collect()
중간 연산
|
반환 타입
|
설 명
|
forEach(Consumer<? super T> action)
|
void
|
각 요소에 지정된 작업 수행
|
forEachOrdered(Consumer action)
|
||
count()
|
long
|
스트림의 요소의 개수 반환
|
max(Comparator<? super T> comparator)
|
Optional<T>
|
스트림의 최대값을 반환
|
min(Comparator<? super T> comparator)
|
스트림의 최소값을 반환
|
|
findAny()
|
스트림의 요소 중 아무거나 하나를 반환
|
|
findFirst()
|
스트림의 첫 번째 요소를 반환
|
|
allMatch(Predicate<T> p)
|
boolean
|
주어진 조건을 모두 만족시키는지 확인
|
anyMatch(Predicate<T> p)
|
주어진 조건을 하나라도 만족시키는지 확인
|
|
noneMatch(Predicate<T> p)
|
주어진 조건을 모두 만족하지 않는지 확인
|
|
toArray()
|
Object[]
|
스트림의 모든 요소를 배열로 반환
|
toArray(IntFunction<A[]> generator)
|
A[]
|
|
reduce(BinaryOperator<T> accumulator)
|
Optional<T>
|
스트림의 요소를 하나씩 줄여가며 (리듀싱) 계산한다.
|
reduce(T identity, BinaryOperator<T> accumulator)
|
T
|
|
reduce(U identity, BinaryOperator<U,T<U> accumulator, BinaryOperator<U> combiner)
|
U
|
|
collect(Collector<T,A,R> collector)
|
R
|
스트림의 요소를 수집한다.
주로 요소를 그룹화하거나 분할한 결과를 컬렉션에 담아 반환하는데 사용된다.
|
collect(Supplier<R> supplier, BiConsumer<R,T> accumlator, BiConsumer<R,R> combiner)
|
출처: https://hstory0208.tistory.com/entry/Java자바-Stream스트림이란 [< Hyun / Log >:티스토리]
4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등
www.tcpschool.com
// 컬렉션에서 스트림 생성
ArrayList<Integer> list = new ArrayList<Integer>();
Stream<Integer> stream = list.stream();
stream.forEach(System.out::println); // forEach() 메소드를 이용한 스트림 요소의 순차 접근
// 배열에서 스트림 생성
String[] arr = new String[]{"넷", "둘", "셋", "하나"};
Stream<String> stream1 = Arrays.stream(arr);
stream1.forEach(e -> System.out.print(e + " "));
System.out.println();
// 배열의 특정 부분만을 이용한 스트림 생성
Stream<String> stream2 = Arrays.stream(arr, 1, 3);
stream2.forEach(e -> System.out.print(e + " "));
// 가변 매개변수에서 스트림 생성
Stream<Double> stream = Stream.of(4.2, 2.5, 3.1, 1.9);
stream.forEach(System.out::println);
// 지정된 범위의 연속된 정수에서 스트림 생성
IntStream stream1 = IntStream.range(1, 4);
stream1.forEach(e -> System.out.print(e + " "));
System.out.println();
IntStream stream2 = IntStream.rangeClosed(1, 4);
stream2.forEach(e -> System.out.print(e + " "));
// 특정 타입의 난수로 이루어진 스트림 생성
IntStream stream = new Random().ints(4);
stream.forEach(System.out::println);
// 람다 표현식
IntStream stream = Stream.iterate(2, n -> n + 2); // 2, 4, 6, 8, 10, ...
// 파일
String<String> stream = Files.lines(Path path); // 라인 단위로 접근
// 빈 스트림 생성
Stream<Object> stream = Stream.empty();
System.out.println(stream.count()); // 스트림의 요소의 총 개수를 출력함.
// filter(), distinct()
IntStream stream1 = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
IntStream stream2 = IntStream.of(7, 5, 5, 2, 1, 2, 3, 5, 4, 6);
// stream에서 중복된 요소를 제거함.
stream1.distinct().forEach(e -> System.out.print(e + " ")); // 7 5 2 1 3 4 6
System.out.println();
// stream에서 홀수만을 골라냄.
stream2.filter(n -> n % 2 != 0).forEach(e -> System.out.print(e + " ")); // 7 5 5 1 3 5
System.out.println();
// map(), flatMap()
Stream<String> stream = Stream.of("HTML", "CSS", "JAVA", "JAVASCRIPT");
stream.map(s -> s.toLowerCase()).forEach(System.out::println); //해당 스트림의 요소들을 주어진 함수에 인수로 전달하여, 그 반환값들로 이루어진 새로운 스트림을 반환
/*
* html
* css
* java
* javascript
*/
String[] arr = {"I study hard", "You study JAVA", "I am hungry"};
Stream<String> stream = Arrays.stream(arr);
stream.flatMap(s -> Stream.of(s.split(" +"))).forEach(System.out::println); // 여러 문자열이 저장된 배열을 각 문자열에 포함된 단어로 이루어진 스트림으로 변환
/*
* I
* study
* hard
* You
* study
* JAVA
* I
* am
* hungry
*/
// limit(), skip()
IntStream stream1 = IntStream.range(0, 10);
IntStream stream2 = IntStream.range(0, 10);
IntStream stream3 = IntStream.range(0, 10);
stream1.skip(4).forEach(n -> System.out.print(n + " ")); // 스트림의 첫번째 요소부터 전달된 개수만큼 요소를 제외한 나머지 요소만으로 이루어진 새로운 스트림
System.out.println(); // 4 5 6 7 8 9
stream2.limit(5).forEach(n -> System.out.print(n + " ")); // 첫 번째 요소부터 전달된 개수만큼의 요소만으로 이루어진 새로운 스트림
System.out.println(); // 0 1 2 3 4
stream3.skip(3).limit(5).forEach(n -> System.out.print(n + " ")); // 3 4 5 6 7
// sorted()
Stream<String> stream1 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS");
Stream<String> stream2 = Stream.of("JAVA", "HTML", "JAVASCRIPT", "CSS");
stream1.sorted().forEach(s -> System.out.print(s + " ")); // 해당 스트림을 주어진 비교자(comparator)를 이용하여 정렬
// CSS HTML JAVA JAVASCRIPT
System.out.println();
stream2.sorted(Comparator.reverseOrder()).forEach(s -> System.out.print(s + " ")); // 역순으로 정렬
// JAVASCRIPT JAVA HTML CSS
// forEach()
Stream<String> stream = Stream.of("넷", "둘", "셋", "하나");
stream.forEach(System.out::println); // 각 요소를 출력
/*
* 넷
* 둘
* 셋
* 하나
*/
// reduce()
Stream<String> stream1 = Stream.of("넷", "둘", "셋", "하나");
Stream<String> stream2 = Stream.of("넷", "둘", "셋", "하나");
Optional<String> result1 = stream1.reduce((s1, s2) -> s1 + "++" + s2);
result1.ifPresent(System.out::println); // 넷++둘++셋++하나
String result2 = stream2.reduce("시작", (s1, s2) -> s1 + "++" + s2);
System.out.println(result2); // 시작++넷++둘++셋++하나
// findFirst(), findAny()
IntStream stream1 = IntStream.of(4, 2, 7, 3, 5, 1, 6);
IntStream stream2 = IntStream.of(4, 2, 7, 3, 5, 1, 6);
OptionalInt result1 = stream1.sorted().findFirst(); //stream의 모든 요소를 정렬한 후, 첫 번째에 위치한 요소를 출력
System.out.println(result1.getAsInt()); // 1
OptionalInt result2 = stream2.sorted().findAny(); // stream의 모든 요소를 정렬한 후, 첫 번째에 위치한 요소를 출력
System.out.println(result2.getAsInt()); // 1
// anyMatch(), allMatch(), noneMatch()
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10);
System.out.println(stream1.anyMatch(n -> n > 80)); // 일부 요소에 대해 n > 80 인지 - true
System.out.println(stream2.allMatch(n -> n > 80)); // 모든 요소에 대해 n > 80 인지 - false
// count(), min(), max()
IntStream stream1 = IntStream.of(30, 90, 70, 10);
IntStream stream2 = IntStream.of(30, 90, 70, 10);
System.out.println(stream1.count()); // 모든 요소의 갯수 - 4
System.out.println(stream2.max().getAsInt()); // 모든 요소의 최대값 - 90
// sum(), average()
IntStream stream1 = IntStream.of(30, 90, 70, 10);
DoubleStream stream2 = DoubleStream.of(30.3, 90.9, 70.7, 10.1);
System.out.println(stream1.sum()); // 모든 요소의 총합 - 200
System.out.println(stream2.average().getAsDouble()); // 모든 요소의 평균 - 50.5
// collect()
Stream<String> stream = Stream.of
3. collector 구현하기
3-1. Collector란?
스트림 요소를 어떤 식으로 도출할지 지정하는 것.
- 최종연산에 해당하는 .collect() 함수의 파라미터에 해당하는 인터페이스
- 최종연산 collect에서 collector 추상 메서드의 구현을 받아 어떻게 reduce 할 것 인지 결정.
3-2. Collector 구현하기
구현조건
- 직접 5개의 메서드를 구현
public interface Collector<T, A, R> {
Supplier<A> supplier(); // 작업 결과를 저장할 공간을 제공, 함수형 인터페이스 반환
BiConsumer<A, T> accumulator(); // 스트림의 요소를 수집할 방법을 제공, 함수형 인터페이스 반환
BinaryOperator<A> combiner(); // 두 저장공간을 병합할 방법을 제공, 함수형 인터페이스 반환
Function<A, R> finisher(); // 결과를 최종적으로 변환할 방법을 제공, 함수형 인터페이스 반환
Set<Characteristics> characteristics(); // 컬렉터가 수행하는 작업의 속성에 대한 정보를 제공
// 하기 세 속성 중 해당하는 것을 set에 담아 반환하도록 구현
UNORDERED - 리듀싱 결과가 스트림 요소의 방문 순서나 누적 순서에 영향을 받지 않는다.
CONCURRENT - 다중 스레드에서 accumulator 함수를 동시에 호출할 수 있고 병렬 리듀싱을 수행할 수 있다.
IDENTITY_FINISH - 리듀싱 과정의 최종 결과로 누적자 객체를 바로 사용할 수 있게하고, 누적자 A를 결과 R로 안전하게 형변환
}
4. 스트림의 변환
4-1. 스트림의 변환
Stream ➡ 기본형 Stream
From
|
To
|
변환 메서드
|
Stream<T>
|
IntStream
LongStream
DoubleStream
|
mapToInt(ToIntFunction<T> mapper)
MapToLong(ToLongFunction<T> mapper)
MapToDouble(ToDoubleFunction<T> mapper)
|
기본형 Stream ➡ Stream
From
|
To
|
변환 메서드
|
IntStream
LongStream
DoubleStream
|
Stream<Integer>
Stream<Long>
Stream<Double>
|
boxed()
|
Stream<T>
|
mapToObj(DoubleFunction<T> mapper)
|
기본형 Stream ➡ 기본형 Stream
From
|
To
|
변환 메서드
|
IntStream
LongStream
DoubleStream
|
LongStream
DoubleStream
|
asLongStream()
asDoubleStream()
|
Stream ➡ 부분 Stream
From
|
To
|
변환 메서드
|
Stream<T>
IntStream
|
Stream
IntStream
|
skip(long n)
limit(long maxSize)
|
두 개의 Stream ➡ Stream
From
|
To
|
변환 메서드
|
Stream<T>, Stream<T>
|
Stream<T>
|
concat(Stream<T> a, Stream<T> b)
|
IntStream, IntStream
|
IntStream
|
concat(IntStream a, IntStream b)
|
LongStream, LongStream
|
LongStream
|
concat(LongStream a, LongStream b)
|
DoubleStream, DoubleStream
|
DoubleStream
|
concat(DoubleStream a, DoubleStream b)
|
Stream의 Stream ➡ Stream
From
|
To
|
변환 메서드
|
Stream<Stream<T>>
|
Stream<T>
|
flatMap(Function mapper)
|
Stream<IntStream>
|
IntStream
|
flatMapToInt(Function mapper)
|
Stream<LongStream>
|
LongStream
|
flatMapToLong(Function mapper)
|
Stream<DoubleStream>
|
DoubleStream
|
flatMapToDouble(Function mapper)
|
Stream ➡ 병렬 Stream
From
|
To
|
변환 메서드
|
Stream<T>
IntStream
LongStream
DoubleStream
|
Stream<T>
IntStream
LongStream
DoubleStream
|
parallel() // 스트림 -> 병렬 스트림
sequential // 병렬 스트림 -> 스트림
|
Stream ➡ Collection
From
|
To
|
변환 메서드
|
Stream<T>
IntStream
LongStream
DoubleStream
|
Collection<T>
|
collect(Collectors.toCollection(Supplierfactory)
|
List<T>
|
collect(Collectors.toList())
|
|
Set<T>
|
collect(Collectors.toSet())
|
Collection ➡ Stream
From
|
To
|
변환 메서드
|
Collection<T>, List<T>, Set<T>
|
Stream<T>
|
stream()
|
Stream ➡ Map
From
|
To
|
변환 메서드
|
Stream<T>
IntStream
LongStream
DoubleStream
|
Map<K, V>
|
collect(Collectors.toMap(Function key, Function value)
collect(Collectors.toMap(Function key, Function value, BinaryOperator)
collect(Collectors.toMap(Function key, Function value, BinaryOperator merge, Supplier mapSupplier)
|
Stream ➡ 배열
From
|
To
|
변환 메서드
|
Stream<T>
|
Object[]
|
toArray()
|
T[]
|
toArray(IntFunction<A[] generator)
|
|
IntStream
LongStream
DoubleStream
|
int[]
long[]
double[]
|
toArray()
|
출처: https://hstory0208.tistory.com/entry/Java자바-Stream-변환-표 [< Hyun / Log >:티스토리]
5. 스트림과 for-loop의 성능 차이
5-1. 스트림과 for-loop 성능 차이
for문이 stream 보다 빠르다.
그 이유는,
- for문은 단순 인덱스 기반 반복문이며, 오버헤드가 없다.
- stream은 jvm이 처리하는 것이 많아 실행이 느리다.
- for문은 컴파일러가 최적화를 시킨다.
그럼에도 stream을 사용하는 이유,
- 가독성이 좋아진다.
- 코드로 작성해야하는 로직을 stream에서 제공해주는 함수로 간단하게 해결 가능하다.
'개념' 카테고리의 다른 글
[JAVA의 정석] Chapter16 (4) | 2024.09.08 |
---|---|
[JAVA의 정석] Chapter15 (0) | 2024.09.08 |
[JAVA의 정석] Chapter13 (1) | 2024.09.08 |
[JAVA의 정석] Chapter12 (1) | 2024.09.08 |
[JAVA의 정석] Chapter11 (1) | 2024.09.08 |