JAVA 스트림

5 분 소요

자바의정석 책을 참고하여
스트림에 대해 공부한것을 기록하기 위해 작성하였습니다.

스트림이란?

많은 수의 데이터를 다룰때 컬렉션이나 배열에 데이터를 담고 원하는 결과를 얻기 위하여 우리는 지금까지,
for 문과 iterator를 이용하여 코드를 작성해왔다.
그러나 이런 방식으로 작성된 코드는 너무 가독성과 재사용성이 떨어진다.

데이터 소스마다 다른 방식으로 데이터를 다루어야한다. 각 컬렉션 클래스는 같은 기능의 메서드들이 중복정의 되어있다.
-> List를 정렬할땐 Collections.sort() 를 하용하고 배열을 정렬할땐 Arrays.sort() 를 사용한다.

이러한 문제점을 해결하기 위해 만들것이 바로 ‘스트림’이다.
스트림은 데이터를 추상화하고 데이터를 다루는데 자주 사용되는 메서드를 정의해 놓았다.
데이터를 추상화한다는 의미는 데이터 소스가 무엇이던간에 같은 방식으로 다룰 수 있게 한다는 의미이다.
즉 코드의 재사용성이 높아진다는 의미가 될거같다.

스트림을 사용하면 배열이나 컬렉션뿐만 아니라 파일에 저장된 데이터도 모두 같은 방식으로 다룰 수 있다.

스트림의 특징

스트림은 데이터 소스를 변경하지 않는다.

스트림은 데이터 소스로 부터 데이터를 읽기만 할뿐, 데이터 소스를 변경하지 않는다는 차이가 있다.
결과값이 필요하다면 스트림으로부터 연산된 결과를 컬렉션이나 배열에 담아서 반환할 수 있다.

List<String> sortedList = strStream.sorted().collect(Collector.toList());

스트림은 일회용이다.

스트림은 iterator 처럼 일회용이다. iterator로 컬렉션의 요소를 모두 읽고나면 다시 사용할 수 없는것처럼,
스트림도 한번 사용하면 닫혀서 재사용할 수 없다.

strStream1.sorted().forEach(System.out::println);
strStream1.sorted().forEach(System.out::println); //ERROR 스트림이 닫혀서 사용할 수 없음.

스트림은 작업을 내부 반복으로 처리한다.

내부 반복 이라는 것은 반복문을 메서드의 내부에 숨길 수 있다는 것을 말한다. forEach()는 스트림에 정의된 메서드 중 하나로 매개변수에 대입된 람다식을 데이터 소스의 모든 요소에 적용한다.

// 기존 반복문을 이용하여 컬렉션의 요소를 출력
for(String str : strList)
	System.out.println(str);

// forEach() 를 이용하여 컬렉션의 요소를 출력
stream.forEach(System.out::println);
// System.out::println == (str -> System.out.println(str))

forEach()는 메서드 안으로 for문을 넣은 것이다. 수행할 작업에 쓰일 변수는 매개변수로 받는다.

스트림의 연산

스트림이 제공하는 다양한 연산을 이용해서 복잡한 작업들을 간단하게 처리할 수 있다.

스트림이 제공하는 연산은 중간 연산 최종 연산 으로 분류 할 수 있는데,
중간 연산은 연산결과를 스트림으로 반환하기 때문에 중간 연산을 연속해서 사용할 수 있다. 최종 연산은 스트림의 요소를 소모하면서 연산을 수행하므로 단 한번만 연산이 가능하다.

중간 연산 목록

최종 연산 목록

스트림 사용

스트림 만들기

스트림을 사용하려면 스트림을 생성해야된다.


컬렉션

컬렉션의 최고 조상인 CollectionsStream() 이 정의 되어있다.
그래서 Collections에 자손인 List 와 Set을 구현한 컬렉션 클래스들은 모두 Stream() 사용 가능하다

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

forEach() 를 두번 사용했을 경우 어떻게 되는지 확인해보자.

public class Main {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
        Stream<Integer> stream = list.stream();
        stream.forEach(str -> System.out.print(str));
        stream.forEach(str -> System.out.print(str)); //ERROR 스트림이 이미 닫혔음.
    }
}

