스트림은 컬렉션에 저장된 요소들을 하나씩 순회 하면서 함수형 프로그래밍 방식으로 처리할 수 있는 기능이다.
람다식과 함께 사용할 수 있으며 컬렉션에 들어있는 데이터에 대한 처리를 간결하게 표현할 수 있다.
스트림이 필요한 이유는 다음과 같다.
우리는 스트림은 크게 생성, 가공, 결과 3가지로 구분할 수 있다.
자바에서는 자주 사용하는 배열과 컬렉션 객체에서 stream() 메소드를 지원한다. 우리는 stream() 메소드를 사용하여 스트림을 생성할 수 있다.
public class Application1 {
public static void main(String[] args) {
String[] sarr = new String[] {"java", "oracle", "jdbc"};
//스트림은 사용할 때마다 선언해주어야 한다.
Stream<String> strStream1 = Arrays.stream(sarr); //배열 스트림 생성
strStream1.forEach(System.out::println); //스트림을 순회하여 요소들을 출력
Stream<String> strStream2 = Arrays.stream(sarr, 0, 2); //0~1까지 배열 스트림 생성
strStream2.forEach(System.out::println);
/* 컬렉션 스트림 생성 */
List<String> stringList = Arrays.asList("html", "css", "javascript");
Stream<String> strStream3 = stringList.stream();
strStream3.forEach(System.out::println);
/* 컬렉션의 경우 스트림 생성을 생략하고 사용할 수 있다. */
stringList.forEach(System.out::println);
}
스트림에 forEach() 메소드를 사용하면 스트림 안에 있는 요소들을 순회하며 출력 해준다.
또한 컬렉션의 경우엔 스트림을 생성하지 않고 forEach()를 사용할 수 있다.
range() 는 range(시작 값, 종료 값) 형태로 사용할 수 있으며, 시작 값부터 1씩 증가하는 숫자로 종료 값 전까지 범위의 스트림을 생성 할 수 있다.
rangeClosed 은 rangeClosed(시작 값, 종료 값) 형태로 사용할 수 있으며, 시작 값부터 1씩 증가하는 숫자로 종료 값까지 범위의 스트림을 생성할 수 있다.
public class Application2 {
public static void main(String[] args) {
IntStream intStream = IntStream.range(5, 10); //5부터 1씩 증가하여 9까지 생성
intStream.forEach(value -> System.out.print(value + " "));
System.out.println();
LongStream longStream = LongStream.rangeClosed(5, 10); //5부터 1씩 증가하여 10까지 생성
longStream.forEach(value -> System.out.print(value + " "));
System.out.println();
/* 문자열을 intStream으로 변환 */
IntStream helloworldChars = "hello world".chars(); //문자열이 모두 문자의 아스키코드 값으로 생성됨
helloworldChars.forEach(v -> System.out.print(v + " "));
System.out.println();
/* 문자열을 split하여 stream으로 생성 */
Stream<String> splitStream = Pattern.compile(", ").splitAsStream("html, css, javascript");
splitStream.forEach(System.out::println);
/* Stream.of()를 이용한 생성 */
//괄호 안에 있는 데이터를 스트림으로 생성
Stream<String> stringStream1 = Stream.of("java", "oracle", "jdbc");
Stream<String> stringStream2 = Stream.of("html", "css", "javascript");
// stringStream1.forEach(System.out::println);
// stringStream2.forEach(System.out::println);
/* concat()을 이용하여 두 개의 스트림을 동일 타입 스트림으로 합칠 수 있다.
* 스트림은 1회성으로 사용 가능하며 이미 열린 스트림은 재사용이 불가능하다 (위에꺼 주석 안하면 IllegalStateException발생함)
* */
Stream<String> concatStream = Stream.concat(stringStream1, stringStream2);
concatStream.forEach(v -> System.out.print(v + " "));
}
}
filter()는 스트림의 중계 연산 중 하나이다. 이는 스트림에서 특정 데이터만 걸러내는 메소드이다.
public class Application1 {
public static void main(String[] args) {
IntStream intStream = IntStream.range(0, 10); //0부터 9까지 스트림 생성
intStream.filter(i -> (i % 2) == 0).forEach(i -> System.out.print(i + " "));
//filter를 이용하여 조건을 준다.
//해당 조건에 일치하는 값만 출력할 수 있다.
//출력: 0 2 4 6 8
}
}
map()은 스트림에 들어있는 데이터를 특정 람다식을 통해 데이터를 가공하고 새로운 스트림에 담아 주는 역할을 한다.
public class Application2 {
public static void main(String[] args) {
IntStream intStream = IntStream.range(1, 10); //1부터 9까지 스트림 생성
intStream
.filter(i -> i % 2 == 0) //짝수만 필터링
.map(i -> i * 5) //map으로 각 요소에 접근 가능. 각 요소에 5씩 곱하겠다는 의미
.forEach(result -> System.out.print(result + " ")); //스트림의 각 요소를 result에 할당하고 출력하겠다는 의미
}
}
flatMap()은 중첩 구조를 한 단계 제거하고 단일 컬렉션으로 만들어준다. 이러한 작업을 플래트닝(flattening)이라고 한다.
public class Application3 {
public static void main(String[] args) {
List<List<String>> list = Arrays.asList( //리스트 안에 리스트가 들어가게 됨
Arrays.asList("JAVA", "SPRING", "SPRINGBOOT"), //내부리스트1
Arrays.asList("java", "spring", "springboot") //내부리스트2
);
System.out.println("list = " + list);
List<String> flatList = list.stream() //스트림 생성
.flatMap(Collection::stream) //중첩된 내부 리스트를 하나의 리스트로 만들어줌
.collect(Collectors.toList()); //그 결과 값을 다시 리스트 형태로 만들어줌
System.out.println("flatList = " + flatList);
}
}
sorted()는 인자가 없이도 호출이 가능한데, 인자가 없으면 오름차순으로 자동 정렬된다.
public class Application4 {
public static void main(String[] args) {
List<Integer> integerList = IntStream.of(5, 10, 99, 2, 1, 35) //스트림 생성
.boxed() //박싱을 이용해 int를 Integer로 바꿔줌
.sorted() //스트림의 요소를 오름차순으로 정렬
.collect(Collectors.toList()); //결과 값을 리스트 형태로 반환
System.out.println("integerList = " + integerList);
}
}
우리는 스트림에서 최소,최대,총합 등과 같은 연산을 메소드로 손쉽게 구할 수 있다.
public class Application1 {
public static void main(String[] args) {
long count = IntStream.range(1, 10).count(); //1~9까지 개수가 몇 개인지
long sum = IntStream.range(1, 10).sum(); //1~9까지 합이 몇 인지
System.out.println("count : " + count);
System.out.println("sum : " + sum);
//OptionalInt : 결과가 있을 수도 있고 없을 수도 있을 때 결과 값을 안전하게 받아올 수 있게 해준다.
OptionalInt max = IntStream.range(1, 10).max();
OptionalInt min = IntStream.range(1, 10).min();
System.out.println("max : " + max);
System.out.println("min : " + min);
int oddSum = IntStream
.range(1, 10) //1~9
.filter(i -> i % 2 == 1) //홀수만 필터링
.sum(); //필터링 된 결과를 모두 합해준다.
System.out.println("oddSum : " + oddSum);
}
}
스트림을 생성하고 바로 count(), sum(), max(), min() 등의 메소드를 같이 호출해주면 원하는 연산 결과 값을 바로 구할 수 있다.
위 코드에서 OptionalInt 는 int값이 존재하는 경우와 존재하지 않는 경우를 명확하게 구분할 수 있게 해주며, 이를 통해 더 안전하게 코드를 작성할 수 있게 도와준다.
reduce() 는 스트림에 있는 데이터들의 총합을 계산해준다.
public class Application2 {
public static void main(String[] args) {
OptionalInt reduceOneParam = IntStream.range(1, 4) // 1, 2, 3
.reduce((a, b) -> {
return Integer.sum(a, b);
});
System.out.println("reduceOneParam = " + reduceOneParam.getAsInt());
}
}
reduce()는 누적연산을 해주게 되는데,
예를 들어 1,2,3 이 들어있을 때, a에는 1, b에는 2가 들어가 3이 나오게 된다.
이때 나온 3이 다시 a로 들어가고 b에는 스트림에 있던 3이 들어가며 결과 값이 6이 나온다.
이런식으로 누적하여 합을 계산해 준다.
위 코드에서 getAsInt()는 OptionalInt 클래스에서 제공하는 메소드로,
OptionalInt로 인스턴스를 생성하여 값을 받아왔기 때문에 만약 OptionalInt 객체가 값을 가지고 있는 경우에 getAsInt() 메소드를 사용하면 그 값을 반환 시켜주게 된다.
collect()는 Collector 타입을 받아서 처리하는데, 해당 메소드를 통해 컬렉션을 출력으로 받을 수 있다.
public class Application3 {
public static void main(String[] args) {
List<Member> memberList = Arrays.asList( //3개의 멤버리스트 생성
new Member("test01", "testName01"),
new Member("test02", "testName02"),
new Member("test03", "testName03")
);
//getMemberName()은 Member 클래스에 정의한 메소드
List<String> collectorCollection = memberList
.stream() //스트림 생성
.map(Member::getMemberName) //Member 클래스에 있는 멤버 이름을 반환받는다
.collect(Collectors.toList()); //그 결과를 리스트로 다시 반환받는다.
System.out.println("collectorCollection = " + collectorCollection);
String str = memberList
.stream() //스트림 생성
.map(Member::getMemberName)
.collect(Collectors.joining()); //joining(): 구분자 없이 하나로 이어붙이겠다.
System.out.println("str = " + str);
String str2 = memberList
.stream()
.map(Member::getMemberName)
.collect(Collectors.joining(" || ", "**", "**")); // 구분자를 주고, 앞과 뒤에 접두사,접미사를 각각 붙여주겠다는 의미
System.out.println("str2 = " + str2);
}
위 코드에서 joining()은 결과 출력값을 구분자 없이 하나로 이어붙이는 메소드이다.
joining(" || ", "**", "**")) → 이렇게 매개변수를 넘겨줄 수도 있다.
구분자를 주고, 앞과 뒤에 접두사, 접미사를 각각 붙여주겠다는 의미이다.
match는 anyMatch(), allMatch(), noneMatch() 이렇게 3가지 종류가 있다.
결과로는 true, false 를 리턴한다.
public class Application4 {
public static void main(String[] args) {
List<String> stringList = Arrays.asList("Java", "Spring", "SpringBoot");
boolean anyMatch = stringList.stream()
.anyMatch(str -> str.contains("p")); //p를 포함하고 있는지
boolean allMatch = stringList.stream()
.allMatch(str -> str.length() > 4); //문자열의 길이가 모두 4인지
boolean noneMatch = stringList.stream()
.noneMatch(str -> str.contains("c")); //c를 포함하고 있는지
System.out.println("anyMatch = " + anyMatch); //true
System.out.println("allMatch = " + allMatch); //false
System.out.println("noneMatch = " + noneMatch); //true
}
}