관리 메뉴

나구리의 개발공부기록

자바의 정석 기초편 ch14 - 15 ~ 22[스트림, 스트림의 특징, 스트림 만들기] 본문

유튜브 공부/JAVA의 정석 기초편(유튜브)

자바의 정석 기초편 ch14 - 15 ~ 22[스트림, 스트림의 특징, 스트림 만들기]

소소한나구리 2023. 12. 20. 16:30

1) 스트림(Stream)

  • Java8부터 적용됨
  • 데이터의 연속적인 흐름, 즉 메서드 체인 방식으로 데이터들의 연산결과를 반환
  • 다양한 데이터 소스(컬렉션, 배열)를 표준화된 방법으로 다루기 위하여 나옴

(1) 동작 방식

  1. 데이터소스 [컬렉션(List,Set,Map), 배열]
  2. Stream 생성
  3. 중간연산(여러번)
  4. 최종연산(한번)
  5. 결과반환
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
Stream<Integer> intStream = list.stream(); // 컬렉션
Stream<String> strStream = Stream.of(new String[]{"a", "b", "c"}); // 배열
Stream<Integer> evenStream = Stream.iterate(0, n -> n + 2); // 0,2,4,6, ...
Stream<Double> randomStream = Stream.generate(Math::random); // 랜덤수
IntStream intStreamRandom = new Random().ints(5); // 난수 스트림(크기가 5)

 

(2) 스트림이 제공하는 기능

  • 중간 연산 : 연산결과가 스트림인 연산, 반복적으로 사용 가능
  • 최종 연산 : 연산결과가 스트림이 아닌 연산. 단 한번만 적용 가능(스트림의 요소를 소모함)

스트림 작성 방식
중간연산은 여러개 적용 가능, 최종연산은 1번만하여 결과를 반환하고 스트림은 종료

 

2) 스트림의 특징

(1) 데이터 소스로부터 데이터를 읽기만 함

  • 스트림은 읽기 전용이기 때문에 데이터를 저장할 수 없음
  • 즉, 스트림의 연산결과는 원본에 영향을 주지않고 연산한 결과를 담기위해선 새로운 참조변수가 필요함
List<Integer> list = Arrays.asList(3,1,5,4,2);
List<Integer> sortedList = list.stream().sorted()
                               .collect(Collectors.toList()); // 새로운 List에 저장
System.out.println(list);        // [3, 1, 5, 4, 2]
System.out.println(sortedList);  // [1, 2, 3, 4, 5]

 

(2) 일회용

  • Iterator처럼 일회용이기 때문에 최종연산으로 스트림을 소모하면 다시 스트림을 생성해야함
  • 즉 재사용이 불가능함
strStream.forEach(System.out::println); // 모든 요소를 화면에 출력(최종연산)
int numOfStr = strStream.count();       // 에러. 스트림이 이미 닫혔음.

 

(3) 지연된 연산

  • 스트림의 중간연산은 기본적으로 순서대로 연산을 하지만 당장 수행을 할 수 없을경우에는 연산을 지연시킨 후 연산을 적용할 수 있을 때 연산을 적용함
  • 최종 연산 전까지 지연된연산까지 모든 연산을 마친 후에 최종 연산 결과를 반환함
  • 무한정으로 스트림의 요소가 제공되는 무한 스트림을 아래처럼 중간연산의 순서로 작성하면 distinct로 중복을 제거할 수 없어 동작이 안할 것 같지만, 스트림은 지연된 연산을 제공하기 때문에 해당 연산을 지연 시긴 후 다른 중간연산부터 수행함
  • 가능한 중간연산이 수행되고 나서 지연된 연산을 다시 수행하고 모든 연산이 완료되면 최종 연산을 반환함
  • 즉, 중간연산은 순서에 관계없이 적용하고 싶은 연산을 모두 입력해면 됨 -> 장점
