JAVA 스트림
자바의정석 책을 참고하여
스트림에 대해 공부한것을 기록하기 위해 작성하였습니다.
스트림이란?
많은 수의 데이터를 다룰때 컬렉션이나 배열에 데이터를 담고 원하는 결과를 얻기 위하여 우리는 지금까지,
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
문을 넣은 것이다. 수행할 작업에 쓰일 변수는 매개변수로 받는다.
스트림의 연산
스트림이 제공하는 다양한 연산을 이용해서 복잡한 작업들을 간단하게 처리할 수 있다.
스트림이 제공하는 연산은 중간 연산
최종 연산
으로 분류 할 수 있는데,
중간 연산
은 연산결과를 스트림으로 반환하기 때문에 중간 연산을 연속해서 사용할 수 있다.
최종 연산
은 스트림의 요소를 소모하면서 연산을 수행하므로 단 한번만 연산이 가능하다.
중간 연산 목록
최종 연산 목록
스트림 사용
스트림 만들기
스트림을 사용하려면 스트림을 생성해야된다.
컬렉션
컬렉션의 최고 조상인 Collections
에 Stream()
이 정의 되어있다.
그래서 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()
는 스트림의 요소를 소모하면서 작업을 수행하므로 같은 스트림에 두 번 이상 호출될 수 없다.
배열
배열을 소스로 하는 스트림을 생성하는 메서드는 Stream
과 Arrays
클래스에 메서드로 정의되어있다.
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)
}
}
특정 범위의 정수
IntStream
과 LongStream
은 지정된 범위의 연속된 정수를 스트림으로 생성하여 반환하는,
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)
댓글남기기