자바 8에서 추가한 스트림(Streams)은 람다1)를 활용할 수 있는 기술 중 하나이다. 이전에는 for 또는 foreach 문을 돌면서 요소 하나씩 꺼내서 다루는 방법이었다. 간단한 경우라면 상관없지만 로직이 복잡해지면 로직이 복잡해지고, 메소드를 나눌 경우 루프를 여러 번 도는 경우가 발생했다.
스트림이란? 데이터의 흐름으로 정의할 수 있다. 즉 배열과 컬렉션을 함수형으로 처리할 수 있다.
장점으로는 병렬처리(multi-threading)2)가 가능하다.
stream 사용이유? Collection을 사용하는 코드를 반복적으로 구현해야 했음 병렬적으로 Collection을 처리하기 어려웠기 때문이다. Divide and Conquer알고리즘이 성능을 높일 수 있는 방법 중 하나아기 때문에 작업들을 병렬적으로 수행하여 작업의 효율을 높일 수 있기 때문에 사용 --> parallelStream()
확인사항 parallelStream만 쓰면 되지 왜 stream도 사용하는 것일까? parallel stream을 남용할 경우 성능이 떨어지는 경우가 있다 ??!
스트림을 사용한 간단한 예시
import java.util.*;
import java.util.stream.Collectors;
public class StreamTest {
public static void main(String[] args) {
int arr[] = {8, 4, 10, 6, 6, 3};
List<Integer> list = new ArrayList<>();
Set<Integer> set = new HashSet<>();
//1. 스트림을 사용하지 않을 경우
for (int i = 0; i < arr.length; i++) { //배열을 set에 넣기
set.add(arr[i]);
}
Iterator<Integer> iterator = set.iterator(); //set을 iterator안에 담기
while(iterator.hasNext()){
list.add(iterator.next());
}
list.sort(Comparator.reverseOrder()); //역순으로 정렬
System.out.println("스트림을 사용하지 않은 출력 " + list.toString());
//2. 스트림을 사용하는 경우
System.out.println("스트림을 이용한 출력: " +
Arrays.stream(arr).boxed() //스트림생성
.distinct() //중복제거
.sorted(Comparator.reverseOrder()) //역정렬
.collect(Collectors.toList()) //List로 반환
);
}
}
위 예제로 알 수 있듯이 스트림은 배열이나 컬렉션(List, Set, Map)으로 원하는 값을 얻을 때 for문 도배를 방지할 수 있다.
스트림은 선언(생성), 가공, 반환(결과) 세 부분으로 이루어진다.
선언 : 배열, 컬렉션등을 스트림 형태로 만든다.
// 배열 스트림
String[] stringArr = new String[]{"a", "b", "c", "d"};
Stream<String> stream = Arrays.stream(stringArr);
Stream<String> streamOfPart = Arrays.stream(stringArr, 1, 3)
// 컬렉션 스트림
List<String> stringList = Arrays.asList("a", "b", "c", "d");
Stream<String> stream = stringList.stream();
Stream<String> parallelStream = stringList.parallelStream();
// 빈 스트림
Stream.empty();
// Stream.builder()
Stream<String> streamBuilder =
Stream.<String>builder()
.add("a").add("b").add("c")
.build();
// Stream.generate()
Stream<String> streamGenerated =
Stream.generate(() -> "hi").limit(5);
// Stream.iterate()
Stream<Integer> streamIterated =
Stream.iterate(10, n -> n + 3).limit(3);
// primitive stream
IntStream intStream = IntStream.range(1, 4); // [1, 2, 3]
LongStream longStream = LongStream.rangeClosed(1, 4); //[1, 2, 3, 4]
Stream<Integer> intStreamBox = IntStream.range(1, 4).boxed();
DoubleStream doubles = new Random().doubles(3); //난수 3개
// char stream
Stream<String> stringStream =
Pattern.compile(", ").splitAsStream("A, B, C");
// 파일 스트림
Stream<String> fileStream =
Files.lines(Paths.get("input.txt"),
Charset.forName("UTF-8"));
// 병렬 스트림 Parallel Stream
stream 대신 parallelStream 메소드를 사용한다.
// 스트림 연결하기 Stream.concat
Stream<String> stream1 = Stream.of("A", "B", "C");
Stream<String> stream2 = Stream.of("D", "E", "F");
Stream<String> concat = Stream.concat(stream1, stream2);
가공 : 스트림을 필요한 형태로 가공한다.
// Filtering
필터는 스트림 내 요소들을 하나씩 평가해서 걸러내는 작업이다.
// Mapping
맵은 스트림 내 요소들을 하나씩 특정 값으로 변환해준다. 이 때 값을 변환하기 위한 람다를 인자로 받는다.
flatMap -> 중첩 구조를 제거한 후 작업할 수 있게 해준다.
// Sorting
Comparator을 이용해서 정렬하는 것
//Iterating
peek은 스트림 내 요소들 각각에 특정 작업을 수행할 뿐 결과에 영향을 미치지는 않는다.
작업 중간에 사용할 수 있다.
반환 : 가공한 값을 원하는 형태로 가져오기
// Calculation
최소, 최대, 합, 평균 등 기본형 타입으로 결과를 만들 수 있다.
// reduction
reduce는 세 가지의 파라미터를 받을 수 있음
- accumulator : 각 요소를 처리하는 계산 로직, 각 요소가 올 때마다 중간 결과를 생성
- identity : 계산을 위한 초기값으로 스트림이 비어서 계산할 내용이 없더라도 이 값은 리턴
- combiner : 병렬 스트림에서 나눠 계산한 결과를 하나로 합치는 동장하는 로직
// Collecting
Collectors.toList() // 스트림에서 작업한 결과를 담은 리스트로 반환
Collectors.joining() // 스트림에서 작업한 결과를 하나의 스트링으로 이어 붙일 수 있음
- delimiter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자
- prefix : 결과 맨 앞에 붙는 문자
- suffix : 결과 맨 뒤에 붙는 문자
Collectors.averageingInt()
숫자 값의 평균을 낸다.
Collectors.summingInt()
숫자값의 합을 낸다.
Collectors.summarizingInt()
합계와 편균 모두 필요할 때
Collectors.groupingBy()
특정 조건으로 요소들을 그룹지을 수 있다.
여기서 받는 인자는 함수형 인터페이스 Function이다.
Collectors.partitioningBy()
함수형 인터페이스 Predicate를 받는다. Predicat인자를 받아서 boolean 값을 리턴
Collectors.collectingAndThen()
특정 타입으로 결과를 collect 한 이후에 추가 작업이 필요한 경우에 사용
Collectors.of()
직접 만들 수 있다.
// Matching
anyMatch : 하나라도 조건을 만족하는 요소가 있는지
allMatch : 모두 조건을 만족하는지
noneMatch : 모두 조건을 만족하지 않는지
// Iterating
forEach는 요소를 돌면서 실행되는 최종 작업 peek과는 중간 작업과 최종 작업이라는 차이가 있음
그렇다면 스트림의 단점은 무엇일까?
- 디버그가 힘듬
- 에러가 발생한다면 일반 코드의 경우 값이 잘못되기 직전에 디버그를 걸어놓으면 확인할 수 있음 하지만 스트림은 한 번에 수행되기 때문에 처음부터 전부 확인해야 한다.
- 재사용이 불가능
- 스트림은 한 번 쓰면 close되기 때문에 한 번 정의해놓고 계속 사용이 불가능하다.
1)람다식이란? 람다식이란 "식별자 없이 실행가능한 함수" 함수인데 함수를 따로 만들지 않고 코드한줄에 함수를 써서 그것을 호출하는 방식
2)병렬처리란? 하나의 작업을 둘 이상의 작업으로 잘게 나눠서 동시에 진행하는 것
[java stream reference](https://www.baeldung.com/java-8-streams)
stream 예제 풀어본 후 업로드 하겠습니다.
'개발자 > v0' 카테고리의 다른 글
java에서 enum 사용법 (0) | 2021.04.04 |
---|---|
window에서 ssh 서버 접근 쉽게하기 (0) | 2021.03.02 |
Markdown이란?, 마크다운 사용법, 마크다운 에디터 (0) | 2021.02.16 |
초보 개발자가 범하기 쉬운 실수 (2) | 2021.01.20 |
[ERROR] java.security.InvalidKeyException: Illegal key size (0) | 2021.01.12 |