자바 Stream | [자바의 정석 – 기초편] Ch14-15,16 스트림, 스트림의 특징 인기 답변 업데이트

당신은 주제를 찾고 있습니까 “자바 stream – [자바의 정석 – 기초편] ch14-15,16 스트림, 스트림의 특징“? 다음 카테고리의 웹사이트 you.maxfit.vn 에서 귀하의 모든 질문에 답변해 드립니다: you.maxfit.vn/blog. 바로 아래에서 답을 찾을 수 있습니다. 작성자 남궁성의 정석코딩 이(가) 작성한 기사에는 조회수 12,173회 및 좋아요 195개 개의 좋아요가 있습니다.

자바 stream 주제에 대한 동영상 보기

여기에서 이 주제에 대한 비디오를 시청하십시오. 주의 깊게 살펴보고 읽고 있는 내용에 대한 피드백을 제공하세요!

d여기에서 [자바의 정석 – 기초편] ch14-15,16 스트림, 스트림의 특징 – 자바 stream 주제에 대한 세부정보를 참조하세요

[자바의 정석 – 기초편] ch14-15,16 스트림, 스트림의 특징에 대한 강의입니다.
주위에 많은 소개 부탁드릴게요. 동영상강의를 만드는데 많은 힘이 됩니다.
http://www.codechobo.com

자바 stream 주제에 대한 자세한 내용은 여기를 참조하세요.

Java 스트림 Stream (1) 총정리

스트림 Streams. 자바 8에서 추가한 스트림(Streams)은 람다를 활용할 수 있는 기술 중 하나입니다. 자바 8 이전에는 배열 또는 컬렉션 인스턴스 …

+ 여기에 보기

Source: futurecreator.github.io

Date Published: 5/28/2021

View: 8160

[Java] 자바 스트림(Stream) 사용법 및 예제 – 데이터 엔지니어링

자바의 스트림(Stream)은 ‘Java 8’부터 지원되기 시작한 기능이다. 컬렉션에 저장되어 있는 엘리먼트들을 하나씩 순회하면서 처리할 수 있는 코드패턴 …

+ 여기에 더 보기

Source: hbase.tistory.com

Date Published: 7/7/2022

View: 9184

자바 – 스트림(Stream)

스트림(Stream). 다양한 데이터 소스(컬렉션, 배열 등)를 표준화된 방법으로 다루기 위한 것. 그 전까지는 List, Set, Map 등의 사용 방법들이 서로 …

+ 여기에 표시

Source: velog.io

Date Published: 9/24/2021

View: 8100

자바 스트림(Stream) 예제부터 사용법까지 정리

자바에서 스트림이라고 하면 대부분 엄청나게 어려운 기술인 줄 알고 시작도 전에 포기하는 경우가 많은데 스트림은 엄청 간단하고 유용한 기술이다 …

+ 여기에 보기

Source: wakestand.tistory.com

Date Published: 3/4/2021

View: 404

Stream (Java Platform SE 8 ) – Oracle Help Center