forEach()는 스트림의 요소를 소모하면서 작업을 수행하므로 같은 스트림에 두 번 이상 호출될 수 없다.


배열

배열을 소스로 하는 스트림을 생성하는 메서드는 StreamArrays 클래스에 메서드로 정의되어있다.

Stream<T> stream.of(T... values) // 가변인자
Stream<T> stream.of(T[])
Stream<T> Arrays.stream(T[])
Stream<T> Arrays.stream(T[] array, int startInclusive, int endExclusive)

정수형 배열을 갖는 스트림을 생성해보자!

public class Main {
    public static void main(String[] args) {
        Stream<Integer> integerStream = Stream.of(1, 2, 3, 4);
        Stream<int[]> stream = Stream.of(new int[]{1, 2, 3, 4});
        IntStream stream1 = Arrays.stream(new int[]{1, 2, 3, 4});
        IntStream stream2 = Arrays.stream(new int[]{1, 2, 3, 4}, 0, 3);
    }
}

문자열 배열을 갖는 스트림을 생성해보자!

public class Main {
    public static void main(String[] args) {
        Stream<String> strStream = stream.of("a","b","c");
        Stream<String> strStream = stream.of(new String[] {"a","b","c"})
        Stream<String> strStream = Arrays.stream(new String[] {"a","b","c"})
        Stream<String> strStream = Arrays.stream(new String[] {"a","b","c"}, 0, 3)
    }
}

특정 범위의 정수

IntStreamLongStream은 지정된 범위의 연속된 정수를 스트림으로 생성하여 반환하는,
range()rangeClosed()를 가지고 있다.

IntStream IntStream.range(int begin, int end)
IntStream IntStream.rangeClose(int begin, int end)
IntStream IntStream.range(1, 5)     // 1,2,3,4
IntStream IntStream.rangeClose(1,5) // 1,2,3,4,5

int보다 큰 범위의 스트림을 생성하려면 LongStream에 있는 range() 메서드를 사용하면 된다.


임의의 수

난수를 생성하는 Random 클래스에는 아래와 같은 인스턴스 메서드들이 포함되어있다.

IntStream ints()
LongStream longs()
DoubleStream doubles()

이 메서드들이 반환하는 스트림은 크기가 정해져있지 않은 ‘무한 스트림’ 이므로 limit() 연산자를 이용하여 크기를 제한.

public class Main {
    public static void main(String[] args) {
        IntStream ints = new Random().ints();  //무한스트림
        ints.limit(5).forEach(System.out::println); //5개의 요소만 출력
    }
}

limit()을 사용하지 않고 메서드의 매개변수를 이용하여 스트림의 크기를 지정가능하다.

IntStream ints(long streamSize)
LongStream longs(long streamSize)
DoubleStream doubles(long streamSize)

지정된 범위의 난수를 발생시키는 스트림을 얻는 메서드 -> 무한 스트림

IntStream ints(int begin, int end)
LongStream longs(long begin, long end)
DoubleStream doubles(double begin, long end)

스트림 중간연산

skip(), limit()

skip()limit()는 스트림의 일부를 잘라날때 사용한다.

  • skip(3) = 처음 3개의 요소를 건너뛴다.
  • limit(5) = 스트림의 요소를 5개로 제한한다.
public class Main {
    public static void main(String[] args) {
        IntStream intStream = IntStream.rangeClosed(1, 10); // 1부터 ~ 10 까지 난수를 생성하여 스트림으로반환
        intStream.skip(3).limit(5).forEach(System.out::print); // 3개의 요소를 건너뛰고 요소를 5개로 제한
    }
}


filter(), distinct()

filter()distinct() 는 스트림의 요소를 걸러낼때 사용한다.

  • filter() = 주어진 조건(Pridicate)에 맞지 않은 요소를 걸러낸다.
  • distinct = 스트림에서 중복요소를 제거한다.
public class Main {
    public static void main(String[] args) {
        IntStream intStream = IntStream.of(1, 2, 2, 5, 3, 3, 4, 5, 6);
        intStream.distinct().forEach(System.out::print);
    }
}