IntStream intStream = new Random().ints(1, 46);  // 1~45범위의 무한 스트림
intStream.distinct()  // 중간 연산
         .limit(6)    // 중간 연산
         .sorted()    // 중간 연산
         .forEach(i->System.out.print(i+",")); // 최종 연산

 

 

(4) 반복처리

  • 스트림은 forEach()메서드(최종연산)로 간단하게 요소를 반복할 수 있음
  • 성능은 조금 떨어질 수 있지만 코드가 매우 간결해지기때문에 자주 사용됨
// 일반 적인 반복문
for (String str : strList)
    System.out.println(str);

// stream의 forEach로 요소를 반복해서 출력
stream.forEach(System.out::println);

// forEach의 구조
void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);  // 매개변수의 널 체크
    
    for (T t : src)  // 내부 반복 (for문을 메서드 안으로 넣음)
        action.accept(T);
}

 

(5) 병렬 처리

  • 병렬스트림으로 전환하는 .parallel()메서드로 멀티 쓰레드로 처리할 수 있음
Stream<String> strStream = Stream.of("dd", "aaa", "CC", "cc", "b");
int sum = strStream.parallel() // 병렬 스트림으로 전환(속성만 변경)
                   .mapToInt(s -> s.length()).sum(); // 모든 문자열의 길이의 합

 

(6) 기본형 스트림

  • IntStream, LongStream, DoubleStream
  • 오토박싱&언박싱의 비효율이 제거 됨 (Stream<Integer> 대신 intStream을 사용)
  • 데이터 소스가 기본형일때 사용 가능 하며 스트림 변환시 참조형으로 변환이 되는데 데이터가 많으면 효율이 떨어져 기본형스트림을 제공함
  • 숫자와 관련된 유용한 메서드를 Stream<T>보다 더 많이 제공 (sum(), count(), average()... 등등)

3) 스트림 만들기(스트림 생성)

  • Collection인터페이스의 stream()으로 컬렉션을 스트림으로 변환할 수 있음
Stream<E> stream()	// Collection인터페이스의 메서드 (list, set에서 사용가능)

List<Integer> list = Arrays.asList(1,2,3,4,5); 
Stream<Integer> intStream = list.stream();	// list를 stream()으로 변환(스트림 생성)
 
 // 스트림의 모든 요소를 출력
intStream.forEach(System.out::print);	// 12345 forEach(메서드 참조)
intStream.forEach(System.out::print);	// 한번더? forEach는 최종연산 -> 에러. 스트림이 이미 종료

 

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

  • Stream.of(T... values) : 인수의 값을 직접 입력하여 스트림을 생성, 가변 인자이기 때문에 원하는 만큼 입력할 수 있음
  • Arrays.strea(T[]) : 인수로 배열을 입력(생성)해야하며, 2번째(from) ~ 3번째(to, to는 범위에 미포함)까지의 범위를 지정할 수 있음
 // Stream.of()
 Stream<String> strStream = Stream.of("a","b","c"); // 가변 인자
 Stream<String> strStream = Stream.of(new String[]{"a","b","c"});
 
// Arrays.stream()
 Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"});
 // from ~ to (to는 미포함 = 0,1,2 까지만 생성)
 Stream<String> strStream = Arrays.stream(new String[]{"a","b","c"}, 0, 3);

 

(2) 기본형 배열로부터 스트림 생성

  • 생성 방식은 동일함
IntStream IntStream.of(int... values)	// 가변인자
IntStream IntStream.of(int[])

IntStream Arrays.IntStream(int[])
// from ~ to (to는 미포함)
IntStream Arrays.IntStream(int[] array, int from, int to)

 

(3) 배열 스트림 예제