Streams are created with an initial choice of sequential or parallel execution. (For example, Collection.stream() creates a sequential stream, and Collection.

+ 여기에 보기

Source: docs.oracle.com

Date Published: 6/5/2021

View: 1800

[Java] Stream API의 활용 및 사용법 – 기초 (3/5) – 망나니개발자

Filter는 Stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어내는 연산이다. Java에서는 filter 함수의 인자로 함수형 인터페이스 …

+ 여기에 표시

Source: mangkyu.tistory.com

Date Published: 10/10/2021

View: 1386

스트림(stream) – 코딩의 시작, TCP School

자바에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림(stream)이라는 흐름을 통해 다룹니다. 스트림(stream)이란 실제의 입력이나 출력이 표현된 데이터의 …

+ 여기에 더 보기

Source: www.tcpschool.com

Date Published: 2/22/2021

View: 5833

[Java] 스트림(Stream) 익히기 – 코딩 바다에서의 항해일지

스트림은 컬렉션에 저장된 요소를 하나식 꺼내서 람다식으로 처리하는 반복자이다. 스트림을 사용하기 위해서는 람다식에 대한 지식이 필요하며 컬렉션, …

+ 여기에 자세히 보기

Source: makecodework.tistory.com

Date Published: 7/1/2022

View: 1890

자바 스트림(Stream) API 정리, 스트림을 이용한 가독성 좋은 …

Stream은 자바 8부터 추가된 기능으로 “컬렉션, 배열등의 저장 요소를 하나씩 참조하며 함수형 인터페이스(람다식)를 적용하며 반복적으로 처리할 수 …

+ 여기에 자세히 보기

Source: jeong-pro.tistory.com

Date Published: 11/6/2021

View: 8938

Java에서 스트림을 만드는 10가지 방법

Java Collection 프레임워크는 두 가지 방법을 제공합니다. stream() 그리고 parallelStream() , 모든 컬렉션에서 각각 순차 및 병렬 스트림을 생성합니다.

+ 여기에 보기

Source: www.techiedelight.com

Date Published: 5/3/2021

View: 8749

주제와 관련된 이미지 자바 stream

주제와 관련된 더 많은 사진을 참조하십시오 [자바의 정석 – 기초편] ch14-15,16 스트림, 스트림의 특징. 댓글에서 더 많은 관련 이미지를 보거나 필요한 경우 더 많은 관련 기사를 볼 수 있습니다.

[자바의 정석 - 기초편] ch14-15,16 스트림, 스트림의 특징
[자바의 정석 – 기초편] ch14-15,16 스트림, 스트림의 특징

주제에 대한 기사 평가 자바 stream

  • Author: 남궁성의 정석코딩
  • Views: 조회수 12,173회
  • Likes: 좋아요 195개
  • Date Published: 2020. 12. 3.
  • Video Url link: https://www.youtube.com/watch?v=7Kyf4mMjbTQ

Java 스트림 Stream (1) 총정리

1

2

3

4

5

6

List productList =

Arrays.asList( new Product( 23 , “potatoes” ),

new Product( 14 , “orange” ),

new Product( 13 , “lemon” ),

new Product( 23 , “bread” ),

new Product( 13 , “sugar” ));

[Java] 자바 스트림(Stream) 사용법 및 예제

반응형

자바 스트림(Stream)

자바의 스트림(Stream)은 ‘Java 8’부터 지원되기 시작한 기능이다. 컬렉션에 저장되어 있는 엘리먼트들을 하나씩 순회하면서 처리할 수 있는 코드패턴이다. 람다식과 함께 사용되어 컬렉션에 들어있는 데이터에 대한 처리를 매우 간결한 표현으로 작성할 수 있다. 또 한, 내부 반복자를 사용하기 때문에 병렬처리가 쉽다는 점이 있다.

‘Java 6’ 이전까지는 컬렉션의 엘리먼트들을 순회하기 위해서 Iterator 객체를 이용했다.

ArrayList list = new ArrayList(Arrays.asList(“a”, “b”, “c”)); Iterator iterator = list.iterator(); while(iterator.hasNext()) { String value = iterator.next(); if (StringUtils.equals(value, “b”) { System.out.println(“값 : ” + value); } }

“컬렉션을 순회하면서 값들을 출력하라”라는 단순한 동작을 위해서 보기에 지저분한 코드들이 많이 생성된다. for each 구문을 이용하면 좀 더 깔끔해지긴한다.

ArrayList list = new ArrayList(Arrays.asList(“a”, “b”, “c”)); for (String value : list) { if (StringUtils.equals(value, “b”) { System.out.println(“값 : ” + value); } }

‘Java 8’부터 추가된 스트림을 사용하면 조금 더 단순하게 코드를 작성할 수 있다.

ArrayList list = new ArrayList(Arrays.asList(“a”, “b”, “c”)); list.stream() .filter(“b”::equals) .forEach(System.out::println);

스트림(Stream) 사용법

스트림 생성

스트림 생성 – 컬렉션

자바의 스트림을 사용하려면 우선 스트림 객체를 생성해야한다.

List list = Arrays.asList(“a”, “b”, “c”); Stream stream = list.stream();

자바 코드에서 자주 사용하는 컬렉션 객체들은 stream() 메소드를 지원한다. 컬렉션 객체에서 stream() 메소드를 호출하면 스트림 객체를 만들 수 있다.

스트림 생성 – 배열

배열의 경우 정적 메소드를 이용하면 된다.

String[] array = new String[]{“a”, “b”, “c”}; Stream stream1 = Arrays.stream(array); Stream stream2 = Arrays.stream(array, 1, 3); // 인덱스 1포함, 3제외 (“b”, “c”)

정적 메소드 ‘Arrays.stream()’에 인자로 배열을 입력하면 배열을 순회하는 스트림 객체를 만들 수 있다. Arrays.stream() 메소드에 배열과 시작, 종료 인덱스를 인자로 주면 배열의 일부를 순회하는 스트림 객체를 만들 수도 있다. (이 때, 종료 인덱스는 포함되지 않음을 주의해야한다)

스트림 생성 – 빌더

배열이나 컬렉션을 통해서 생성하는게 아닌 직접 값을 입력해서 스트림 객체를 생성하는 방법도 있다.

String stream = Streambuilder() .add(“Apple”) .add(“Banana”) .add(“Melon”) .build();

이렇게 만들어진 스트림 객체는 “Apple”, “Banana”, “Melon” 순서로 문자열 데이터를 처리하게 된다.

스트림 생성 – Generator

데이터를 생성하는 람다식을 이용해서 스트림을 생성할 수도 있다.

public static Stream generate(Supplier s) { … }

Supplier 에 해당하는 람다식이 데이터를 생성하는 람다식이다.

Stream stream = Stream.generate(() -> “Hello”).limit(5);

generate() 메소드의 인자로 “Hello”를 찍어주는 람다식을 주었다. 이렇게 되면 “Hello”라는 데이터를 무한대로 생성하는 스트림이 만들어진다. 여기에 limit() 메소드를 이용해서 스트림이 “Hello” 문자열을 5개만 찍어내도록 제한을 걸어줬다.

스트림 생성 – Iterator

혹은 iterate() 메소드를 이용해서 수열 형태의 데이터를 생성할 수도 있다.

// (100, 110, 120, 130, 140) Stream stream = Stream.iterate(100, n -> n + 10).limit(5);

‘n → n + 10’ 이라는 람다를 인자로 넘겨서 초기 값 100부터 10씩 증가하는 숫자를 생성하는 스트림을 만들 수 있다.

스트림 생성 – Empty 스트림

특수한 스트림으로 ‘빈 스트림(Empty Stream)’을 사용할 수 있다. stream 객체를 참조하는 변수가 null이라면 NullPointException이 발생할 수도 있다.

Stream stream = Stream.empty();

이럴 때에는 ‘Stream.empty()’를 사용하면 된다.

스트림 생성 – 기본 타입

자바에서는 기본타입(Primitive Type)에 대해서 오토박싱과 언박싱이 발생한다. int 변수를 다룰 때, Integer 클래스로 오토박생해서 처리하는 경우가 있는데, 이 경우 오버헤드가 발생해서 성능저하가 있을 수 있다. 스트림 객체의 생성에서도 마찬가지인데 오토박싱을 하지 않으려면 다음과 같이 스트림을 사용하면 된다.

IntStream intStream = IntStream.range(1, 10); // 1 ~ 9 LongStream longStream = LngStream.range(1, 10000); // 1 ~ 9999

이러면 오토박싱이 수행되지 않는다.

Stream stream = IntStream.range(1, 10).boxed();

제네릭을 이용한 클래스로 사용하려면 박싱을 해서 사용해야한다.

정해진 값이 아니라 랜덤 값을 스트림으로 뽑아내려면 Random() 클래스를 사용하면 된다.

DoubleStream stream = new Random().double(3); // double 형 랜덤 숫자 3개 생성

스트림 생성 – 문자열 스트림

문자열에 대해서 스트림을 생성할 수도 있다.

IntStream stream = “Hello,World”.chars(); //(72, 101, 108, 108, 111, 44, 87, 111, 114, 108, 100)

문자열을 구성하고 있는 문자들의 ASCII 코드 값을 스트림형태로 뽑아주는 예제코드다.

Stream stream = Pattern.compile(“,”).splitAsStream(“Apple,Banana,Melon”);

특정 구분자(Delimiter)를 이용해서 문자열을 스플릿 한 다음 각각을 스트림으로 뽑아낼 수도 있다.

스트림 생성 – 파일

텍스트 파일을 읽어서 라인단위로 처리하는 코드는 매우 흔하다. 이런 코드 역시 스트림으로 작성할 수 있다.

Stream stream = Files.lines(Paths.get(“test.txt”), Charset.forName(“UTF-8”));

‘test.txt’ 파일의 데이터를 라인단위로 읽어서 뽑아주는 스트림 객체다. 이 때, 데이터는 ‘UTF-8’로 디코딩해서 읽어들인다.

스트림 생성 – 스트림연결

두 개의 스트림을 연결해서 하나의 새로운 스트림으로 만들어 낼 수도 있다.

Stream stream1 = Stream.of(“Apple”, “Banana”, “Melon”); Stream stream2 = Stream.of(“Kim”, “Lee”, “Park”); Stream stream3 = Stream.concat(stream1, stream2); // “Apple”, “Banana”, “Melon”, “Kim”, “Lee”, “Park”

Stream.concat() 메소드를 이용해서 두 개의 스트림을 붙여서 새로운 스트림을 만들 수 있다.

스트림 데이터 가공

스트림 객체가 뽑아내는 데이터들에 대해 뭔가 작업을 해야한다. 특정 데이터들만 걸러내거나 데이터에 대해서 가공을 할 수 있다. 데이터를 가공해주는 메소드들은 가공된 결과를 생성해주는 스트림 객체를 리턴한다.

Filter

필터(filter)는 스트림에서 뽑아져 나오는 데이터에서 특정 데이터들만 골라내는 역할을 한다.

Stream filter(Predicate predicate);

filter() 메소드에는 boolean 값을 리턴하는 람다식을 넘겨주게된다. 그러면 뽑아져 나오는 데이터에 대해 람다식을 적용해서 true가 리턴되는 데이터만 선별한다.

filter의 간단한 예를 살펴보자.

Stream stream = IntStream.range(1, 10).boxed(); stream.filter(v -> ((v % 2) == 0)) .forEach(System.out::println); // 2, 4, 6, 8

1부터 9까지 데이터를 뽑아내는 스트림을 만들고, filter 메소드에 짝수를 선별해주는 람다식을 넣어줬다. 이러면 1부터 9까지의 데이터 중 짝수 데이터만 뽑아내주는 스트림 객체가 리턴된다.

Map

map()은 스트림에서 뽑아져 나오는 데이터에 변경을 가해준다.

Stream map(Function mapper);

map() 메소드는 값을 변환해주는 람다식을 인자로 받는다. 스트림에서 생성된 데이터에 map() 메소드의 인자로 받은 람다식을 적용해 새로운 데이터를 만들어낸다.

Stream stream = IntStream.range(1, 10).boxed(); stream.filter(v -> ((v % 2) == 0)) .map(v -> v * 10) .forEach(System.out::println); // 20, 40, 60, 80

위 예제를 보면, 1부터 9까지의 숫자 중에 filter()를 이용해서 짝수만 뽑아낸 다음 곱하기 10을 해서 10배에 해당하는 숫자를 생성하는 스트림 예제다.

flatMap

map() 메소드와 비슷한 역할을 하는 flatMap() 메소드도 있다.

Stream flatMap(Function> mapper);

flatMap() 메소드의 인자로 받는 람다는 리턴 타입이 Stream이다. 즉, 새로운 스트림을 생성해서 리턴하는 람다를 인자로 받는다. flatMap()은 중첩된 스트림 구조를 한단계 적은 단일 컬렉션에 대한 스트림으로 만들어주는 역할을 한다. 프로그래밍에서는 이런 작업을 ‘플랫트닝(Flattening)’이라고 한다.

예를 들어보자.

List> list = Arrays.asLists(Arrays.asList(“A”, “B”, “C”), Arrays.asList(“a”, “b”, “c”)); // [[“A”, “B”, “C”], [“a”, “b”, “c”]]

이런 형태의 이중 구조 리스트를 스트림으로 순회한다고 생각해보자.

List> list = Arrays.asLists(Arrays.asList(“A”, “B”, “C”), Arrays.asList(“a”, “b”, “c”)); List flatList = list.stream() .flatMap(Collection::stream) .collect(Collectors.toList()); // [“A”, “B”, “C”, “a”, “b”, “c”]

한 껍데기를 벗겨내서 조금 플랫한 리스트로 만들어주는 예제다. 여기서 람다는 ‘(e) → Collection.stream(e)’이며, 이를 축약해서 ‘Collection::stream’으로 사용했다.

Sorted

스트림 데이터들을 정렬하고자할 때, sorted() 메소드를 이용한다.

Stream sorted(); Stream sorted(Comparator comparator);

인자 없이 sorted() 메소드를 호출할 때에는 오름차순으로 정렬한다. 만약 정렬할 때 두 값을 비교하는 별도의 로직이 있다면, comparator를 sorted() 메소드의 인자로 넘겨줄 수도 있다.

Peek

peek() 메소드는 스트림 내 엘리먼트들을 대상으로 map() 메소드처럼 연산을 수행한다. 하지만 새로운 스트림을 생성하지는 않고 그냥 인자로 받은 람다를 적용하기만 한다.

Stream peek(Consumer action);

peek() 메소드는 그냥 한번 해본다는 의미로 생성되는 데이터들에 변형을 가하지 않고 그냥 인자로 받은 람다식만 수행해준다.

int sum = IntStream.range(1, 10) .peek(System.out::println) .sum();

이런식으로 중간에 로깅 같은 것을 하고자 할 때, peek() 메소드를 사용하면 좋다.

스트림 결과 생성

지금까지 본 데이터 수정 연산들은 또 데이터에 수정을 가한 결과 데이터들을 만들어내는 또 다른 스트림 객체를 리턴했다. 즉, 중간 작업(Intermediate Operations)들이며 이들만으로는 의미있는 스트림을 만들 수 없다. 데이터를 가공하고, 필터링한 다음 그 값을 출력하거나 또 다른 컬렉션으로 모아두는 등의 마무리 작업이 필요하다.

통계 값

정수 값을 받는 스트림의 마무리는 ‘총합’을 구하거나 ‘최대값’, ‘최소값’, ‘숫자의 개수’, ‘평균 값’ 등에 대한 계산이다.

int sum = IntStream.range(1, 10).sum(); int count = IntStream.range(1, 10).count(); int max = IntStream.range(1, 10).max(); int min = IntStream.range(1, 10).min(); int avg = IntStream.range(1, 10).average(); // 짝수 숫자의 총합 int evenSum = IntStream.range(1, 10) .filter(v -> ((v % 2) == 0)) .sum();

만약 비어있는 스트림이라면 count(), sum() 메소드는 0을 리턴한다. 최대, 최소 값의 경우 Optional을 이용해 리턴한다.

Reduce

중간 연산을 거친 값들은 reduce 라는 메소드를 이용해 결과값을 만들어낸다. reduce() 메소드는 파라미터에 따라 3가지 종류가 있다.

// 스트림에서 나오는 값들을 accumulator 함수로 누적 Optional reduce(BinaryOperator accumulator); // 동일하게 accumulator 함수로 누적하지만 초기값(identity)이 있음 T reduce(T identity, BinaryOperator accumulator);

우선 스트림에서 뽑아져 나오는 값들을 누적시키는 accumulator 함수는 2개의 파라미터를 인자로 받아 하나의 값을 리턴하는 함수형 인터페이스다.

예를 들어

(a, b) -> Integer.sum(a, b)

이런 함수를 accumulator로 사용할 수 있다.

스트림은 때에 따라서 뽑아져 나오는 값이 없을 수 있다. 중간에 filter() 메소드를 거치면서 값이 하나도 나오지 않을 수 있기 때문이다. 때문에 reduce() 메소드의 리턴값은 Optional이다. 만약 초기값을 주는 reduce() 메소드를 사용한다면, 초기값이 있기 때문에 Optional이 아닌 것을 확인할 수 있다. 뽑아져 나오는 값은 초기값에 accumulator 함수가 적용된다.

Collect

자바 스트림을 이용하는 가장 많은 패턴 중 하나는 컬렉션의 엘리먼트 중 일부를 필터링하고, 값을 변형해서 또 다른 컬렉션으로 만드는 것이다.

Set evenNumber = IntStream.range(1, 1000).boxed() .filter(n -> (n%2 == 0)) .collect(Collectors.toSet());

자바 스트림은 collect() 메소드를 이용해 뽑아져 나오는 데이터들을 컬렉션으로 모아 둘 수 있다. 위 예제는 1부터 999까지의 숫자 중 짝수만 모아서 Set 컬렉션에 모아두는 예제다.

collect() 메소드에는 Collector 메소드를 사용할 수 있다. Collector 클래스에 있는 정적 메소드를 이용해서 뽑아져나오는 객체들을 원하는 컬렉션으로 만들 수 있다. Collector.toList()를 호출하면 리스트로 만들고, Collector.toSet()을 호출하면 Set으로 만들어준다.

Collector.joining()을 사용하면 작업한 결과를 하나의 문자열로 이어 붙이게 된다.

List fruit = Arrays.asList(“Banana”, “Apple”, “Melon”); String returnValue = fruit.stream() .collect(Collectors.joining()); System.out.println(returnValue); // BananaAppleMelon

Collector.joining() 메소드에 추가로 인자를 주면 문자열을 좀 더 멋지게 붙일 수 있다.

List fruit = Arrays.asList(“Banana”, “Apple”, “Melon”); String returnValue = fruit.stream() .collect(Collectors.joining(“,”, “<", ">“)); System.out.println(returnValue); //

첫 번째 인자는 구분자이고, 두번째는 문자열의 맨 처음(prefix), 세번째는 문자열의 마지막(suffix)에 올 문자열이다.

foreach

스트림에서 뽑아져 나오는 값에 대해서 어떤 작업을 하고 싶을 때 foreach 메소드를 사용한다. 이 메소드는 앞에서 본 메소드들과 다르게 어떤 값을 리턴하지는 않는다.

Set evenNumber = IntStream.range(1, 1000).boxed() .filter(n -> (n%2 == 0)) .forEach(System.out::println);

1부터 999까지의 숫자 중 짝수만 뽑아내서 출력하는 코드다.

여기까지가 자바 스트림 기초다. 시간이 되면 조금 더 고급 내용을 정리하겠다.

반응형

자바 – 스트림(Stream)

본 게시물은 스스로의 공부를 위한 글입니다.

틀린 내용이 있을 수 있습니다.

📒 스트림(Stream)

다양한 데이터 소스(컬렉션, 배열 등)를 표준화 된 방법으로 다루기 위한 것 그 전까지는 List, Set, Map 등의 사용 방법들이 서로 달랐다.

된 방법으로 다루기 위한 것 데이터 소스를 스트림으로 변환 후 여러 번의 중간연산과 마지막의 최종 연산을 통해 다를 수 있다.

순서 스트림 만들기 중간연산(반복 적용 가능, 연산 결과가 스트림) 최종연산 (스트림의 요소를 소모) -> 결과 리턴

list . stream ( ) . distinct ( ) . limit ( 5 ) . sorted ( ) . forEach ( System . out : : println )

📒 스트림의 특징

데이터를 담고 있는 저장소 (컬렉션)이 아니다. 스트림은 원본 데이터 소스를 변경하지 않는다.(Read Only) 스트림은 lterator 처럼 일회용이다. (필요하면 다시 스트림을 생성해야 함) 최종 연산 전까지 중간연산을 수행되지 않는다.(lazy) 무제한일 수도 있다. (Short Circuit 메소드를 사용해서 제한할 수 있다.) 손쉽게 병렬 처리할 수 있다. (멀티 쓰레드 사용) ( .parallel ) 기본형 스트림으로 IntStream , LongStream , DoubleStream 등 제공 오토박싱 등의 불필요한 과정이 생략됨.

Stream 대신에 IntStream 을 사용하는게 더 효율적이다.

대신에 을 사용하는게 더 효율적이다. 뿐만 아니라 숫자의 경우 더 유용한 메서드를 Stream 보다 더 많이 제공한다.( .sum() , .averge() 등)

📒 스트림 사용하기

📌 1. 스트림 생성

Collection의 경우 .stream() 으로 스트림 변환

Stream < Integer > stream = Arrays . asList ( 1 , 2 , 3 , 4 , 5 ) . stream ( ) ;

객체 배열로부터 스트림 생성

Stream . of ( “a” , “b” , “c” ) ; Stream . of ( new String [ ] { “a” , “b” , “c” } ) ; Arrays . stream ( new String [ ] { “a” , “b” , “c” } ) ; Arrays . stream ( new String [ ] { “a” , “b” , “c” } , startIndex , EndIndex + 1 ) ;

람다식 iterate(), generate()

Stream . iterate ( 0 , n -> n + 2 ) ; Stream . generate ( Math : : random ) ; Stream . generate ( ( ) -> 1 ) ;

📌 2. 스트림의 중간 연산

. distinct ( ) . filter ( Predicate < T > predicate ) . limit ( long maxSize ) . skip ( long n ) . sorted ( ) . sorted ( Comparator < T > comparator ) . map ( Function < T > mapper ) . peek ( Consumer < T > action ) . flatMap ( )

📌 3. 스트림의 최종 연산

void forEach ( Consumer < ? super T > action ) void forEachOrdered ( Consumer < ? super T > action ) long count ( ) Optional < T > max ( Comparator < ? super T > comparator ) Optional < T > min ( Comparator < ? super T > comparator ) Optional < T > findAny ( ) Optional < T > findFirst ( ) boolean allMatch ( Predicate < T > p ) boolean anyMatch ( Predicate < T > p ) boolean noneMatch ( Predicate < T > p ) Object [ ] toArray ( ) A [ ] toArray ( IntFunction < A [ ] > generator ) Optional < T > reduce ( BinaryOperator < T > accumulator ) collect ( ~ )

📒 Stream API 사용 예시

🎈 Filter(Predicate)

if문이라고 생각하면 될듯.

람다식의 리턴값은 boolean. true면 다음 단계 진행, false면 버려짐

classes . stream ( ) . filter ( c -> c . getTitle ( ) . startWith ( “spring” ) ) . forEach ( c -> System . out . println ( oc . getId ) ) ; classes . stream ( ) . filter ( Predicate . not ( OnlineClass : : isClosed ) ) . forEach ( c -> System . out . println ( oc . getId ) ) ;

예) 이름이 3글자 이상인 데이터만 새로운 스트림으로 변경하기

🎈 Map(Function) 또는 FlatMap(Function)

stream을 우리가 원하는 모양의 새로운 스트림으로 변환 예) 각각의 File에서 String name만 새로운 스트림으로 예) string 타입 요소를 짤라서 새로운 스트림으로

map ( File : : getName ) map ( s -> s . subString ( 3 ) )

🎈 limit(long) 또는 skip(long)

예) 최대 5개의 요소가 담긴 스트림을 리턴한다.

예) 앞에서 3개를 뺀 나머지 스트림을 리턴한다.

Stream . iterate ( 10 , i -> i + 1 ) . skip ( 10 ) . limit ( 10 ) . forEach ( System . out : : println )

🎈 anyMatch(), allMatch(), nonMatch()

예) k를 포함한 문자열이 있는지 확인한다. (true 또는 false를 리턴한다.)

boolean test = javaClasses . stream ( ) . anyMatch ( oc -> oc . getTitle ( ) . contains ( “k” ) ) ;

예) 스트림에 있는 모든 값이 10보다 작은지 확인한다.

🎈 findFirst() VS findAny()

