본문 바로가기
개념

[JAVA의 정석] Chapter14

by cook_code 2024. 9. 8.
반응형

교재 목차

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)
코딩교육 티씨피스쿨

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()

5. 스트림과 for-loop의 성능 차이

5-1. 스트림과 for-loop 성능 차이

for문이 stream 보다 빠르다.

그 이유는,

  1. for문은 단순 인덱스 기반 반복문이며, 오버헤드가 없다.
  2. stream은 jvm이 처리하는 것이 많아 실행이 느리다.
  3. for문은 컴파일러가 최적화를 시킨다.

그럼에도 stream을 사용하는 이유,

  1. 가독성이 좋아진다.
  2. 코드로 작성해야하는 로직을 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