filter()는 매개변수로 Predicate를 필요로 하는데 연산결과가 boolean일땐 람다식을 사용가능하다.

public class Main {
    public static void main(String[] args) {
        IntStream intStream = IntStream.rangeClosed(1, 10);
        intStream.filter(i -> i % 2 == 0).forEach(System.out::print);
    }
}


sorted()

sorted() 는 스트림을 정렬할때 사용한다.

public class Main {
    public static void main(String[] args) {
        Stream<String> strStream = Stream.of("aa", "bb", "dd", "ff", "CC");
        strStream.sorted().forEach(System.out::print);
    }
}


map()

map() 은 스트림의 요소에 저장된 값 중에서 원하는 필드만을 뽑아내거나 특정한 형태로 변환해야 할 경우 사용한다.

배열을 소스로 하는 문자열 스트림을 생성하고 map() 함수를 이용하여 요소의 길이만 가져와 출력하는 코드

public class Main {
    public static void main(String[] args) {
        Stream<String> stringStream = Stream.of("a", "ab","abc","abcd");
        stringStream.map(String :: length).forEach(System.out::println);
    }
}

map() 도 중간연산자 이므로 하나의 스트림에 여러번 중복 적용할 수 있다.

public class Main {
    public static void main(String[] args) {
        Stream<String> stringStream = Stream.of("a", "ab","abc","abcd");
        stringStream.map(String :: length).map(i -> Integer.reverse(i)).forEach(System.out::println);
    }
}

mapToInt(), mapToLong(), mapToDouble()

map()은 연산결과로 Stream<T> 타입의 스트림을 반환한다.
스트림의 요소를 숫자로 변환하는경우 IntStream 과 같은 기본형 스트림으로 변환하는것이 더 유용할 수 있다. count() 만 지원하는 Stream<T>와 달리 intStream과 같은 기본형 스트림은 편리한 메서드를 제공한다.

int sum()
OptionalDouble average()
OptionalInt max()
OptionalInt min()

❗ 이메서드들은 최종 연산 이므로 호출 후 스트림이 닫히는 점을 주의해야한다.


flatMap()

스트림의 요소가 배열이거나 map()의 연산결과가 배열인 경우 사용한다.

public class Main {
    public static void main(String[] args) {
        Stream<String[]> stream = Stream.of(
            new String[]{"abc", "def", "ghi"},
            new String[]{"ABC", "DEF", "GHI"}
        );
        Stream<Stream<String>> streamStream = stream.map(i -> Arrays.stream(i));
        // 위 코드와 같은 코드 Stream<Stream<String>> streamStream1 = stream.map(Arrays::stream);
    }
}

위 코드는 Stream<Stream<String>>을 반환한다 즉, 스트림의 스트림인 것이다. 우리가 원하는 결과는 이것이 아니다.

flatMap() 이용하자!!

Stream<String> stringStream = stream.flatMap(Arrays::stream);

peek()

forEach() 를 사용하지않고 연산이 결과를 중간에 확인하고 싶다면 peek() 을 사용한다. forEach() 와 달리 스트림의 요소를 소모하지 않는다.

public class Main {
    public static void main(String[] args) {
        Stream<String> stringStream = Stream.of("a", "ab","abc","abcd");
        stringStream.filter(i -> i.length() > 1).peek(System.out::println).map(String :: length).forEach(System.out::println);
    }
}

스트림 최종연산

forEach()

forEach()peek() 과달리 스트림의 요소를 소모하는 최종연산이다.
반환 타입이 void이므로 스트림의 요소를 출력하는 용도로 많이 사용된다.


allMatch(),anyMatch(),noneMatch(),findFirst(),findAny()

스트림 요소에 대해 지정된 조건에 요소가 일치하는지 확인할때 사용한다. 연산결과로 boolean을 반환한다.

boolean noFailed = stuStream.anyMatch ( s -> s.getTotalScore() <= 100)

count(), sum(), aeverage(), max(), min()

reduce()

태그:

카테고리:

업데이트:

댓글남기기