스트림을 직렬로 처리할 때는 차이가 없다.

하지만 병렬로 처리할 경우에 차이가 생기는데, findFirst() 는 stream의 순서를 고려해, 가장 앞쪽에 있는 요소를 반환 findAny() 는 멀티 쓰레드에서 가장 먼저 찾은 요소를 반환. stream의 뒤쪽에 있는 요소가 반환될 수도 있다.

🎈 reduce 자세히 보기

스트림의 요소를 하나씩 줄여가며 누적연산 수행

reduce(초기값, (누적 변수, 요소)-> 수행문)

Integer sum = Stream . of ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) . reduce ( ( total , y ) -> total + y ) ; System . out . println ( “sum: ” + s ) ; Integer sum = Stream . of ( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 , 10 ) . reduce ( 10 , ( total , n ) -> total + n ) ; System . out . println ( “sum: ” + sum ) ;

🎈 collect 자세히 보기

Stream의 요소들을 우리가 원하는 자료형으로 변환할 수 있다.

stream . collect ( Collectors . toSet ( ) ) ; stream . collect ( Collectors . toList ( ) ) ; stream . collect ( Collectors . joining ( ) ) ; stream . collect ( Collectors . joining ( “, ” ) ) ;

Reference

자바의 정석

인프런 더 자바(백기선)

자바 스트림(Stream) 예제부터 사용법까지 정리

반응형

자바에서 스트림이라고 하면

대부분 엄청나게 어려운 기술인 줄 알고

시작도 전에 포기하는 경우가 많은데

스트림은 엄청 간단하고 유용한 기술이다

다만 설명하는 사람들이 쉬운 걸 너무 어렵게 설명하기 때문에

아 들어도 모르겠네.. 그냥 어렵네.. 난 못하겠다..

이러고 넘어가는 경우가 대부분인데

스트림은 간단한 개념이지만 정말 유용하기 때문에

반드시 알아두는 것이 좋다

먼저 스트림을 왜 쓰냐면

스트림은 배열이나 컬렉션(List, Set, Map)으로 원하는 값을 얻을 때

for문 도배를 방지하기 위해 나온 개념인데

가령 int 형태의 배열을 가지고

중복을 제거하고 내림차순으로 정렬한 뒤

List 형태로 반환한다고 생각을 해 보자

일단 배열 내용을 가지고 for를 돌리면서

set에 값을 밀어넣은 후

set의 내용을 Iterator에 담아 다시 for를 돌리면서

Iterator의 값을 리스트에 값을 넣어준 후

List를 역정렬 후 반환하면 되는데

이것만 보면 손이 많이 가나?

이런 생각이 들겠지만

Stream을 사용하면

Arrays.stream(배열명).boxed()로 스트림을 생성한 뒤

.distinct()로 중복 다 떨어내고

.sorted(Comparator.reverseOrder())로 역정렬한 뒤

.collect(Collectors.toList())로 List 형태로 반환하면 끝이다

다시 위로 올라가 슥 보면

10줄짜리 코드를 Stream을 사용하면

한 줄로 줄여버릴 수 있다

여기에 set은 중복을 허용하지 않는다거나

iterator 사용방법을 모르는 등

개념이 부족해서 중간에 막혀버리면

결국 for문 돌려가면서

무한한 시간을 허비하게 되는데

Stream은 그냥 메소드만 연달아 쓰면

모든 값이 정리가 되어 나온다

근데 .distinct 이런것도 뭐 알아야 쓸것 아닌가

초보자도 바로 활용할 수 있게

처음부터 시작을 해 보자면

스트림은 선언, 가공, 반환 세 부분으로 이뤄지는데

방법은 차례대로 알아보자

선언

– 배열, 컬렉션(list, set, map) 등을 스트림 형태로 만들기

Stream<데이터타입> stream명 = Arrays.stream(배열명);

Stream<데이터타입> stream명 = 리스트명.stream();

Stream<데이터타입> stream명 = Stream.of(‘값’, ‘값’….);

각각 배열과 컬렉션을 사용하는 경우

아니면 직접 값을 넣어 사용하는 경우인데

꼭 stream을 선언한 후 값을 넣고 사용하는 것이 아니라

Arrays.stream(배열명).가공메소드…

리스트명.stream.가공메소드…

이런 식으로 바로 사용해도 된다

가공

– 스트림을 필요한 형태로 가공하기

여기는 분량이 꽤 긴데

예제 코드와 사용방법은 글 맨 아래에 정리해 놨고

다 외우는게 초점이 아니라

어떻게 쓰는지 한번 보고 필요한 내용이 있을 때

참고해서 쭉쭉 붙여서 만들어주면 된다

.boxed()

Int, Long, Double 배열 로 Stream을 만들었을 경우

각종 메소드를 사용하기 위해 사용

처음에는 붙이지 않고 쓰다가

특정 메소드가 안된다 싶으면 붙이는 식으로 사용하면 된다

– 컬렉션(List, Set, Map) 스트림 에서는 해당 메소드를 사용하지 않는다!!!

.count()

배열, 컬렉션 크기 확인

.sorted()

정렬

.sorted(Comparator.reverseOrder())

역정렬

.findFirst()

스트림의 처음 값 가져오기

.skip(배열크기 – 1).findFirst()

스트림의 마지막 값 가져오기

.skip(값)

값의 인덱스까지 생략하고 나머지를 가져옴

.limit(값)

값의 인덱스까지 가져옴

.distinct()

중복 생략

.max(데이터타입::compare)

최대값

.min(데이터타입::compare)

최소값

.average()

평균

– 배열일 경우에는 바로 사용 가능하지만

list, set, map의 경우에는 mapToDouble()을 이용해

한번 바꿔준 후 사용해야 함 하단 예제 코드 참고

.sum()

합계

– average와 동일하므로

list, set, map으로 합계를 구할때는 하단의 예제 참고

람다(Lambda)를 활용한 Stream 메소드

Stream에서 람다가 필수라고 하는데

그 사람들은 스트림을 제대로 써보지 않은 사람들이고

람다를 이용한 메소드도 있긴 한데

람다 하면 어려워서 스크롤을 내리게 되지만

람다는 (파라미터) -> {코드}의 매우 간단한 구조이기 때문에

뭔 개념인지 모른다면 링크를 한번 보고 온 뒤에

따라해주면 된다

.map((파라미터) -> 코드)

각 인덱스의 값을 파라미터로 넘기고 코드를 수행한다

주로 값을 바꿔주거나 더해주거나 할때 사용하게 된다

map은 코드 부분에서 메소드 사용이 불가능한데

이건 아래의 forEach를 사용해주면 된다

전체 예제는 아래에 코드를 넣어놨으니

그걸 봐주면 된다

.forEach((파라미터) -> {코드})

각 인덱스의 값을 파라미터로 넘기고 코드를 수행

(값마다 다른 메소드를 수행한다거나 할때 사용)

map과 forEach는 흡사하지만

map은 값만 바꿔주는 정도고

forEach는 if else나 메소드 등을 사용한느 것이 중점이 되겠다

.anyMatch((파라미터) -> {코드})

.noneMatch((파라미터) -> {코드})

.allMatch((파라미터) -> {코드})

anyMatch는 스트림 중 하나의 값이라도 조건에 맞으면 true

noneMatch는 스트림 중 하나의 값도 조건에 맞지 않으면 true

allMatch는 스트림의 값이 모두 조건에 맞아야 true

.filter(파라미터) -> {코드})

코드에 맞는 값만 가져온다

.reduce(값, 데이터타입::sum)

스트림의 값을 모두 하나로 합칠때 사용하는데

데이터타입과 sum으로 하나로 합친 뒤

마지막에 값을 더해서 가져오게 된다

(String의 경우에는 값, String::concat을 사용)

가공의 경우에는 내용이 많기 때문에

항목마다 모두 예제를 만들어놓지 않았는데

위에도 써놨다시피

이것만 보고 어떻게 만들어가 아니라

맨 아래에 예제 코드가 있으므로

하다가 막히면 그걸 봐주면 된다

반환

– 가공한 값을 원하는 형태로 가져오기

이제 가공까지 했으면 실제 사용할 수 있도록

값을 가져와야 하는데

System.out.println 으로 값을 찍어봤을 때

위와 같이 나온다면 정상적인 값이 나올 수 있도록

반환 작업을 해줘야 하는데

값이 하나만 있는 경우라면

.g를 입력한 후 ctrl + space를 하면 나오는

위와 같이 get(), getAsInt() 등으로 가져올 수 있고

배열, 컬렉션(List, Set, Map) 형태로 가져오는 경우라면

배열의 경우에는 끝에

.toArray();

나머지는

.collect(Collectors.toList());

에서 List만 Set, Map으로 바꿔주면 된다

다른 반환 방법으로는 다음과 같은데

반환방법도 글 맨 아래에 사용방법 및 예제 코드를

써 놨으니까 보다가 막히면 그걸 봐주면 된다

.collect(Collectors.counting());

해당하는 갯수 반환

.collect(Collectors.joining(“|”));

모든 값을 합치면서 |를 붙여줌

“” 붙이면 그냥 값만 다 붙이게 됨

