티스토리 뷰
1. Stream 개요
1-1. Stream 이란?
💡 컬렉션에 저장된 엘리먼트들을 하나씩 순회하면서 처리할 수 있는 기능.
람다식과 함께 사용할 수 있으며 컬렉션에 들어있는 데이터에 대한 처리를 간결하게 표현
또한 내부 반복자를 사용하기 때문에 병렬처리가 쉽다는 장점.
package com.ohgiraffers.section01.intro;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. Stream에 대해 이해하고 활용할 수 있다. */
/* 설명.
* Arrays.asList(): 매개변수로 요소들을 전달하면 List로 반환
* ArrayList<>(Collection 타입): Collection 타입을 ArrayList 객체로 생성할 때 쓰이는 생성자
* */
List<String> stringList = new ArrayList<>(Arrays.asList("hello", "world", "stream"));
// .asList -> 가변 인자로 받고 반환형은 해당 타입의 List로 반환
System.out.println("====== foreach");
for(String str: stringList) {
System.out.println(str);
}
System.out.println("====== stream");
// stringList.stream().forEach(x -> System.out.println(x)); // stream으로 바뀐 ArrayList의 요소들이 각각
// 람다식에 적용되어 동작한다.
/* 필기. 복습하자!! */
stringList.stream().forEach(System.out::println); // 이런 코드 많이 쓴다. 이거 보고 위의 것처럼 인식할 수 있어야한다!
// 아직 시간복잡도 상 O(N)으로 효율이 생기진 않았다.
}
}
// 실행 결과
====== foreach
hello
world
stream
====== stream
hello
world
stream
1-2. Stream이 필요한 이유
- 배열 또는 컬렉션을 함수 여러 개를 사용해서 결과를 쉽게 얻을 수 있다.
- 람다식을 활용해 코드 양도 줄이고 간결하게 표현도 가능.
- 또 하나의 장점은 병렬처리가 가능.
package com.ohgiraffers.section01.intro;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Application2 {
public static void main(String[] args) {
/* 수업목표. 스트림의 병렬처리에 대해 이해할 수 있다. */
List<String> stringList =
new ArrayList<>(Arrays.asList("java", "mariadb", "jdbc", "html", "css"));
/* 설명. main 쓰레드 상에서 스트림을 사용하지 않고 확인 */
System.out.println("====== foreach");
for(String s: stringList) {
System.out.println(s + ": " + Thread.currentThread().getName());
}
/* 설명. main 쓰레드 상에서 단순 스트림을 사용하고 확인 */
/* 필기. forEach(): 각각의 요소에 대해서 반환형이 없이 그냥 실해하기 위해 사용 */
System.out.println("====== normal stream");
stringList.stream().forEach(System.out::println); // 잘 동작하는지 그냥 확인
// stringList.stream().forEach(x -> Applcation2.print(x));
stringList.stream().forEach(Application2::print);
/* 설명. main 쓰레드 상에서 병렬 스트림을 사용하고 확인(main 쓰레드 외의 다른 쓰레드를 활용한다.) */
/* parallel을 사용해야 실제로 병렬처리하면서 효율적이다. */
System.out.println("====== parallel stream");
stringList.parallelStream().forEach(Application2::print);
}
private static void print(String s) {
System.out.println(s + ": " + Thread.currentThread().getName());
System.out.println(s + ": " + Thread.currentThread().getName());
System.out.println(s + ": " + Thread.currentThread().getName());
}
}
// 실행 결과
====== foreach
java: main
mariadb: main
jdbc: main
html: main
css: main
====== normal stream
java
mariadb
jdbc
html
css
java: main
java: main
java: main
mariadb: main
mariadb: main
mariadb: main
jdbc: main
jdbc: main
jdbc: main
html: main
html: main
html: main
css: main
css: main
css: main
====== parallel stream
jdbc: main
jdbc: main
jdbc: main
mariadb: ForkJoinPool.commonPool-worker-1
mariadb: ForkJoinPool.commonPool-worker-1
mariadb: ForkJoinPool.commonPool-worker-1
html: ForkJoinPool.commonPool-worker-2
html: ForkJoinPool.commonPool-worker-2
html: ForkJoinPool.commonPool-worker-2
css: main
css: main
css: main
java: ForkJoinPool.commonPool-worker-4
java: ForkJoinPool.commonPool-worker-4
java: ForkJoinPool.commonPool-worker-4
1-3. Stream 특징
- 원본을 변경하지 않는 읽기 전용.
- `iterator` 처럼 한번만 사용되고 사라짐. 필요하면 다시 스트림을 생성해야 한다.
- 최종 연산 전까지 중간 연산이 처리되지 않는다.
- 병렬 처리가 용이하다.
2. Stream 활용
- 생성 : 스트림 생성
- 가공 : 원하는 결과를 만들기 위한 필터링, 매핑 등의 작업
- 결과 : 최종 결과를 만들어 내는 작업
배열, 컬렉션 → 스트림 생성 → 매핑 → 필터링 → 결과
2-1. Stream 생성
💡 자주 사용하는 배열과 컬렉션 객체에서 `stream()` 메소드를 지원.
2-1-1. 배열 스트림 생성
- 스트림을 생성할 배열을 생성한 후 `stream()` 사용.
2-1-2. 컬렉션 스트림 생성
- 컬렉션도 마찬가지로 `stream()`을 사용.
2-1-3. 비어 있는 스트림
- 비어있는 스트림도 생성할 수 있는데, 요소가 없을 떄 사용.
2-1-4. Stream.builder()
- 빌더를 사용해 직접 값을 넣을 수도 있다.
2-1-5. Stream().iterate()
- `iterate()` 메소드를 사용하여 수열 형태로 스트림을 생성.
2-1-6. 스트림 생성 - 기본 타입
- 스트림을 제네릭을 사용하지 않고 기본 타입으로 스트림을 생성할 수 있다.
- 제네릭을 사용하지 않기 때문에 불필요한 오토박싱도 일어나지 않는다.
- 필요한 경우에는 `boxed()`를 사용해 박싱을 할 수도 있다.
2-1-7. 문자 관련 스트림
2-1-8. 스트림 합치기
package com.ohgiraffers.section02.uses.subsection01.generation;
import java.util.Arrays;
import java.util.stream.Stream;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. 배열이나 컬렉션은 스트림을 이용할 수 있고 이를 이해해서 활용할 수 있다. */
String[] sArr = new String[] {"java", "mariadb", "jdbd"};
/* 필기. Arrays.stream(배열): 배열 자료형을 Stream 자료형으로 변환 */
/* 설명. 1. 배열로 스트림 생성 */
System.out.println("======= 배열로 스트림 생성 =======");
Stream<String> strStream1 = Arrays.stream(sArr);
strStream1.forEach(System.out::println);
System.out.println(); // 구분을 위한 개행
Stream<String> strStream2 = Arrays.stream(sArr, 0, 2);
strStream2.forEach(System.out::println);
System.out.println();
/* 설명. 2.
* Builder를 활용한 스트림 생성
* builder는 static<T>로 되어 있는 메소드이며, 호출 시 타입 파라미터를 메소드 호출 방식으로 전달한다.
* */
System.out.println("======= Builder로 스트림 생성 =======");
Stream<String> builderStream = Stream.<String>builder() // 메소드를 호출할 때 제네릭을 사용. 그냥 받아들이자...
.add("홍길동")
.add("유관순")
.add("윤봉길")
.build();
// 남용하지 말고! 고급 기술 사용 시 알고 쓰자! 면접 떄 공격 들어온다!~
builderStream.forEach(System.out::println);
/* 설명. 3. iterate()를 활용하여 수열 형태의 스트림을 생성 */
System.out.println("======== iterate로 스트림 생성 ========");
Stream<Integer> intStream = Stream.iterate(10, value -> value * 2)
.limit(10);
intStream.forEach(value -> System.out.print(value + " "));
// // 이렇게도 가능하다.
// Stream.iterate(10, value -> value * 2)
// .limit(10).forEach(value -> System.out.print(value + " "));
}
}
// 실행 결과
======= 배열로 스트림 생성 =======
java
mariadb
jdbd
java
mariadb
======= Builder로 스트림 생성 =======
홍길동
유관순
윤봉길
======== iterate로 스트림 생성 ========
10 20 40 80 160 320 640 1280 2560 5120
package com.ohgiraffers.section02.uses.subsection01.generation;
import java.util.Random;
import java.util.regex.Pattern;
import java.util.stream.IntStream;
import java.util.stream.LongStream;
import java.util.stream.Stream;
public class Application2 {
public static void main(String[] args) {
/* 수업목표. 기본 타입 스트림 생성에 대해 이해하고 활용할 수 있다. */
/* 필기.
* range(시작값, 종료값): 시작값부터 1씩 증가하는 숫자로 종료값 직전(-1)까지 범위의 스트림 생성
* rangeClosed(시작값, 종료값): 시작값부터 1씩 증가하는 숫자로 종료값까지 포함한 스트림 생성
* */
IntStream intStream = IntStream.range(5, 10);
intStream.forEach(value -> System.out.print(value + " "));
System.out.println();
LongStream longStream = LongStream.rangeClosed(5, 10); // 파이썬에도 가능? 나중에 꼭 사용해보자!
longStream.forEach(value -> System.out.print(value + " "));
System.out.println();
/* 필기.
* Wrapper 클래스 자료형의 스트림이 필요한 경우 boxing도 가능하다.
* double(갯수): 난수를 활용한 DoubleStream을 갯수만큼 생성하여 반환한다.
* boxed(): 기본 타입 스트림인 XXXStream을 박싱하여 Wrapper 타입의 Stream<XXX>로 변환한다.
* */
Stream<Double> doubleStream = new Random().doubles(5).boxed();
doubleStream.forEach(value -> System.out.print(value + " "));
System.out.println();
/* 설명. 문자열을 split하여 Stream으로 생성 */
// 그냥 통짜로 보자!
Stream<String> splitStream = Pattern.compile(", ").splitAsStream("html, css, javascript");
splitStream.forEach(System.out::println);
}
}
// 실행 결과
5 6 7 8 9
5 6 7 8 9 10
0.49334622605269485 0.9065024657621377 0.9595659744642123 0.6629202467254909 0.14572468272728478
html
css
javascript
2-2. Stream 가공
💡 스트림에 있는 데이터에 대해 내가 원하는 결과를 만들기 위해서 중간 처리 작업
2-2-1. Filter
- 스트림에서 특정 데이터만 걸러내는 메소드
- 매개변수로 받는 `Predicate`는 `boolean`을 리턴하는 함수형 인터페이스.
package com.ohgiraffers.section02.uses.subsection02.intermediate;
import java.util.stream.IntStream;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. 스트림의 중계 연산 중 하나인 filter에 대해 이해하고 사용할 수 있다. */
/* 필기. 중계 연산(intermediate operator): Stream을 반환하며 Stream 관련 메소드 체이닝이 이어진다. */
/* 설명. 필터(filter)는 스트림에서 특정 데이터만을 걸러내기 위한 메소드이다. */
IntStream intStream = IntStream.range(0, 10);
intStream.filter(i -> i % 2 == 0) // 쓸 수 있는 능력을 갖춰야한다!
.forEach(i -> System.out.print(i + " "));
}
}
// 실행 결과
0 2 4 6 8
2-2-2. Map
- `map()` 메소드는 값을 가공할 수 있는 람다식을 매개변수로 받는다.
- 스트림에 들어있는 데이터를 특정 람다식을 통해 데이터를 가공하고 새로운 스트림에 담아주는 역할
package com.ohgiraffers.section02.uses.subsection02.intermediate;
import java.util.stream.IntStream;
public class Application2 {
public static void main(String[] args) {
/* 수업목표. 스트림의 중계 연산 중 하나인 map에 대해 이해하고 사용할 수 있다. */
/* 설명. 맵(map)은 스트림에 들어있는 데이터를 람다식으로 가공하고 새로운 스트림에 담아주는 메소드이다. */
IntStream intStream = IntStream.range(1, 10);
intStream.filter(i -> i % 2 != 0) // boolean 반환형으로 람다식 작성(predicate)
.map(i -> i * 5) // 요소를 반환하는 형태로 람다식 작성(operator)
.forEach(result -> System.out.print(result + " ")); // 반환형이 없는 람다식 작성(consumer)
}
}
// 실행 결과
5 15 25 35 45
2-2-3. flatMap
- 중첩 구조를 한 단게 제거하고 단일 컬렉션으로 만들어준다.
package com.ohgiraffers.section02.uses.subsection02.intermediate;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class Application3 {
public static void main(String[] args) {
/* 수업목표. 스트림의 중계 연산 중 하나인 flatMap에 대해 이해하고 사용할 수 있다. */
List<List<String>> list = Arrays.asList(
Arrays.asList("JAVA", "SPRING", "SPRINGBOOT"),
Arrays.asList("java", "spring", "springboot")
);
list.stream().forEach(System.out::println);
System.out.println("list = " + list);
List<String> flatList = list.stream().flatMap(Collection::stream)
.collect(Collectors.toList());
flatList.stream().forEach(System.out::println);
System.out.println("flatList = " + flatList); // depth가 깊어져도 하나의 리스트로 만들어준다.
}
}
// 실행 결과
[JAVA, SPRING, SPRINGBOOT]
[java, spring, springboot]
list = [[JAVA, SPRING, SPRINGBOOT], [java, spring, springboot]]
JAVA
SPRING
SPRINGBOOT
java
spring
springboot
flatList = [JAVA, SPRING, SPRINGBOOT, java, spring, springboot]
2-2-4. Sorting
- 데이터를 정렬
package com.ohgiraffers.section02.uses.subsection02.intermediate;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
public class Application4 {
public static void main(String[] args) {
/* 수업목표. 스트림의 중계 연산 중 하나인 sorted에 대해 이해하고 사용할 수 있다. */
List<Integer> integerList = IntStream.of(5, 10, 99, 2, 1, 35)
.boxed() // sorted를 사용하기 위해 int 형으로 바꿔준다.
// .sorted()
.sorted(new DescInteger())
.collect(Collectors.toList());
System.out.println("정렬 된 Integer List: " + integerList);
}
}
// 실행 결과
정렬 된 Integer List: [99, 35, 10, 5, 2, 1]
package com.ohgiraffers.section02.uses.subsection02.intermediate;
import java.util.Comparator;
public class DescInteger implements Comparator<Integer> {
@Override
public int compare(Integer o1, Integer o2) {
return o2 - o1; // 매개변수와 순서를 바꾸면 내림차순
}
}
2-3. Stream 결과
💡 데이터를 필터링하고 가공한 뒤에 출력하기 위해서 진행하는 작업
2-3-1. Calculating
- 다양한 메소드들을 제공.
- 최소/최대/총합/평균 등
- 만약 스트림이 비어 있으면 count와 sum은 0을 출력
package com.ohgiraffers.section02.uses.subsection03.terminal;
import java.util.OptionalInt;
import java.util.stream.IntStream;
public class Application1 {
public static void main(String[] args) {
/* 수업목표. 스트림의 최종 연산(terminal Operator) 중 하나인 calculation에 대해 이해하고 사용할 수 있다. */
/* 필기. 최종 연산: Stream이 아닌 값을 반환하며 Stream이 끝날 때 사용한다. */
long count = IntStream.range(1, 10).count();
int sum = IntStream.range(1, 10).sum();
System.out.println("count = " + count);
System.out.println("sum = " + sum);
/* 설명. OptionalInt는 '결과 없음'을 나타내야 하는 명확한 요구가 있는 메소드 반환 형식으로 사용하기 위한 타입이다. */
OptionalInt max = IntStream.range(1, 10).max();
// OptionalInt max = IntStream.range(1, 1).max(); // OptionalInt: 기본자료형도 존재하지 않으면
// // empty의 개념을 주기위한 타입
OptionalInt min = IntStream.range(1, 10).min();
System.out.println("max = " + max);
System.out.println("min = " + min);
int oddSum = IntStream.range(1, 10)
.filter(i -> i % 2 == 1)
.sum();
System.out.println("oddSum = " + oddSum);
}
}
// 실행 결과
count = 9
sum = 45
max = OptionalInt[9]
min = OptionalInt[1]
oddSum = 25
2-3-2. Reduction
- `reduce()` 라는 메소드는 스트림에 있는 데이터들의 총합을 계산
- 파라미터에 따라 3가지 종류가 있다.
package com.ohgiraffers.section02.uses.subsection03.terminal;
import java.util.OptionalInt;
import java.util.stream.IntStream;
import java.util.stream.Stream;
public class Application2 {
public static void main(String[] args) {
/* 수업목표. 스트림의 최종 연산 중 하나인 reduce에 대해 이해하고 사용할 수 있다. */
/* 설명. 인자가 1개일 경우 */
OptionalInt reduceOneParam = IntStream.range(1, 4)
.reduce((a, b) -> Integer.sum(a, b)); // reduce -> 누적을 시킨다!
System.out.println("reduceOneParam = " + reduceOneParam.getAsInt());
/* 설명. 인자가 2개일 경우 */
int reduceTwoParam = IntStream.range(1, 4)
.reduce(100, Integer::sum); // identity부터 누적 시작
// Integer::sum == (a, b) -> Integer.sum(a, b)
System.out.println("reduceTwoParam = " + reduceTwoParam);
/* 설명. 인자가 3개일 경우 */
/* 설명. 매개변수 3번째는 좀 더 효율적인 가산 누적연산을 위한 충간 합계 처리용 람다식을 작성한다.(2번째 인자의 결과와 호환이 가능해야 한다.) */
Integer reduceThreeParam = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.reduce(100, Integer::sum, (x, y) -> x + y);
System.out.println("reduceThreeParam = " + reduceThreeParam);
}
}
// 실행 결과
reduceOneParam = 6
reduceTwoParam = 106
reduceThreeParam = 155
2-3-3. Collecting
- `collect()`는 Collector 타입을 받아서 처리, 해당 메소드르 통해 컬렉션을 출력으로 받을 수 있다.
- `collect()` 메소드는 Collector 객체에서 제공하는 정적 메소드를 사용할 수 있다.
- Collectors.toList()
: 스트림 작업 결과를 리스트로 반환 - Collectors.joining()
: 스트림의 작업 결과를 String 타입으로 이어 붙인다.
: 세 개의 인자를 받을 수 있다. → delimiter(구분자), prefix(맨 앞 문자), suffix(맨 뒤 문자)
package com.ohgiraffers.section02.uses.subsection03.terminal;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Application3 {
public static void main(String[] args) {
/* 수업목표. 스트림의 최종 연산 중 하나인 Collect에 대해 이해하고 사용할 수 있다. */
/* 필기.
* collect()는 Collector 객체에서 제공하는 정적(static)메소드를 사용할 수 있고 해당 메소드들을 통해
* 일반적으로 컬렉션(list, Set, Map)을 출력으로 받을 수 있다.
* */
List<Member> memberList = Arrays.asList(
new Member("test01", "testName01"),
new Member("test02", "testName02"),
new Member("test03", "testName03")
);
List<String> collectorCollection = memberList.stream()
.map(Member::getMemberName) // 중계 연산
.collect(Collectors.toList()); // 최종 연산
collectorCollection.forEach(System.out::println);
/* 설명. joining()은 요소들을 하나로 합쳐서 하나의 문자열로 바꿔주는 메소드이다. */
String str = memberList.stream()
.map(Member::getMemberName)
.collect(Collectors.joining());
System.out.println("str = " + str);
String str2 = memberList.stream()
.map(Member::getMemberName)
.collect(Collectors.joining("||", "**", "!!"));
System.out.println("str2 = " + str2);
}
}
// 실행 결과
testName01
testName02
testName03
str = testName01testName02testName03
str2 = **testName01||testName02||testName03!!
package com.ohgiraffers.section02.uses.subsection03.terminal;
public class Member {
private String memberId;
private String memberName;
public Member() {
}
public Member(String memberId, String memberName) {
this.memberId = memberId;
this.memberName = memberName;
}
public String getMemberId() {
return memberId;
}
public void setMemberId(String memberId) {
this.memberId = memberId;
}
public String getMemberName() {
return memberName;
}
public void setMemberName(String memberName) {
this.memberName = memberName;
}
@Override
public String toString() {
return "Member{" +
"memberId='" + memberId + '\'' +
", memberName='" + memberName + '\'' +
'}';
}
}
2-3-4. Matching
- Predicate를 인자로 받아 조건을 만족하는 엘리먼트가 있는지 확인하고 `boolean` 으로 리턴
package com.ohgiraffers.section02.uses.subsection03.terminal;
import java.util.Arrays;
import java.util.List;
public class Application4 {
public static void main(String[] args) {
/* 수업목표. 스트림의 최종 연산 중 하나인 match에 대해 이해하고 사용할 수 있다. */
List<String> stringList = Arrays.asList("Java", "Spring", "SpringBoot");
boolean anyMatch = stringList.stream() // 하나라도 만족하면 true
.anyMatch(str -> str.contains("p"));
boolean allMatch = stringList.stream() // 모두 만족하면 true
.allMatch(str -> str.length() > 4);
boolean noneMatch = stringList.stream() // 하나도 만족하지 않으면 true
.noneMatch(str -> str.contains("c"));
System.out.println("anyMatch = " + anyMatch);
System.out.println("allMatch = " + allMatch);
System.out.println("noneMatch = " + noneMatch);
}
}
// 실행 결과
anyMatch = true
allMatch = false
noneMatch = true
'한화시스템 > 백엔드' 카테고리의 다른 글
[BE] Spring AOP (0) | 2024.08.12 |
---|---|
[BE] JAVA_ENUM (0) | 2024.07.24 |
[BE] JAVA_컬렉션(Collection)_Map (2) | 2024.07.24 |
[BE] JAVA_컬렉션(Collection)_Set (1) | 2024.07.24 |
[BE] JAVA_컬렉션(Collection)_List (1) | 2024.07.23 |