public class Ex14_StreamArr {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5);

        // list를 데이터 소스로 하는 새로운 스트림 생성
        Stream<Integer> intStream = list.stream();
        intStream.forEach(System.out::print);	// 출력

        intStream = list.stream();	// Stream을 재생성(일회성이기 때문에 새로 생성해야함)
        intStream.forEach(System.out::print);	// 재출력

        System.out.println();

        // of(), 가변인자로 스트림 생성
        Stream<String> strStream = Stream.of("a","b","c");
        strStream.forEach(System.out::println);

        // of(), 스트링 배열을 생성하여 스트림을 생성
        Stream<String> strStream2 = Stream.of(new String[] {"a","b","c"});
        strStream2.forEach(System.out::println);

        // stream(), 스트링 배열을 생성하여 스트림을 생성
        Stream<String> strStream3 = Arrays.stream(new String[] {"a","b","c"});
        strStream3.forEach(System.out::println);

        // stream(), 인트 배열을 인수로 스트림을 생성
        // 숫자타입은 기본형스트림을 사용하면 편리함, 성능면에서 장점이 있음
        int[] intArr = {1,2,3,4,5};
        IntStream intStream2 = Arrays.stream(intArr);
		intStream2.forEach(System.out::println);        // 그냥 출력

        // forEach외 다른 최종연산들
        intStream2 = Arrays.stream(intArr);
		System.out.println("intStream2의 count = "+intStream2.count());

        intStream2 = Arrays.stream(intArr);
		System.out.println("intStream2의 sum = "+intStream2.sum());

        intStream2 = Arrays.stream(intArr);
        System.out.println("intStream2의 average = "+intStream2.average());

        // Integer배열로 기본형이아닌 참조형 스트림을 생성, max, sum등의 숫자관련 편의메서드를 사용할 수 없음
        Integer[] intArr2 = {1,2,3,4,5};
        Stream<Integer> intStream3 = Arrays.stream(intArr2);
        intStream3.forEach(System.out::println);
    }
}

 

(4) 난수를 요소로 갖는 스트림 생성 

  • Random클래스에는 int, long, double타입의 난수를 생성하는 메서드를 가지고 있어 해당 메서드들을 활용하여 난수를 요소로 갖는 스트림을 생성할 수 있음
  • ints() : Integer 타입 난수 생성(Integer 범위)
  • longs() : Long 타입 난수 생성(Long 범위 )
  • doubles() : Doublec 타입 난수 생성(0.0이상 1.0미만)
IntStream intStream = new Random().ints();        // 무한 스트림

// 무한스트림은 limit()로 자르거나, 무한스트림을 생성하는 메서드에 크기를 지정해 줘야함.
intStream.limit(5).forEach(System.out::println); // 5개의 요소만 출력한다.
IntStream intStream = new Random().ints(5);      // 크기가 5인 난수 스트림을 반환


// Random()클래스에 정의되어있는 메서드들 - 아래의 메서드들은 무한스트림을 생성함
             Integer.MIN_VALUE <=  ints()  <= Integer.MAX_VALUE
                Long.MIN_VALUE <=  longs() <= Long.MAX_VALUE
                           0.0 <= doubles() < 1.0

// 무한 스트림, begin ~ end(미포함) 범위의 난수를 생성
IntStream    ints(int begin, int end)                    
LongStream   longs(long begin, long end)  
DoubleStream doubles(double begin, double end)

// 유한 스트림, 난수의 생성 개수를 지정할 수 있음
IntStream    ints(long streamSize, int begin, int end)   
LongStream   longs(long streamSize, long begin, long end)
DoubleStream doubles(long streamSize, double begin, double end)

 

(6) 난수 스트림 예제

  • 중간연산인 limit()혹은 난수를 생성하는 메서드에 인수를 하나만 입력하여 스트림의 개수를 지정
  • 메서드의 인수에 2개를 입력하면 범위를 지정할 수 있음(2번째 인수의 값은 포함되지 않음)
  • 메서드의 인수에 3개를 입력하면 개수, 범위를 모두 지정할 수 있음(범위는 위와 동일)