.collect(Collectors.averagingInt(val -> Integer.parseInt(val))

.collect(Collectors.averagingDouble(val -> Double.parseDouble(val))

.collect(Collectors.averagingLong(val -> Long.parseLong(val))

값을 int, double, long 형태로 변환한 뒤

double 형태의 평균을 구해 반환

.collect(Collectors.groupingBy(Function.identity(), Collectors.counting())

이름, 갯수의 형태로 Map로 반환을 해 준다

1,1,1,2,2 이렇게 들었으면

“1” , 3 이런식으로 들어간다는 얘기

오라클에서 그룹핑 해서 가져오는 것과 동일한 개념이라고 보면 된다

.collect(Collectors.partitioningBy((파라미터) -> {코드})

조건에 맞으면 true, 아니면 false로 list를 만들고

Map> 형태로 반환한다

이제 선언, 가공, 반환방법을 알아봤으니

실제 예를 몇개 들어보면

Arrays.stream(arr).boxed().sorted().collect(Collectors.toList())

Arrays.stream(arr) – 선언

.boxed().sorted() – 가공

.collect(Collectors.toList()) – 반환

arr 배열을 정렬 후 List 타입으로 반환

Arrays.stream(arr).findFirst().getAsInt()

Arrays.stream(arr) – 선언

.findFirst() – 가공

.getAsInt() – 반환

arr 배열의 첫번째 값을 int 타입으로 반환

list.stream().skip(1).collect(Collectors.toList())

list.stream() – 선언

.skip(1) – 가공

.collect(Collectors.toList()) – 반환

list명의 첫번째 값은 생략한 뒤 list 타입으로 반환

처음에 딱 보면 뭔가 어려워 보이지만

결국은 동일한 이름의 메소드를

어떻게 붙였다 떼냐 하는 것이기 때문에

개념만 이해한다면 활용하기는 어렵지 않다

이러한 Stream을 사용하면 장점이

1. 사용하기 편하다

스트림을 사용할 줄 알면

머리 굴려가며 이걸 어떻게 해야 원하는 값이 나올까

이런 고민을 덜 해도 되는데

웬만한걸 다 메소드로 구현해 놨기 때문에

메소드만 순서에 맞게 꽂아주면

원하는 값을 쉽게 얻을 수 있다

2. 코드가 짧아진다

for, if else가 내용에 따라

무한하게 늘어나는 것을 생각해보면

Stream은 대부분 다 한줄이고

줄 수가 바뀐다고 한들 가독성을 높이기 위해

. 기준으로 엔터를 치는 정도다

3. 가독성이 높아진다

스트림이 가독성이 뭐가 높은지

이해가 안간다는 사람들이 많은데

기본적으로는 Stream 메소드를 봐도 모르기 때문에

가독성이 뭐가 높은지를 모르는 것이다

그러나 for 돌려가며 중복 값 지우는 것과

distinct() 하나로 지우는 것의 차이가 엄청난 것 처럼

메소드를 읽을 수 있게 되면

s

h

o

w

와 show 정도로

가독성이 차이난다는 것을 알게 된다

단점은

1. 디버그가 힘들다

뭘 잘못 짰거나 에러가 발생한다면

일반 코드의 경우에는 값이 틀어지기 직전,

에러가 나기 직전에 디버그를 걸어놓으면

손쉽게 디버깅이 가능한데

스트림은 한번에 모든 것이 수행되기 떄문에

에러라도 난다고 치면

스트림을 다 뜯어놓고 다시 조립을 해야 한다

가장 문제는 만든 사람이야 그나마 이해가 빠르지만

아예 모르는 사람이 스트림 코드를 디버깅할라면

굉장히 힘들다는 점이다

2. 재활용 불가능

스트림은 한번 쓰면 close 되기 때문에

Stream<> 스트림명 = 값;

이런 식으로 한번 정의해놓고 계속 사용이 불가능하다

Array.stream().~

list.stream.~

이런 식으로 바로 만들어서 사용하게 된다

3. 속도가 느리다

이건 단점이 약간 애매한데

예전에야 Stream 코드가 비효율적이기 때문에

for 도배에 비해 속도가 느렸다지만

요새는 개선이 많이 되어 상당히 따라잡았고

결국 퍼포먼스 비교는

코드를 최적으로 짰을 경우를 두고 비교하는 것인데

일반적으로는 코드를 최적으로 짜는 경우보다는

빙글빙글 돌아가며 짜는 경우도 상당히 많기 때문에

아무리 기본 속도가 빠르건 간에

만드는 사람이 돌아가며 짜기 시작하면

모든 방법이 동일한 스트림보다 느려질 수 있다는 거다

마지막으로 정리해보자면

스트림을 사용하면 배열이나 컬렉션(List, Set, Map) 등을

사용해 원하는 값을 얻으려는 경우

기존의 for 도배를 줄일 수 있고

원하는 값을 메소드만 사용해서 쉽게 가져올 수 있다

다만 디버깅이 힘들다는 것이 흠이 되겠다

마지막으로 사용한 예제 코드들은 아래와 같다

// 일반 방법과 스트림을 이용한 방법 public static void main(String[] args) { int[] arr = {1,1,10,30,2}; List list = new ArrayList<>(); Set set = new HashSet<>(); // Stream을 쓰지 않았을 경우 for(int i = 0; i iter = set.iterator(); // set을 iterator 안에 담기 for(int i = 0; iter.hasNext(); i++) { // iterator를 list 안에 list.add(iter.next()); } list.sort(Comparator.reverseOrder()); // 역정렬 System.out.println(“일반 방법을 이용한 출력 : ” + list.toString()); // Stream을 사용하는 경우 System.out.println(“Stream을 이용한 출력 : ” + Arrays.stream(arr).boxed() // Stream 생성 .distinct() // 중복 제거 .sorted(Comparator.reverseOrder()) // 역정렬 .collect(Collectors.toList()) // List로 반환 ); }

// 스트림 가공방법 예제 public static void main(String[] args) { int[] arr = {1,1,10,30,2}; List list = new ArrayList<>(); list.add(1); list.add(1); list.add(10); list.add(30); list.add(2); // 숫자 배열 Stream 사용 시 여러 메소드를 쓰기 위한 boxed 처리 System.out.println(Arrays.stream(arr).boxed()); // count() 배열, 컬렉션 크기 확인 System.out.println(“count() 배열, 컬렉션 크기 확인”); System.out.println(Arrays.stream(arr).count()); System.out.println(list.stream().count()); // sorted() 정렬 System.out.println(“sorted() 정렬”); System.out.println(Arrays.stream(arr).boxed().sorted().collect(Collectors.toList())); System.out.println(list.stream().sorted().collect(Collectors.toList())); // sorted(Comparator.reverseOrder()) 역정렬 System.out.println(“sorted(Comparator.reverseOrder()) 역정렬”); System.out.println(Arrays.stream(arr).boxed().sorted(Comparator.reverseOrder()).collect(Collectors.toList())); System.out.println(list.stream().sorted(Comparator.reverseOrder()).collect(Collectors.toList())); // findFirst() 처음 값 System.out.println(“findFirst() 처음 값”); System.out.println(Arrays.stream(arr).findFirst().getAsInt()); System.out.println(list.stream().findFirst().get()); // skip(배열크기 – 1).findFirst() 마지막 값 System.out.println(“skip(배열크기 – 1).findFirst()”); System.out.println(Arrays.stream(arr).skip(arr.length – 1).findFirst().getAsInt()); System.out.println(list.stream().skip(list.size() – 1).findFirst().get()); // skip(값) N개 생략하고 System.out.println(“skip(값) N개 생략하고”); System.out.println(Arrays.stream(arr).boxed().skip(1).collect(Collectors.toList())); System.out.println(list.stream().skip(1).collect(Collectors.toList())); // limit(값) N개 까지 System.out.println(“limit(값) N개 까지”); System.out.println(Arrays.stream(arr).boxed().limit(2).collect(Collectors.toList())); System.out.println(list.stream().limit(2).collect(Collectors.toList())); // distinct() 중복 생략 System.out.println(“distinct() 중복 생략”); System.out.println(Arrays.stream(arr).boxed().distinct().collect(Collectors.toList())); System.out.println(list.stream().distinct().collect(Collectors.toList())); // max(데이터타입::compare) 최대값 System.out.println(“max(데이터타입::compare) 최대값”); System.out.println(Arrays.stream(arr).boxed().max(Integer::compare).get()); System.out.println(list.stream().max(Integer::compare).get()); // min(데이터타입::compare) 최소값 System.out.println(“min(데이터타입::compare) 최소값”); System.out.println(Arrays.stream(arr).boxed().min(Integer::compare).get()); System.out.println(list.stream().min(Integer::compare).get()); // average() 평균 System.out.println(“average() 평균”); System.out.println(Arrays.stream(arr).average().getAsDouble()); System.out.println(list.stream().mapToDouble(Integer::doubleValue).average().getAsDouble()); // sum() 합계 System.out.println(“sum() 합계”); System.out.println(Arrays.stream(arr).sum()); System.out.println(list.stream().mapToInt(Integer::intValue).sum()); // 람다(Lambda)를 이용한 가공 // map(값을 원하는대로 가공) System.out.println(“map 1이면 true 아니면 false 예제”); System.out.println(Arrays.stream(arr).boxed().map(val -> val == 1).collect(Collectors.toList())); System.out.println(list.stream().map(val -> val == 1).collect(Collectors.toList())); // map 값마다 10 더하기 예제 System.out.println(“map 값마다 10 더하기 예제”); System.out.println(Arrays.stream(arr).boxed().map(val -> val = val + 10).collect(Collectors.toList())); System.out.println(list.stream().map(val -> val = val + 10).collect(Collectors.toList())); // map 값 반올림 예제 System.out.println(“map 값 반올림 예제”); System.out.println(Arrays.stream(arr).boxed().map(val -> Math.round(val*10)/10.0).collect(Collectors.toList())); System.out.println(list.stream().map(val -> Math.round(val*10)/10.0).collect(Collectors.toList())); // forEach(모든 값마다 입력한 내용 수행) System.out.println(“forEach(모든 값마다 입력한 내용 수행)”); Arrays.stream(arr).boxed().forEach(val -> System.out.println(“ForEach 출력! : ” + val)); list.stream().forEach(val -> System.out.println(“ForEach 출력! : ” + val)); // anyMatch(스트림에서 조건이 하나라도 맞으면) System.out.println(“anyMatch(스트림에서 조건이 하나라도 맞으면) TRUE”); System.out.println(Arrays.stream(arr).anyMatch(val -> val == 1)); System.out.println(list.stream().anyMatch(val -> val == 1)); // noneMatch(스트림에서 조건이 하나도 안맞으면) System.out.println(“noneMatch(스트림에서 조건이 하나도 안맞으면) TRUE”); System.out.println(Arrays.stream(arr).noneMatch(val -> val == 99)); System.out.println(list.stream().noneMatch(val -> val == 99)); // allMatch(스트림의 값이 모두 조건과 맞아야) System.out.println(“allMatch(스트림의 값이 모두 조건과 맞아야) TRUE”); System.out.println(Arrays.stream(arr).allMatch(val -> val == 1)); System.out.println(list.stream().allMatch(val -> val == 1)); // filter (특정 값만 허용) System.out.println(“filter (특정 값만 허용)”); System.out.println(Arrays.stream(arr).boxed().filter(val -> val == 10).collect(Collectors.toList())); System.out.println(list.stream().filter(val -> val == 10).collect(Collectors.toList())); // reduce (스트림 값을 모두 하나로 합치기) System.out.println(“reduce (스트림 값을 모두 하나로 합치기) 다 합치고 5 더하기 예제”); System.out.println(Arrays.stream(arr).reduce(5, Integer::sum)); System.out.println(list.stream().reduce(5, Integer::sum)); }

// 스트림 반환방법 예제 public static void main(String[] args) { int[] arr = {1,1,10,30,2}; List list = new ArrayList<>(); list.add(1); list.add(1); list.add(10); list.add(30); list.add(2); System.out.println(Arrays.stream(arr).boxed().distinct()); // 반환하기 전 System.out.println(list.stream().max(Integer::compare)); // 반환하기 전 int[] arr2 = Arrays.stream(arr).distinct().toArray(); // 배열로 반환 List list2 = Arrays.stream(arr).boxed().distinct().collect(Collectors.toList()); // List로 반환 int val2 = list.stream().max(Integer::compare).get(); // 값 하나 반환 long val3 = list.stream().collect(Collectors.counting()); // 해당하는 갯수 반환 String[] strArr = {“10”, “20”, “30”}; // 컬렉션 내 모든 값을 |를 붙여서 반환 // | 없이 붙여줄려면 “”로 변경 System.out.println(Arrays.stream(strArr) .collect(Collectors.joining(“|”))); Double val4 = Arrays.stream(strArr) // Int 형태로 평균값 반환 (배열이 String일 경우) .collect(Collectors.averagingInt(val -> Integer.parseInt(val))); Double val5 = Arrays.stream(strArr) // Long 형태로 평균값 반환(배열이 String일 경우) .collect(Collectors.averagingDouble(val -> Double.parseDouble(val))); Double val6 = Arrays.stream(strArr) // Long 형태로 평균값 반환(배열이 String일 경우) .collect(Collectors.averagingLong(val -> Long.parseLong(val))); System.out.println(“val4 : ” + val4); System.out.println(“val4 : ” + val5); System.out.println(“val4 : ” + val6); // 값 확인 String[] getGroupParti = {“zeebra”, “cobra”, “cobra”, “dog”}; // 이름, 갯수로 Group으로 묶어 담아줌 Map map = Arrays.stream(getGroupParti) .collect(Collectors.groupingBy(Function.identity(), Collectors.counting())); System.out.println(“cobra : ” + map.get(“cobra”)); // 조건에 맞으면 true, 아니면 false의 list 형태로 담아줌 Map> map2 = Arrays.stream(getGroupParti) .collect(Collectors.partitioningBy(val -> val == “cobra”)); System.out.println(map2.get(true)); }

반응형

Stream (Java Platform SE 8 )

int sum = widgets.stream() .filter(w -> w.getColor() == RED) .mapToInt(w -> w.getWeight()) .sum();

widgets

Collection

Widget

int

A sequence of elements supporting sequential and parallel aggregate operations. The following example illustrates an aggregate operation using Stream and IntStream In this example,is a. We create a stream ofobjects via Collection.stream() , filter it to produce a stream containing only the red widgets, and then transform it into a stream ofvalues representing the weight of each red widget. Then this stream is summed to produce a total weight.

In addition to Stream , which is a stream of object references, there are primitive specializations for IntStream , LongStream , and DoubleStream , all of which are referred to as “streams” and conform to the characteristics and restrictions described here.

To perform a computation, stream operations are composed into a stream pipeline. A stream pipeline consists of a source (which might be an array, a collection, a generator function, an I/O channel, etc), zero or more intermediate operations (which transform a stream into another stream, such as filter(Predicate) ), and a terminal operation (which produces a result or side-effect, such as count() or forEach(Consumer) ). Streams are lazy; computation on the source data is only performed when the terminal operation is initiated, and source elements are consumed only as needed.

Collections and streams, while bearing some superficial similarities, have different goals. Collections are primarily concerned with the efficient management of, and access to, their elements. By contrast, streams do not provide a means to directly access or manipulate their elements, and are instead concerned with declaratively describing their source and the computational operations which will be performed in aggregate on that source. However, if the provided stream operations do not offer the desired functionality, the BaseStream.iterator() and BaseStream.spliterator() operations can be used to perform a controlled traversal.

A stream pipeline, like the “widgets” example above, can be viewed as a query on the stream source. Unless the source was explicitly designed for concurrent modification (such as a ConcurrentHashMap ), unpredictable or erroneous behavior may result from modifying the stream source while it is being queried.

Most stream operations accept parameters that describe user-specified behavior, such as the lambda expression w -> w.getWeight() passed to mapToInt in the example above. To preserve correct behavior, these behavioral parameters:

must be non-interfering (they do not modify the stream source); and

in most cases must be stateless (their result should not depend on any state that might change during execution of the stream pipeline).

Such parameters are always instances of a functional interface such as Function , and are often lambda expressions or method references. Unless otherwise specified these parameters must be non-null.

A stream should be operated on (invoking an intermediate or terminal stream operation) only once. This rules out, for example, “forked” streams, where the same source feeds two or more pipelines, or multiple traversals of the same stream. A stream implementation may throw IllegalStateException if it detects that the stream is being reused. However, since some stream operations may return their receiver rather than a new stream object, it may not be possible to detect reuse in all cases.

Streams have a BaseStream.close() method and implement AutoCloseable , but nearly all stream instances do not actually need to be closed after use. Generally, only streams whose source is an IO channel (such as those returned by Files.lines(Path, Charset) ) will require closing. Most streams are backed by collections, arrays, or generating functions, which require no special resource management. (If a stream does require closing, it can be declared as a resource in a try -with-resources statement.)

[Java] Stream API의 활용 및 사용법 – 기초 (3/5)

반응형

1. Stream 생성하기

앞서 설명한대로 Stream API를 사용하기 위해서는 먼저 Stream을 생성해주어야 한다. 타입에 따라 Stream을 생성하는 방법이 다른데, 여기서는 Collection과 Array에 대해서 Stream을 생성하는 방법에 대해 알아보도록 하자.

[ Collection의 Stream 생성 ]

Collection 인터페이스에는 stream()이 정의되어 있기 때문에, Collection 인터페이스를 구현한 객체들(List, Set 등)은 모두 이 메소드를 이용해 Stream을 생성할 수 있다. stream()을 사용하면 해당 Collection의 객체를 소스로 하는 Stream을 반환한다.

// List로부터 스트림을 생성 List list = Arrays.asList(“a”, “b”, “c”); Stream listStream = list.stream();

[ 배열의 Stream 생성 ]

배열의 원소들을 소스로하는 Stream을 생성하기 위해서는 Stream의 of 메소드 또는 Arrays의 stream 메소드를 사용하면 된다.

// 배열로부터 스트림을 생성 Stream stream = Stream.of(“a”, “b”, “c”); //가변인자 Stream stream = Stream.of(new String[] {“a”, “b”, “c”}); Stream stream = Arrays.stream(new String[] {“a”, “b”, “c”}); Stream stream = Arrays.stream(new String[] {“a”, “b”, “c”}, 0, 3); //end범위 포함 x

[ 원시 Stream 생성 ]

위와 같이 객체를 위한 Stream 외에도 int와 long 그리고 double과 같은 원시 자료형들을 사용하기 위한 특수한 종류의 Stream(IntStream, LongStream, DoubleStream) 들도 사용할 수 있으며, Intstream같은 경우 range()함수를 사용하여 기존의 for문을 대체할 수 있다.

// 4이상 10 이하의 숫자를 갖는 IntStream IntStream stream = IntStream.range(4, 10);

2. Stream 가공하기(중간연산)

생성한 Stream 객체에서 요소들을 가공하기 위해서는 중간연산이 필요하다. 가공하기 단계의 파라미터로는 앞서 설명하였던 함수형 인터페이스들이 사용되며, 여러 개의 중간연산이 연결되도록 반환값으로 Stream을 반환한다.

[ 필터링 – Filter ]

Filter는 Stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어내는 연산이다. Java에서는 filter 함수의 인자로 함수형 인터페이스 Predicate를 받고 있기 때문에, boolean을 반환하는 람다식을 작성하여 filter 함수를 구현할 수 있다. 예를 들어 어떤 String의 stream에서 a가 들어간 문자열만을 포함하도록 필터링하는 예제는 다음과 같이 작성할 수 있다.

Stream stream = list.stream() .filter(name -> name.contains(“a”));

[ 데이터 변환 – Map ]

Map은 기존의 Stream 요소들을 변환하여 새로운 Stream을 형성하는 연산이다. 저장된 값을 특정한 형태로 변환하는데 주로 사용되며, Java에서는 map 함수의 인자로 함수형 인터페이스 function을 받고 있다. 예를 들어 String을 요소들로 갖는 Stream을 모두 대문자 String의 요소들로 변환하고자 할 때 map을 이용할 수 있다.

Stream stream = names.stream() .map(s -> s.toUpperCase());

위의 map 함수의 람다식은 메소드 참조를 이용해 변경이 가능하다. 이번에는 메소드 참조를 이용하여 파일의 Stream을 파일 이름의 Stream으로 변경해보자.

Stream fileStream = Stream.of(new File(“Test1.java”), new File(“Test2.java”), new File(“Test3.java”)); //Stream –> Stream 변환 Stream fileNameStream = fileStream.map(File::getName);

[ 정렬 – Sorted ]

Stream의 요소들을 정렬하기 위해서는 sorted를 사용해야 하며, 파라미터로 Comparator를 넘길 수도 있다. Comparator 인자 없이 호출할 경우에는 오름차순으로 정렬이 되며, 내림차순으로 정렬하기 위해서는 Comparator의 reverseOrder를 이용하면 된다. 예를 들어 어떤 Stream의 String 요소들을 정렬하기 위해서는 다음과 같이 sorted를 활용할 수 있다.

List list = Arrays.asList(“Java”, “Scala”, “Groovy”, “Python”, “Go”, “Swift”); Stream stream = list.stream() .sorted() // [Go, Groovy, Java, Python, Scala, Swift] Stream stream = list.stream() .sorted(Comparator.reverseOrder()) // [Swift, Scala, Python, Java, Groovy, Go] [ 중복 제거 – Distinct ]

Stream의 요소들에 중복된 데이터가 존재하는 경우, 중복을 제거하기 위해 distinct를 사용할 수 있다. distinct는 중복된 데이터를 검사하기 위해 Object의 equals() 메소드를 사용한다. 예를 들어 중복된 Stream의 요소들을 제거하기 위해서는 아래와 같이 distinct()를 사용할 수 있다.

List list = Arrays.asList(“Java”, “Scala”, “Groovy”, “Python”, “Go”, “Swift”, “Java”); Stream stream = list.stream() .distinct() // [Java, Scala, Groovy, Python, Go, Swift]

만약 우리가 생성한 클래스를 Stream으로 사용한다고 하면 equals와 hashCode를 오버라이드 해야만 distinct()를 제대로 적용할 수 있다. equals와 hashCode에 대해서는 여기에서 자세히 다루었다.

만약 다음과 같은 Employee 클래스가 있다고 하자.

public class Employee { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } }

위의 Employee 클래스는 equals와 hashCode를 오버라이드하지 않았기 때문에, 아래의 코드를 실행해도 중복된 데이터가 제거되지 않고, size 값으로 2를 출력하게 된다.

import java.util.*; public class Main { public static void main(String[] args) { Employee e1 = new Employee(“MangKyu”); Employee e2 = new Employee(“MangKyu”); List employees = new ArrayList<>(); employees.add(e1); employees.add(e2); int size = employees.stream().distinct().collect(Collectors.toList()).size(); System.out.println(size); } }

그렇기 때문에 우리는 아래와 같이 equals와 hashCode를 오버라이드하여 이러한 문제를 해결해야 한다.

import java.util.Objects; public class Employee { private String name; public Employee(String name) { this.name = name; } public String getName() { return name; } @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; Employee employee = (Employee) o; return Objects.equals(name, employee.name); } @Override public int hashCode() { return Objects.hash(name); } }

위와 같은 코드를 추가하고 main 함수를 다시 실행시키면 size는 1이 된다.

[ 특정 연산 수행 – Peek ]

Stream의 요소들을 대상으로 Stream에 영향을 주지 않고 특정 연산을 수행하기 위한 peek 함수가 존재한다. ‘확인해본다’라는 뜻을 지닌 peek 단어처럼, peek 함수는 Stream의 각각의 요소들에 대해 특정 작업을 수행할 뿐 결과에 영향을 주지 않는다. 또한 peek 함수는 파라미터로 함수형 인터페이스 Consumer를 인자로 받는다. 예를 들어 어떤 stream의 요소들을 중간에 출력하기를 원할 때 다음과 같이 활용할 수 있다.

int sum = IntStream.of(1, 3, 5, 7, 9) .peek(System.out::println) .sum();

[ 원시 Stream <-> Stream ]

작업을 하다 보면 일반적인 Stream 객체를 원시 Stream으로 바꾸거나 그 반대로 하는 작업이 필요한 경우가 있다. 이러한 경우를 위해서, 일반적인 Stream 객체는 mapToInt(), mapToLong(), mapToDouble()이라는 특수한 Mapping 연산을 지원하고 있으며, 그 반대로 원시객체는 mapToObject를 통해 일반적인 Stream 객체로 바꿀 수 있다.

// IntStream -> Stream IntStream.range(1, 4) .mapToObj(i -> “a” + i) // Stream -> IntStream -> Stream Stream.of(1.0, 2.0, 3.0) .mapToInt(Double::intValue) .mapToObj(i -> “a” + i)

3. Stream 결과 만들기(최종 연산)

중간 연산을 통해 생성된 Stream을 바탕으로 이제 결과를 만들 차례이다. 결과를 만들기 위한 최종 연산에는 다음과 같은 것들이 존재한다.

[ 최댓값/최솟값/총합/평균/갯수 – Max/Min/Sum/Average/Count ]

Stream의 요소들을 대상으로 최솟값이나 최댓값 또는 총합을 구하기 위한 최종 연산들이 존재한다. 최솟값이나 최댓값을 구하기 위해서는 max와 min을 이용해야 하며, 총합 또는 평균 또는 개수를 구하기 위해서는 sum과 average, count를 이용해야 한다. min이나 max 또는 average는 Stream이 비어있는 경우에 값을 특정할 수 없다. 그렇기 때문에 다음과 같이 Optional로 값이 반환된다.

OptionalInt min = IntStream.of(1, 3, 5, 7, 9).min(); int max = IntStream.of().max().orElse(0); IntStream.of(1, 3, 5, 7, 9).average().ifPresent(System.out::println);

반면에 총합이나 갯수의 경우에는 값이 비어있는 경우 0으로 값을 특정할 수 있다. 그렇기 때문에 Stream API는 sum 메소드와 count 메소드에대해 Optional이 아닌 원시 값을 반환하도록 구현해두었다. 당연히 Stream이 비어있을 경우에는 0을 반환하게 된다.

long count = IntStream.of(1, 3, 5, 7, 9).count(); long sum = LongStream.of(1, 3, 5, 7, 9).sum();

[ 데이터 수집 – collect ]

Stream의 요소들을 List나 Set, Map, 등 다른 종류의 결과로 수집하고 싶은 경우에는 collect 함수를 이용할 수 있다. collect 함수는 어떻게 Stream의 요소들을 수집할 것인가를 정의한 Collector 타입을 인자로 받아서 처리한다. 일반적으로 List로 Stream의 요소들을 수집하는 경우가 많은데, 이렇듯 자주 사용하는 작업은 Collectors 객체에서 static 메소드로 제공하고 있다. 원하는 것이 없는 경우에는 Collector 인터페이스를 직접 구현하여 사용할 수도 있다.

collect() : 스트림의 최종연산, 매개변수로 Collector를 필요로 한다. Collector : 인터페이스, collect의 파라미터는 이 인터페이스를 구현해야한다. Collectors : 클래스, static메소드로 미리 작성된 컬렉터를 제공한다. // collect의 파라미터로 Collector의 구현체가 와야 한다. Object collect(Collector collector)

collect()를 응용한 다양한 예제들을 살펴보기 위해 다음과 같은 데이터가 사전에 정의되어 있다고 하자. Product 객체는 수량(amount)와 이름(name)을 변수로 가지며, 주어진 데이터를 다양한 방식으로 수집해볼 것이다.

List productList = Arrays.asList( new Product(23, “potatoes”), new Product(14, “orange”), new Product(13, “lemon”), new Product(23, “bread”), new Product(13, “sugar”));

1. Collectors.toList()

Stream에서 작업한 결과를 List로 반환받을 수 있다. 아래의 예제에서는 Stream의 요소들을 Product의 이름으로 변환하여, 그 결과를 List로 반환받고 있다.

List nameList = productList.stream() .map(Product::getName) .collect(Collectors.toList());

만약 해당 결과를 set으로 반환받기를 원한다면 Collectors.toSet()을 사용하면 된다.

2. Collectors.joining()

Stream에서 작업한 결과를 1개의 String으로 이어붙이기를 원하는 경우에 Collectors.joining()을 이용할 수 있다. Collectors.joining()은 총 3개의 인자를 받을 수 있는데, 이를 이용하면 간단하게 String을 조합할 수 있다.

delimiter : 각 요소 중간에 들어가 요소를 구분시켜주는 구분자

prefix : 결과 맨 앞에 붙는 문자

suffix : 결과 맨 뒤에 붙는 문자

String listToString = productList.stream() .map(Product::getName) .collect(Collectors.joining()); // potatoesorangelemonbreadsugar String listToString = productList.stream() .map(Product::getName) .collect(Collectors.joining(” “)); // potatoes orange lemon bread sugar String listToString = productList.stream() .map(Product::getName) .collect(Collectors.joining(“, “, “<", ">“)); //

3. Collectors.averagingInt(), Collectors.summingInt(), Collectors.summarizingInt()

Stream에서 작업한 결과의 평균값이나 총합 등을 구하기 위해서는 Collectors.averagingInt()와 Collectors.summingInt()를 이용할 수 있다. 물론 총합의 경우 이를 구현할 수 있는 방법이 그 외에도 많이 있다.

Double averageAmount = productList.stream() .collect(Collectors.averagingInt(Product::getAmount)); // 86 Integer summingAmount = productList.stream() .collect(Collectors.summingInt(Product::getAmount)); // 86 Integer summingAmount = productList.stream() .mapToInt(Product::getAmount) .sum();

하지만 만약 1개의 Stream으로부터 갯수, 합계, 평균, 최댓값, 최솟값을 한번에 얻고 싶은 경우에는 어떻게 할 수 있을까? 동일한 Stream 작업을 여러 번 실행하는 것은 그렇게 좋지 못한 방법이기 때문에, 이러한 경우에는 Collectors.summarizingInt()를 이용하는 것이 좋다. 이를 이용하면 IntSummaryStatistics 객체가 반환되며, 필요한 값에 대해 get 메소드를 이용하여 원하는 값을 꺼내면 된다.

개수: getCount()

합계: getSum()

평균: getAverage()

최소: getMin()

최대: getMax()

IntSummaryStatistics statistics = productList.stream() .collect(Collectors.summarizingInt(Product::getAmount)); //IntSummaryStatistics {count=5, sum=86, min=13, average=17.200000, max=23}

4. Collectors.groupingBy()

Stream에서 작업한 결과를 특정 그룹으로 묶기를 원할 수 있다. 이러한 경우에는 Collectors.groupingBy()를 이용할 수 있으며, 결과는 Map으로 반환받게 된다. groupingBy는 매개변수로 함수형 인터페이스 Function을 필요로 한다.

예를 들어 수량을 기준으로 grouping을 원하는 경우에 다음과 같이 작성할 수 있으며, 같은 수량일 경우에는 List로 묶어서 값을 반환받게 된다.

Map> collectorMapOfLists = productList.stream() .collect(Collectors.groupingBy(Product::getAmount)); /* {23=[Product{amount=23, name=’potatoes’}, Product{amount=23, name=’bread’}], 13=[Product{amount=13, name=’lemon’}, Product{amount=13, name=’sugar’}], 14=[Product{amount=14, name=’orange’}]} */

5. Collectors.partitioningBy()

Collectors.groupingBy()가 함수형 인터페이스 Function을 사용해서 특정 값을 기준으로 Stream 내의 요소들을 그룹핑하였다면, Collectors.partitioningBy()는 함수형 인터페이스 Predicate를 받아 Boolean을 Key값으로 partitioning한다.

예를 들어 제품의 갯수가 15보드 큰 경우와 그렇지 않은 경우를 나누고자 한다면 다음과 같이 코드를 작성할 수 있다.

Map> mapPartitioned = productList.stream() .collect(Collectors.partitioningBy(p -> p.getAmount() > 15)); /* {false=[Product{amount=14, name=’orange’}, Product{amount=13, name=’lemon’}, Product{amount=13, name=’sugar’}], true=[Product{amount=23, name=’potatoes’}, Product{amount=23, name=’bread’}]} */

[ 조건 검사 – Match ]

Stream의 요소들이 특정한 조건을 충족하는지 검사하고 싶은 경우에는 match 함수를 이용할 수 있다. match 함수는 함수형 인터페이스 Predicate를 받아서 해당 조건을 만족하는지 검사를 하게 되고, 검사 결과를 boolean으로 반환한다. match 함수에는 크게 다음의 3가지가 있다.

anyMatch: 1개의 요소라도 해당 조건을 만족하는가

allMatch: 모든 요소가 해당 조건을 만족하는가

nonMatch: 모든 요소가 해당 조건을 만족하지 않는가

예를 들어 다음과 같은 예시 코드가 있다고 할 때, 아래의 경우 모두 true를 반환하게 된다.

List names = Arrays.asList(“Eric”, “Elena”, “Java”); boolean anyMatch = names.stream() .anyMatch(name -> name.contains(“a”)); boolean allMatch = names.stream() .allMatch(name -> name.length() > 3); boolean noneMatch = names.stream() .noneMatch(name -> name.endsWith(“s”));

[ 특정 연산 수행 – forEach ]

Stream의 요소들을 대상으로 어떤 특정한 연산을 수행하고 싶은 경우에는 forEach 함수를 이용할 수 있다. 앞에서 살펴본 비슷한 함수로 peek()가 있다. peek()는 중간 연산으로써 실제 요소들에 영향을 주지 않은 채로 작업을 진행하고, Stream을 반환하는 함수였다. 하지만 forEach()는 최종 연산으로써 실제 요소들에 영향을 줄 수 있으며, 반환값이 존재하지 않는다. 예를 들어 요소들을 출력하기를 원할 때 다음과 같이 forEach를 사용할 수 있다.

names.stream() .forEach(System.out::println);

관련 포스팅

참고 자료

반응형

코딩교육 티씨피스쿨

스트림

스트림(stream)

자바에서는 파일이나 콘솔의 입출력을 직접 다루지 않고, 스트림(stream)이라는 흐름을 통해 다룹니다.

스트림(stream)이란 실제의 입력이나 출력이 표현된 데이터의 이상화된 흐름을 의미합니다.

즉, 스트림은 운영체제에 의해 생성되는 가상의 연결 고리를 의미하며, 중간 매개자 역할을 합니다.

Java SE 8 버전부터 추가된 스트림 API는 앞서 설명한 스트림과는 전혀 다른 개념입니다.

스트림 API에 대한 더 자세한 사항은 자바 스트림 API 수업에서 확인할 수 있습니다.

자바 스트림 API 수업 확인 =>

입출력 스트림

스트림은 한 방향으로만 통신할 수 있으므로, 입력과 출력을 동시에 처리할 수는 없습니다.

따라서 스트림은 사용 목적에 따라 입력 스트림과 출력 스트림으로 구분됩니다.

자바에서는 java.io 패키지를 통해 InputStream과 OutputStream 클래스를 별도로 제공하고 있습니다.

즉, 자바에서의 스트림 생성이란 이러한 스트림 클래스 타입의 인스턴스를 생성한다는 의미입니다.

InputStream 클래스에는 read() 메소드가, OutputStream 클래스에는 write() 메소드가 각각 추상 메소드로 포함되어 있습니다.

사용자는 이 두 메소드를 상황에 맞게 적절히 구현해야만 입출력 스트림을 생성하여 사용할 수 있습니다.

클래스 메소드 설명 InputStream abstract int read() 해당 입력 스트림으로부터 다음 바이트를 읽어들임. int read(byte[] b) 해당 입력 스트림으로부터 특정 바이트를 읽어들인 후, 배열 b에 저장함. int read(byte[] b, int off, int len) 해당 입력 스트림으로부터 len 바이트를 읽어들인 후, 배열 b[off]부터 저장함. OutputStream abstract void write(int b) 해당 출력 스트림에 특정 바이트를 저장함. void write(byte[] b) 배열 b의 특정 바이트를 배열 b의 길이만큼 해당 출력 스트림에 저장함. void write(byte[] b, int off, int len) 배열 b[off]부터 len 바이트를 해당 출력 스트림에 저장함.

read() 메소드는 해당 입력 스트림에서 더 이상 읽어들일 바이트가 없으면, -1을 반환해야 합니다.

그런데 반환 타입을 byte 타입으로 하면, 0부터 255까지의 바이트 정보는 표현할 수 있지만 -1은 표현할 수 없게 됩니다.

따라서 InputStream의 read() 메소드는 반환 타입을 int형으로 선언하고 있습니다.

바이트 기반 스트림

자바에서 스트림은 기본적으로 바이트 단위로 데이터를 전송합니다.

자바에서는 다음과 같은 다양한 바이트 기반의 입출력 스트림을 제공하고 있습니다.

입력 스트림 출력 스트림 입출력 대상 FileInputStream FileOutputStream 파일 ByteArrayInputStream ByteArrayOutputStream 메모리 PipedInputStream PipedOutputStream 프로세스 AudioInputStream AudioOutputStream 오디오 장치

보조 스트림

자바에서 제공하는 보조 스트림은 실제로 데이터를 주고받을 수는 없지만, 다른 스트림의 기능을 향상시키거나 새로운 기능을 추가해 주는 스트림입니다.

자바에서는 다음과 같은 다양한 보조 스트림을 제공하고 있습니다.

입력 스트림 출력 스트림 설명 FilterInputStream FilterOutputStream 필터를 이용한 입출력 BufferedInputStream BufferedOutputStream 버퍼를 이용한 입출력 DataInputStream DataOutputStream 입출력 스트림으로부터 자바의 기본 타입으로 데이터를 읽어올 수 있게 함. ObjectInputStream ObjectOutputStream 데이터를 객체 단위로 읽거나, 읽어 들인 객체를 역직렬화시킴. SequenceInputStream X 두 개의 입력 스트림을 논리적으로 연결함. PushbackInputStream X 다른 입력 스트림에 버퍼를 이용하여 push back이나 unread와 같은 기능을 추가함. X PrintStream 다른 출력 스트림에 버퍼를 이용하여 다양한 데이터를 출력하기 위한 기능을 추가함.

문자 기반 스트림

자바에서 스트림은 기본적으로 바이트 단위로 데이터를 전송합니다.

하지만 자바에서 가장 작은 타입인 char 형이 2바이트이므로, 1바이트씩 전송되는 바이트 기반 스트림으로는 원활한 처리가 힘든 경우가 있습니다.

따라서 자바에서는 바이트 기반 스트림뿐만 아니라 문자 기반의 스트림도 별도로 제공합니다.

이러한 문자 기반 스트림은 기존의 바이트 기반 스트림에서 InputStream을 Reader로, OutputStream을 Writer로 변경하면 사용할 수 있습니다.

자바에서는 다음과 같은 다양한 문자 기반의 입출력 스트림을 제공하고 있습니다.

입력 스트림 출력 스트림 입출력 대상 FileReader FileWriter 파일 CharArrayReader CharArrayWriter 메모리 PipedReader PipedWriter 프로세스 StringReader StringWriter 문자열

지금까지 살펴본 바이트 기반의 스트림과 문자 기반의 스트림은 활용 방법이 거의 같습니다.

따라서 문자 기반의 보조 스트림도 다음과 같이 제공됩니다.

입력 스트림 출력 스트림 설명 FilterReader FilterWriter 필터를 이용한 입출력 BufferedReader BufferedWriter 버퍼를 이용한 입출력 PushbackReader X 다른 입력 스트림에 버퍼를 이용하여 push back이나 unread와 같은 기능을 추가함. X PrintWriter 다른 출력 스트림에 버퍼를 이용하여 다양한 데이터를 출력하기 위한 기능을 추가함.

연습문제

[Java] 스트림(Stream) 익히기

스트림이란

스트림은 컬렉션에 저장된 요소를 하나식 꺼내서 람다식으로 처리하는 반복자이다. 스트림을 사용하기 위해서는 람다식 에 대한 지식이 필요하며 컬렉션, 스레드 에 대한 이해도 필요하다. java 에서 반복자 대표주자는 Iterator (java.util) 이다. 스트림은 java.util.stream 패키지에 속한 인터페이스이다.

스트림과 컬렉션은 집합에 대한 클래스이다. 컬렉션이 요소를 할당하고 관리하는데 목적이 있다면, 스트림은 요소를 검색하거나 값을 처리하는데 목적이 있다.

스트림의 특징

람다식으로 처리 -> 코드가 간결해짐

내부 반복자 사용 -> 병렬처리가 쉽다

중간처리 및 최종처리(집계) 결과 관리가 쉽다

참고1) 외부반복자 vs 내부반복자

구분 내용 외부반복자(external iterator) -컬렉션의 요소를 하나씩 가져오는 코드를 직접 작성하기

-외부반복자 예시 : for문, iterator를 사용하는 while문

-코딩 : 컬렉션 요소 꺼내는 코드 및 요소를 처리하는 코드 작성 내부반복자(internal iterator) -컬렉션 내부에서 코드를 하나씩 꺼내줌

-코딩 : 컬렉션 요소를 처리하는 코드만 작성

참고2) 병렬처리란

병렬처리란 한 가지 작업을 여러 스레드가 처리할 수 있도록 여러 작업단위를 나누고 병렬로 처리하는 것을 말한다. 각 스레드가 처리한 결과는 자동으로 결합해 최종수행결과물이 나온다. 작업단위로 나눈 부분의 결과가 중간처리결과, 중간처리결과를 합하면 최종처리결과가 나온다.

참고3) 중간처리 & 최종처리란

-중간처리 : 매핑, 필터링, 정렬 등 작업

-최종처리 : 요소반복, 개수세기(count), 평균(average), 합계(sum) 등 집계

1. 스트림 얻기

컬렉션에서 Iterator 반복자를 얻어내듯이 배열, 컬렉션 등 집합에서 Stream을 얻어낼 수 있다.

java.util.Arrays – stream() 메서드

java.util.Collection – stream() 메서드

1-1) 배열에서 스트림 얻기

public class Test { public static void main(String[] args) { String[] strArr = {“백지영”, “박효신”, “양파”, “버즈”}; //Arrays 클래스 – stream()메서드로 스트림 얻기 Stream strStream = Arrays.stream(strArr); //forEach는 반복하면서 모든 개별요소를 가져올 수 있다 strStream.forEach( a -> { System.out.print(a+ ” “); }); System.out.println(); int[] intArr = {12, 64, 92}; //요소가 int 인 경우 IntStream으로 얻는다 IntStream intStream = Arrays.stream(intArr); IntConsumer intConsumer = i -> System.out.print(i+ ” “); intStream.forEach(intConsumer); System.out.println(); double[] doubleArr = {145.46, 2.85}; //요소가 double 인 경우 DoubleStream으로 얻는다 DoubleStream doubleStream = Arrays.stream(doubleArr); doubleStream.forEach( a -> System.out.print(a+ ” “) ); } }

실행 결과

백지영 박효신 양파 버즈

12 64 92

145.46 2.85

1-2) 컬렉션에서 스트림 얻기

내부클래스 Student 를 요소로 하는 List, Set에서 스트림 얻기

public class Test { class Student{ private int stuNum; private String stuName; private boolean isCome; Student(int stuNum, String stuName, boolean isCome){ this.stuNum = stuNum; this.stuName = stuName; this.isCome = isCome; } @Override public String toString() { return “Student{” + “stuNum=” + stuNum + “, stuName='” + stuName + ‘\” + “, isCome=” + isCome + ‘}’; } } public static void main(String[] args) { Test t = new Test(); Student stu1 = t.new Student(1, “조성모”, true); Student stu2 = t.new Student(2, “이승기”, false); Student stu3 = t.new Student(3, “윤도현”, false); //List 에 요소 추가하고 스트림 얻기 List list = new ArrayList<>(); list.add(stu1); list.add(stu2); list.add(stu3); Stream stuStream = list.stream(); stuStream.forEach( a -> System.out.println(a.toString()) ); //Set 에 요소 추가하고 스트림 얻기 Set set = new HashSet<>(); set.add(stu1); set.add(stu2); set.add(stu3); set.stream() .forEach(a -> System.out.println(a)); } }

실행 결과

Student{stuNum=1, stuName=’조성모’, isCome=true}

Student{stuNum=2, stuName=’이승기’, isCome=false}

Student{stuNum=3, stuName=’윤도현’, isCome=false}

Student{stuNum=1, stuName=’조성모’, isCome=true}

Student{stuNum=3, stuName=’윤도현’, isCome=false}

Student{stuNum=2, stuName=’이승기’, isCome=false}

1-3) 숫자범위에서 스트림 얻기

IntSteam의 range() 메서드를 이용해 범위를 지정해서 스트림을 얻을 수 있다.

public class Test { public static void main(String[] args) { //마지막 숫자 포함 IntStream intStream1 = IntStream.rangeClosed(1, 10); intStream1.forEach( a -> System.out.print(a+ ” “)); System.out.println(); //마지막 숫자 제외 IntStream intStream2 = IntStream.range(1, 10); intStream2.forEach( a -> System.out.print(a+ ” “)); } }

실행 결과

1 2 3 4 5 6 7 8 9 10

1 2 3 4 5 6 7 8 9

2. 파이프라인

파이프라인(pipeline)은 데이터의 처리결과가 다시 다음 단계의 입력값으로 들어가 연속적으로 처리되는 구조를 말한다. 수도관이 길게 연결되어 취수, 정수 등의 과정을 거쳐 개별 가정으로 보급되듯, 파이프라인이란 데이터가 일련의 과정을 거쳐 사용자가 원하는 결과값을 내놓도록 처리되는 구조이다.

파이프라인은 여러개의 스트림을 연결하는데 중간처리 스트림 여러개와 최종처리 스트림1개로 구성된다. 필요에 따라 중간처리 스트림을 부품처럼 연결해 사용한다. 중간처리 스트림은 필요한 데이터를 검색하거나 원하는 형태변환하는 등의 처리를 한다. 최종처리 스트림은 중간처리 스트림에서 반환하는 값을 토대로 결과값을 내놓는 처리를 한다.

중간처리(intermediate operators) : 필터링, 매핑, 정렬, 그룹핑 등

최종처리(terminal operators) : 합계, 평균, 개수, 최댓값, 최솟값 등

대량의 데이터 중에서 우리가 관심있는 데이터만 조건에 걸어 데이터 량을 줄이고 계산(가공)하는 과정을 리덕션(reduction)이라고 한다.

파이프라인 맛보기

스트림을 연결해 파이프라인 구조를 만들어서 데이터를 처리해보자. 맛보기이므로 ‘이런식으로 처리되는구나’ 하는 느낌만 느껴보자.

Member 클래스를 요소로 하는 List가 있다. 이를 가공해서 남성 나이의 평균값 을 구해보자.

먼저 Member 클래스이다.

public class Member { public static int MALE = 0; public static int FEMALE = 1; private String name; private int sex; private int age; Member(String name, int sex, int age){ this.name = name; this.sex = sex; this.age = age; } public int getSex(){ return sex; }; public int getAge(){ return age; }; @Override public String toString() { return “Member{” + “name='” + name + ‘\” + “, sex=” + sex + “, age=” + age + ‘}’; } }

List를 생성해 데이터를 넣고 가공해보기

public class Test { public static void main(String[] args) { List list = Arrays.asList( new Member(“홍길동”, Member.MALE, 30), new Member(“최나리”, Member.FEMALE, 26), new Member(“이태진”, Member.FEMALE, 92), new Member(“최성혁”, Member.MALE, 59) ); //f. 변수에 대입 double averageAge = //a. 원본스트림 생성 list.stream() //b. 새로운 스트림 생성–> Male 과 일치하는 값으로 필터링 .filter(m -> m.getSex() == Member.MALE) //c. 새로운 스트림 생성–> Member 와 age 매핑하기 .mapToInt(Member :: getAge) //d. 평균값을 계산해서 OptionalDouble 객체에 저장 .average() //e. 객체에 저장된 값 읽어들이기 .getAsDouble(); System.out.println(“남자 평균 나이 : “+ averageAge); /* 위 코드 한줄씩 실행해보기 */ //필터링 Stream stream1 = list.stream() .filter(m -> m.getSex() == Member.MALE); stream1.forEach( a -> System.out.println(a)); //필터링 + 매핑 IntStream stream2 = list.stream() .filter(m -> m.getSex() == Member.MALE) .mapToInt(Member::getAge); stream2.forEach(a -> System.out.println(a)); //필터링 + 매핑 + 평균(OptionalDouble로 받기) OptionalDouble optionalDouble = list.stream() .filter(m -> m.getSex() == Member.MALE) .mapToInt(Member::getAge) .average(); System.out.println(optionalDouble); //OptionalDouble에 있는 값 얻기 Double value = optionalDouble.getAsDouble(); System.out.println(value); } }

실행 결과

남자 평균 나이 : 44.5

Member{name=’홍길동’, sex=0, age=30}

Member{name=’최성혁’, sex=0, age=59}

30

59

OptionalDouble[44.5]

44.5

뭔가 코드가 길다. 여기서는 스트림을 연결해 내가 원하는 값을 쉽게 구할 수 있다는 점을 느끼면 된다. 아래에서 하나씩 익혀보자.

2-1) 필터링 – 요소 걸러내기

메서드 내용 distinct() 중복 제거하기 filter() 조건에 맞는값 찾기

public class Test { public static void main(String[] args) { List list = Arrays.asList(“사과”, “사과”, “딸기”, “바나나”, “복숭아”, “수박”, “딸기”, “바나나”, “수박”, “토마토”); System.out.println(“–중복제거”); Stream stream = list.stream().distinct(); stream.forEach(a -> System.out.print(a+ ” “)); System.out.println(”

–이름이 3글자”); list.stream() .filter(fruit -> fruit.length() == 3) .forEach(a -> System.out.print(a+ ” “)); System.out.println(”

–이름이 3글자, 중복제거”); list.stream() .distinct() .filter(fruit -> fruit.length() == 3) .forEach(a -> System.out.print(a+ ” “)); System.out.println(”

–이름이 3글자, 중복제거, 첫글자가 ‘바'”); list.stream() .distinct() .filter(fruit -> fruit.length() == 3) .filter(fruit -> fruit.startsWith(“바”)) .forEach(a -> System.out.print(a+ ” “)); } }

실행 결과

–중복제거

사과 딸기 바나나 복숭아 수박 토마토

–이름이 3글자

바나나 복숭아 바나나 토마토

–이름이 3글자, 중복제거

바나나 복숭아 토마토

–이름이 3글자, 중복제거, 첫글자가 ‘바’

바나나

2-2) 매핑 – 다른 값으로 변경하기

메서드 내용 flatMap XXX() 특정요소가 여러개의 요소로 대체되는 스트림 리턴 map XXX() 요소를 대체하는 새로운 스트림 리턴 as XXX Stream () 타입변환된 스트림 리턴 boxed () 박싱(boxing)된 값을 요소로 하는 스트림 리턴

flatMap 사용하기

public class Test01 { public static void main(String[] args) { /* String 요소를 공백기준으로 각각의 String 요소로 나누기 */ List listString = Arrays.asList( “홍 길 동”, “임 꺽 정” ); listString.stream() .flatMap(a -> Arrays.stream(a.split(” “))) .forEach(a -> System.out.print(a+ ” “)); System.out.println(); /* String 요소 , 기준으로 분리하기 각각을 int 형태로 바꾸기 */ List listInt = Arrays.asList( “17, 32, 71, 96, 26, 53” ); listInt.stream() .flatMapToInt(data -> { String[] strArr = data.split(“,”); int[] intArr = new int[strArr.length]; for(int i=0; i System.out.print(a+ ” “)); } }

실행 결과

홍 길 동 임 꺽 정

17 32 71 96 26 53

Map 사용하기

public class Test02 { public static void main(String[] args) { Test02 test = new Test02(); List list = Arrays.asList( test.new Student(“홍길동”, 91), test.new Student(“이민형”, 64), test.new Student(“김기준”, 72) ); list.stream() .mapToInt(Student :: getScore) .forEach(a -> System.out.println(“점수 : “+ a)); } class Student{ private String name; private int score; private Student(String name, int score){ this.name = name; this.score = score; } public String getName() { return name; } public int getScore() { return score; } } }

실행 결과

점수 : 91

점수 : 64

점수 : 72

asXXX 사용하기

public class Test03 { public static void main(String[] args) { int[] intArr = {16, 52, 49, 26, 83}; IntStream intStream; System.out.println(“–double 로 변환해서 출력하기”); intStream = Arrays.stream(intArr); intStream.asDoubleStream() .forEach( a -> System.out.print(a+ ” “)); System.out.println(”

–boxing 된 값 출력하기”); intStream = Arrays.stream(intArr); intStream.boxed() .forEach(a -> System.out.print(a+ ” “)); } }

실행 결과

–double 로 변환해서 출력하기

16.0 52.0 49.0 26.0 83.0

–boxing 된 값 출력하기

16 52 49 26 83

2-3) 정렬

숫자는 오름차순, 내림차순으로 정렬할 수 있다. 혹은 Comparator를 구현해 정렬기준을 제시할 수 있다.

메서드 내용 sorted() 오름차순 정렬 sorted() Comparable 구현기준에 따라 정렬 sorted(Comparator) Comparator 구현기준에 따라 정렬

public class Test { public static void main(String[] args) { Test t = new Test(); IntStream intStream = Arrays.stream(new int[]{2, 64, 37, 19, 82}); System.out.println(“–int[] 오름차순 정렬하기”); intStream.sorted() .forEach(a -> System.out.print(a+ ” “)); System.out.println(); List list = Arrays.asList( t.new Student(“홍길동”, 35), t.new Student(“최길동”, 26), t.new Student(“허길동”, 15), t.new Student(“박길동”, 96), t.new Student(“김길동”, 68) ); System.out.println(“–List – Student 기준에 따라 정렬하기”); list.stream() .sorted() .forEach(a-> System.out.println( “이름: “+ a.getName()+ ” | 점수: “+ a.getScore()) ); System.out.println(“–List – Student 기준 반대로 정렬하기”); list.stream() .sorted(Comparator.reverseOrder()) .forEach(a-> System.out.println( “이름: “+ a.getName()+ ” | 점수: “+ a.getScore()) ); } //Comparable 구현객체 class Student implements Comparable{ private String name; private int score; private Student(String name, int score){ this.name = name; this.score = score; } public String getName() { return name; } public int getScore() { return score; } //compareTo 오버라이드 해서 정렬기준(=점수) 제시 @Override public int compareTo(Student o) { return Integer.compare(score, o.score); } } }

실행 결과

–int[] 오름차순 정렬하기

2 19 37 64 82

–List – Student 기준에 따라 정렬하기

이름: 허길동 | 점수: 15

이름: 최길동 | 점수: 26

이름: 홍길동 | 점수: 35

이름: 김길동 | 점수: 68

이름: 박길동 | 점수: 96

–List – Student 기준 반대로 정렬하기

이름: 박길동 | 점수: 96

이름: 김길동 | 점수: 68

이름: 홍길동 | 점수: 35

이름: 최길동 | 점수: 26

이름: 허길동 | 점수: 15

2-4) 루핑 – 요소 반복하기

메서드 내용 peek() -중간처리 메서드

-중간에 호출해야 스트림이 동작한다

-마지막에 호출하면 스트림이 동작하지 않는다 forEach() -최종처리 메서드

-마지막에 호출해야 스트림이 동작한다.

-이후에 다른 메서드를 호출시 컴파일 에러 발생

public class Test { public static void main(String[] args) { int[] intArr = {6, 1, 2, 5, 7, 3, 4, 8}; System.out.println(“–peek 호출(스트림 동작안함)”); Arrays.stream(intArr) .filter( a-> a%2 == 0) //짝수 필터링 .peek( a -> System.out.print(a+ ” “)); System.out.println(“–peek 호출 + 최종메서드 호출”); int total = Arrays.stream(intArr) .filter( a-> a%2 == 0) .peek( a -> System.out.print(a+ ” “)) .sum(); System.out.println(”

total : “+ total); System.out.println(“–forEach 호출”); Arrays.stream(intArr) .filter( a-> a%2 == 0) .forEach(a -> System.out.print(a+ ” “)); System.out.println(”

–forEach + 메서드 호출(컴파일에러)”); // Arrays.stream(intArr) // .filter( a-> a%2 == 0) // .forEach(a -> System.out.println(a)) // .count(); } }

실행 결과

–peek 호출(스트림 동작안함)

–peek 호출 + 최종메서드 호출

6 2 4 8

total : 20

–forEach 호출

6 2 4 8

–forEach + 메서드 호출(컴파일에러)

2-5) 매칭

2-6) 기본집계

2-6-1) 합계, 평균, 최댓값, 최솟값, 카운트 등 최종값 구하기

메서드 내용 count() 개수 findFirst() 첫번째 요소 max() 최댓값 min() 최솟값 sum() 합계 average() 평균

public class Test01 { public static void main(String[] args) { int[] intArr = {6, 1, 2, 5, 7, 3, 4, 8}; long count = Arrays.stream(intArr) .filter( a-> a%2 == 0) .count(); System.out.println(“count : “+ count); long sum = Arrays.stream(intArr) .filter( a-> a%2 == 0) .sum(); System.out.println(“sum : “+ sum); double average = Arrays.stream(intArr) .filter( a-> a%2 == 0) .average() .getAsDouble(); System.out.println(“average : “+ average); int max = Arrays.stream(intArr) .filter( a-> a%2 == 0) .max() .getAsInt(); System.out.println(“max : “+ max); int min = Arrays.stream(intArr) .filter( a-> a%2 == 0) .min() .getAsInt(); System.out.println(“min : “+ min); int first = Arrays.stream(intArr) .filter( a-> a%2 == 0) .findFirst() .getAsInt(); System.out.println(“first : “+ first); } }

실행 결과

count : 4

sum : 20

average : 5.0

max : 8

min : 2

first : 6

2-6-2) Optional 클래스 사용하기

집계된 값을 저장하고, 집계값이 존재하지 않을 경우 대체값 설정할 수 있다

메서드 내용 isPresent() 값이 저장되었는지 여부 orElse() 값이 저장되지 않았다면 대체값 설정 ifPresent() 값이 저장된 경우 Consumer 에서 처리하기

public class Test02 { public static void main(String[] args) { List list = new ArrayList<>(); OptionalDouble optionalDouble = list.stream() .mapToInt(Integer :: intValue) .average(); //값이 존재하는지 확인하고 대체값 설정 if(optionalDouble.isPresent()){ System.out.println(optionalDouble.getAsDouble()); } else { System.out.println(“avg1 : 0.0”); } //값이 존재하는지 확인하고 대체값 설정 double avg = list.stream() .mapToInt(Integer :: intValue) .average() .orElse(0.0); System.out.println(“avg2 : “+ avg); //값이 존재하는지 확인하고 있다면 값 출력하기 list.stream() .mapToInt(Integer :: intValue) .average() .ifPresent(a -> System.out.println(“avg3 : “+ a)); } }

실행 결과

avg1 : 0.0

avg2 : 0.0

2-7) 수집

Student 클래스를 요소로 하는 컬렉션에서 조건에 일치하는 요소로 새로운 컬렉션 생성하기

public class Test { public enum Sex {MALE, FEMALE} public enum City {SEOUL, BUSAN} class Student{ private String name; private int score; private Sex sex; private City city; private Student(String name, int score, Sex sex, City city){ this.name = name; this.score = score; this.sex = sex; this.city = city; } public String getName() { return name; } public int getScore() { return score; } public Sex getSex() { return sex; } public City getCity() { return city; } } public static void main(String[] args) { Test t = new Test(); List list = Arrays.asList( t.new Student(“이순신”, 25, Sex.MALE, City.BUSAN), t.new Student(“이순령”, 55, Sex.FEMALE, City.SEOUL), t.new Student(“이순자”, 74, Sex.FEMALE, City.BUSAN), t.new Student(“이순근”, 17, Sex.MALE, City.SEOUL), t.new Student(“이순혜”, 35, Sex.FEMALE, City.SEOUL) ); System.out.println(“–List -Male”); List listMale = list.stream() .filter(a -> a.getSex() == Sex.MALE) .collect(Collectors.toList()); listMale.stream() .forEach(a-> System.out.print(a.getName()+ ” “)); System.out.println(”

–Set -부산”); Set setFemale = list.stream() .filter(a -> a.getCity() == City.BUSAN) .collect(Collectors.toCollection(HashSet::new)); setFemale.stream() .forEach(a -> System.out.print(a.getName()+ ” “)); } }

실행 결과

–List -Male

이순신 이순근

–Set -부산

이순신 이순자

자바 스트림(Stream) API 정리, 스트림을 이용한 가독성 좋은 코드 만들기(feat. 자바 람다, 함수형 프로그래밍, 자바8)

반응형

Java Stream

자바 공부를 하면서 Stream이 무엇인지, 어떻게 사용되고 있는지 인지는 하고 있었으나 실제 코드로 타이핑해보지 않았다.

그러던 중 이번에 가볍게 API 훑어보는 식으로 공부를 하면서 코드를 쳐보면서 조금 더 익히게 되었다.

Stream은 자바 8부터 추가된 기능으로 “컬렉션, 배열등의 저장 요소를 하나씩 참조하며 함수형 인터페이스(람다식)를 적용하며 반복적으로 처리할 수 있도록 해주는 기능”이다.

(InputStream, OutputStream같은 I/O Stream이 아니다.)

1 2 3 4 5 6 7 8 9 10 11 12 13 14 List < String > names = Arrays.asList( “jeong” , “pro” , “jdk” , “java” ); // 기존의 코딩 방식 long count = 0 ; for ( String name : names) { if (name.contains( “o” )) { count + + ; } } System . out . println ( “Count : ” + count); // 2 // 스트림 이용한 방식 count = 0 ; count = names.stream().filter(x – > x.contains( “o” )).count(); System . out . println ( “Count : ” + count); // 2

다짜고짜 코드를 가지고 설명하면 위와 같다.

어떠한 컬렉션(names)이 존재하고 그 컬렉션의 요소를 순회하면서 “o”가 포함된 요소의 개수를 구한다고 가정했을 때, 기존의 코드 방식은 반복 순회를 위한 for문, 필터링을 위한 분기 if문이 사용되야 비로소 구할 수 있었던 반면,

스트림을 이용하면 한 줄의 코딩만으로 count값을 구할 수 있다. (count 선언, 출력을 제외하면.)

즉, 불필요한 코딩(for, if 문법)을 걷어낼 수 있고 직관적이기 때문에 가독성이 좋아진다 .

이 점이 Stream의 장점 이자 목적 이다.

Stream은 어떤 것들에 적용할 수 있을까?

Stream은 주로 Collection, Arrays에서 쓰인다.

물론 두 개 뿐만 아니라 I/O resources(ex. File), Generators, Stream ranges, Pattern 등에서도 사용할 수 있다.

해당 객체들로 부터 Stream을 생성하는 법은 스스로 찾아보도록 하고 기본적으로 자주 쓰이는 것들만 간단히 소개한다.

1 2 3 4 5 6 7 List < String > names = Arrays.asList( “jeong” , “pro” , “jdk” , “java” ); names.stream(); //Collection에서 스트림 생성 Double[] dArray = { 3. 1 , 3. 2 , 3. 3 }; Arrays.stream(dArray); //배열로 스트림 생성 Stream < Integer > str = Stream.of( 1 , 2 ); // 스트림 직접 생성

스트림 사용법과 주의사항

스트림의 구조는 크게 3가지로 나뉜다.

1. 스트림생성

2. 중개 연산

3. 최종 연산

-> 실제 사용법으로 표기하면 “Collections같은 객체 집합.스트림생성().중개연산().최종연산();” 이런식이다.

* 계속해서 . 으로 연계할 수 있게 하는 방법을 파이프라인이라고도 한다.

위에서 어떻게 스트림을 생성하는지는 알았으니 이제부터는 중개 연산에 쓰이는 함수는 어떤 것들이 있고 어떻게 사용하는지를 알아보고, 최종 연산에 쓰이는 함수는 어떤 것들이 있고 어떻게 사용하는지 API를 훑어보는 느낌으로 알아보면 될 것이다.

– 중개 연산

Filter

1 2 List < String > names = Arrays.asList( “jeong” , “pro” , “jdk” , “java” ); Stream < String > a = names.stream().filter(x – > x.contains( “o” )); cs

filter는 말 그대로 필터링, 즉 조건에 맞는 것만 거른다는 것이다.

위의 코드에서는 람다식을 이용해서 x 로 스트림의 요소를 받고 각 요소에 “o”라는 알파벳이 있는 것들만 거른다.

즉, “jeong” 과 “pro” 만 가지고 있는 스트림을 반환한다.

Map

1 2 3 4 5 6 List < String > names = Arrays.asList( “jeong” , “pro” , “jdk” , “java” ); names.parallelStream() .map(x – > x. concat ( “s” )) .forEach(x – > System . out . println (x)); //jeongs, pros, jdks, javas Colored by Color Scripter cs

앞서 filter나 map은 자바스크립트에서도 다뤄서 어떤 기능을 하는지는 알 수 있다.

map은 스트림의 각 요소를 연산하는데 쓰인다. 위와 같은 경우에는 각 문자열(요소)마다 뒤에 “s”를 붙였다.

숫자일 경우 * 2 로 두 배를 만든다든지 등의 다양한 조작이 가능하다.

Peek

peek()도 Map과 유사하게 각 요소에 어떤 연산을 적용할 때 사용한다.

Sorted

말 그대로 스트림의 요소들을 정렬해준다.

Limit

1 2 3 4 5 List < Integer > ages = Arrays.asList( 1 , 2 , 3 , 4 , 5 , 6 , 7 , 8 , 9 ); ages.stream() .filter(x – > x > 3 ) .limit( 3 ); //4,5,6 Colored by Color Scripter cs

스트림의 개수를 .limit(3) 으로 지정하면 3개로 제한한다. (물론 중개연산이라 스트림 반환)

Distinct

스트림의 요소가 예를 들어 1,2,1,2,1,2,1,2 일 때 .distinct()를 적용하면 1,2로 중복을 제거한다.

Skip

.skip(3) 이라고하면 처음 3개의 요소는 제외하고 나머지 요소들로 새로운 stream을 만든다.

mapToInt, mapToLong, mapToDouble

mapXXX 함수들은 해당 타입의 스트림으로 바꿔준다. 예를들어 “1”,”2″,”3″ 을 가진 스트림이 있었으면 mapToInt를 적용하면 1,2,3 을 가진 스트림으로 변환 해준다.

– 최종 연산

count(), min(), max(), sum(), average()

최종 연산이기 때문에 앞서 함수를 적용했던 스트림에 있는 요소들에 대해 count를 세거나 최소값, 최대값, 합계, 평균 값을 얻을 수 있는 함수다.

* 참고로 average()는 연산에 안보인다… 사라진 건 아닐텐데 확인이 필요하다.

reduce

1 2 3 List < Integer > ages = new ArrayList < Integer > (); ages. add ( 1 );ages. add ( 2 );ages. add ( 3 ); //1,2,3 System . out . println (ages.stream().reduce((b,c) – > b + c).get()); //1+2+3=6 cs

reduce는 누적된 값을 계산하는 함수다.

여기서 b, c로 지정한 파라미터를 가지고 리턴한 결과(b+c)가 다시 b가 되고 다음 스트림의 요소가 c가 되어 계속 누적된다. 따라서 1+2+3인 6이 결과로 찍힌다.

forEach

1 2 3 4 List < Integer > ages = new ArrayList < Integer > (); ages. add ( 1 );ages. add ( 2 );ages. add ( 3 ); //1,2,3 Set < Integer > set = ages.stream().collect(Collectors.toSet()); set.forEach(x – > System . out . println (x)); //1,2,3 cs

forEach는 map이나 peek의 최종연산 버전이다. 각 요소를 돌면서 처리할 수 있도록 되어있다.

collect

collect는 스트림의 값들을 모아주는 기능을 한다. toMap, toSet, toList로 해당 스트림을 다시 컬렉션으로 바꿔준다.

iterator

1 2 3 4 5 List < String > names = Arrays.asList( “jeong” , “pro” , “jdk” , “java” ); Iterator < String > iter = names.stream().iterator(); while (iter.hasNext()) { System . out . println (iter.next()); //jeong, pro, jdk, java } Colored by Color Scripter cs

iterator는 Iterator를 반환한다.

noneMatch, anyMatch, allMatch

1 2 3 List < Integer > ages = new ArrayList < Integer > (); ages. add ( 1 );ages. add ( 2 );ages. add ( 3 ); //1,2,3 System . out . println (ages.stream().filter(x – > x > 1 ).noneMatch(x – > x > 2 )); //false cs

noneMatch는 최종적으로 얻은 스트림의 “모든” 요소들이 조건을 만족하지 “않는”지를 판단해서 boolean값을 리턴한다.

anyMatch는 스트림의 요소들 중에 하나라도 조건을 만족하는지 판단해서 boolean값을 리턴하고,

allMatch는 스트림의 “모든” 요소들이 조건을 만족하는지를 판단해서 boolean값을 리턴한다.

기타로 그룹핑하고 통계를 얻는 것도 있는데 생략한다.

* 알아 둘 것

– Stream은 재사용이 불가능하다.

1 2 3 4 5 // Stream 재사용 불가 stream has already been operated upon or closed. Stream < String > a = names.stream().filter(x – > x.contains( “o” )); count = a.count(); List < String > lists = a.collect(Collectors.toList());

위 코드에서 보듯 한 번 사용한 스트림 a에 대해서 다시 사용하려고 하면 에러가 난다.

– 병렬 스트림은 여러 쓰레드가 작업한다.

1 names.parallelStream().filter(x – > x.contains( “o” )).count();

stream()으로 스트림을 생성하지 않고 위 처럼 parallelStream()으로 병렬 스트림을 만들 수 있다.

이렇게하면 여러 쓰레드가 스트림에서 요소를 필터링하고 나온 요소 수를 계산하고 쓰레드끼리 다시 한 번 각자 계산한 count 값들을 더해서 리턴해준다.

단순하게 생각하면 여러쓰레드가 처리해주니 병렬스트림이 항상 성능면에서 유리해보일 수 있지만 애플리케이션에서 사용하는 쓰레드가 많거나 스트림의 요소 수가 많지 않다면 오히려 쓰레드를 사용하는데 드는 오버헤드가 더 클 수도 있다.

– 중개 연산은 미리하지 않는다 지연 연산을 한다.

1 2 Stream < String > a = names.stream().filter(x – > x.contains( “o” )).map(x – > x. concat ( “s” )); a.forEach(x – > System . out . println (x)); Colored by Color Scripter cs

위와 같은 코드가 있으면 위에 filter와 map 함수는 미리 계산하고 있지 않고 있다가 forEach와 같은 최종연산이 적용될 때 중개 연산도 실행된다.

이로써 얻는 장점은 미리 계산하면서 두 번 순회하는 짓을 안할 수 있게 된다는 점이다.

참고사이트

https://www.slideshare.net/madvirus/8-api

http://iloveulhj.github.io/posts/java/java-stream-api.html

http://jlblog.me/92

https://www.slideshare.net/madvirus/8-api

반응형

Java에서 스트림을 만드는 10가지 방법

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32

import java . util . Arrays ; import java . util . Random ; import java . util . UUID ; import java . util . concurrent . ThreadLocalRandom ; import java . util . stream . Stream ; class Main { public static void main ( String [ ] args ) { // UUID 스트림 생성 Stream UUIDStream = Stream . generate ( UUID :: randomUUID ) . limit ( 2 ) ; System . out . println ( Arrays . toString ( UUIDStream . toArray ( ) ) ) ; // 0과 1 사이의 무작위 더블 스트림을 생성합니다. Stream randomDoubleStream = Stream . generate ( Math :: random ) . limit ( 4 ) ; System . out . println ( Arrays . toString ( randomDoubleStream . toArray ( ) ) ) ; // 임의의 정수 스트림 생성 Stream randomIntStream = Stream . generate ( ThreadLocalRandom . current ( ) :: nextInt ) . limit ( 2 ) ; System . out . println ( Arrays . toString ( randomIntStream . toArray ( ) ) ) ; // 임의의 정수 스트림을 생성합니다. Random random = new Random ( ) ; randomIntStream = Stream . generate ( random :: nextInt ) . limit ( 5 ) ; System . out . println ( Arrays . toString ( randomIntStream . toArray ( ) ) ) ; } }

키워드에 대한 정보 자바 stream

다음은 Bing에서 자바 stream 주제에 대한 검색 결과입니다. 필요한 경우 더 읽을 수 있습니다.

이 기사는 인터넷의 다양한 출처에서 편집되었습니다. 이 기사가 유용했기를 바랍니다. 이 기사가 유용하다고 생각되면 공유하십시오. 매우 감사합니다!

사람들이 주제에 대해 자주 검색하는 키워드 [자바의 정석 – 기초편] ch14-15,16 스트림, 스트림의 특징

  • 자바
  • 자바의정석
  • java
  • 자바의정석기초편
  • 자바기초
  • 자바기초강의
  • 자바강의
  • 남궁성
  • 자바강좌
  • java강좌
  • 코딩
[자바의 #정석 #- #기초편] #ch14-15,16 #스트림, #스트림의 #특징


YouTube에서 자바 stream 주제의 다른 동영상 보기

주제에 대한 기사를 시청해 주셔서 감사합니다 [자바의 정석 – 기초편] ch14-15,16 스트림, 스트림의 특징 | 자바 stream, 이 기사가 유용하다고 생각되면 공유하십시오, 매우 감사합니다.

Leave a Comment