public class Ex14_StreamRandom {
    public static void main(String[] args) {
        // 무한스트림 개수지정 - limit
        IntStream intStream = new Random().ints();
        intStream.limit(3).forEach(System.out::println);
        System.out.println("-----------------------------------------------------");

        // 무한스트림 개수지정 - ints(size)
        IntStream intStream2 = new Random().ints(7);
        intStream2.forEach(System.out::println);
        System.out.println("-----------------------------------------------------");

        // 무한스트림 범위지정 - ints(from, to)
        IntStream intStream3 = new Random().ints(1, 10);
        intStream3.limit(5).forEach(System.out::println);

        System.out.println("-----------------------------------------------------");
        // 유한스트림 개수,범위지정 - ints(출력개수, from, to)
        IntStream intStream4 = new Random().ints(5, 10, 20);
        intStream4.forEach(System.out::println);
    }
}

 

(7) 정수를 요소로 갖는 스트림 생성

  • InteStream과 LongStream의 range()를 사용하면 특정 범위의 정수를 갖는 스트림을 생성할 수 있음
  • 두번째 인수의 값을 미포함, 포함하는 메서드가 모두 존재함
IntStream IntStream.range(int begin, int end)       // from ~ to(to 미포함)
IntStream IntStream.rangeCloed(int begin, int end)  // from ~ to(to 포함)
 
IntStream intStream = IntStream.range(1, 5);       // 1,2,3,4
IntStream intStream = IntStream.rangeClosed(1, 5); // 1,2,3,4,5

 

(8) 람다식을 소스로 하는 스트림 생성

  • iterate()는 이전 요소를 seed로 해서 다음 요소를 계산 즉, 입력된값을 받아서 계산
  • generate()는 seed를 사용하지 않아 독립적으로 계산함
// 무한스트림 - limit으로 개수 설정 해줘야함
static <T> Stream<T> iterate(T seed,UnaryOperator<T> f) // 이전 요소에 종속적(T seed = 초기값)
static <T> Stream<T> generate(Supplier<T> s)            // 이전 요소에 독립적

// iterate - 앞의 결과를 가지고 다음결과가 나옴
Stream<Integer> evenStream   = Stream.iterate(0, n->n+2);  // 0,2,4,6, ...

// generate - 이전 결과가 전혀 상관 없음
Stream<Double>  randomStream = Stream.generate(Math::random);	// 난수
Stream<Integer> oneStream    = Stream.generate(()->1);	// 계속 1이 찍힘

 

(9) 람다식 스트림 실습

public class Ex14_StreamLambda {
    public static void main(String[] args) {

        // iterate(T seed, UnaryOperator f)	: 단항연산자
        // iterate() 무한스트림 출력, seed값을 받아서 람다식의 연산을 수행
        Stream<Integer> intStream = Stream.iterate(1, n -> n + 2);
        intStream.limit(10).forEach(System.out::println);

        // generate(Supplier s) : 주기만 하는것 입력x, 출력O
        // generate() 무한스트림 출력, 입력값이 없음
        Stream<Integer> intStream2 = Stream.generate(() -> 1);
        intStream2.limit(10).forEach(System.out::println);
    }
}

 

(10) 파일을 소스로 하는 스트림 생성

  • Files의 list나 lines로 값을 파일이나 파일의 내용을 스트림으로 생성할 수 있음
Stream<Path>   Files.list(Path dir)   // Path는 파일 또는 디렉토리

// 파일 내용을 라인 단위로 읽어서 String Stream 으로 만듦
Stream<String> Files.lines(Path path)  
Stream<String> Files.lines(Path path, Charset cs)
Stream<String> lines() // BufferedReader클래스의 메서드(15장, 강의에없음)

 

(11) 비어있는 스트림 생성

  • Stream.empty()로 비어있는 스트림을 생성
Stream emptyStream = Stream.empty();
long count = emptyStream.count();

 

** 출처 : 남궁성의 정석코딩_자바의정석_기초편 유튜브