컬렉션과 스트림의 차이
- 데이터를 언제 계산 하느냐가 컬렉션과 스트림의 가장 큰 차이이다.
컬렉션
- 컬렉션은 현재 자료구조가 포함하는 모든 값을 메모리에 저장하는 자료구조이다.
- 즉, 컬렉션의 모든 요소는 컬렉션에 추가하기전에 계산되어야 한다. 컬렉션은 요소를 추가하거나 컬렉션의 요소르 삭제할 수 있다.
- 모든 값을 계산할 때까지 기다린다는 의미
스트림
- 반면 스트림은 이론적으로 요청할 때만 요소를 계산하는 고정된 자료구조이다.이것을 다시말하자면 스트림에 요소를 추가하거나 요소를 제거할 수 없다.
- 필요할 때만 값을 계산한다는 의미
UnsupportedOpertationException
컬렉션과 스트림의 차이를 말하였다.
그 중에 하나인 컬렉션은 요소를 추가하거나 삭제할 수 있지만, 스트림은 요소를 갱신할 수 있지만 추가하거나 삭제할 수 없다.. 만약 요소를 추가하려 하면 UnsupportedOpertationException 예외가 발생한다.
스트림은 게으르게 만들어지는 컬렉션과 같다. 사용자가 데이터를 요청할 때만 값을 계산한다
-🎈재밌게 읽어본 모던 인 액션 자바라는 책의 문장🎈-
@SpringBootTest
public class StreamTest {
public static void main(String[] args) {
List<String> language = Arrays.asList("자바8", "자바스크립트", "C언어");
Stream<String> stream = language.stream();
stream.forEach(System.out::println);
stream.forEach(System.out::println); // <- 재사용 안된다.
}
}
자바8
자바스크립트
C언어
Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed
at java.base/java.util.stream.AbstractPipeline.sourceStageSpliterator(AbstractPipeline.java:279)
at java.base/java.util.stream.ReferencePipeline$Head.forEach(ReferencePipeline.java:658)
at com.spring.jpadata.StreamTest.main(StreamTest.java:18)
package com.spring.jpadata.dto.lamda;
import lombok.Builder;
import lombok.Getter;
@Getter
public class Dish {
private String name;
private boolean vegetarian;
private int calories;
private Type type;
@Builder
public Dish(String name, boolean vegetarian, int calories, Type type) {
this.name = name;
this.vegetarian = vegetarian;
this.calories = calories;
this.type = type;
}
public enum Type{ MEAT, FISH , OTHER}
}
다음의 아래의 코드는 Stream을 사용하여 만든 코드이고
1. 300 칼로리 초과하는 음식
2. 음식의 이름으로 추출
3. 선착순 3개 라는 요구사항이 주어지는 코드이다.
package com.spring.jpadata.dto.lamda;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class doLamda {
public static void main(String[] args) {
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french", true, 530, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("salmon", false, 450, Dish.Type.FISH)
);
List<String> threeHighCalDish = menu.stream()
.filter(dish -> dish.getCalories() > 300)
.map(Dish::getName)
.limit(3)
.collect(Collectors.toList());
System.out.println(threeHighCalDish);
}
}
- 위의 코드에서 collect를 제외한 모든 연산은 서로 파이프라인을 형성할 수 있도록 스트림을 반환하는 메소드이다.
- collect : 스트림을 다른 형식으로 변환하는 메소드이다. 현재로서는 특정 결과로 변환시키는 기능을 수행하는 정도라고 하겠다. 파이프라인을 실행한 다음에 닫는다.
연산 | 형식 | 반환 형식 | 연산의 인수 | 함수 디스크럽터 |
---|---|---|---|---|
filter | 중간 연산 | Stream | Predicate | T -> boolean |
map | 중간 연산 | Stream | Function | T -> R |
limit | 중간 연산 | Stream | ||
sorted | 중간 연산 | Stream | Comparator | (T,T) -> int |
distinct | 중간 연산 | Stream | 고유값 필터링 |
연산 | 형식 | 반환 형식 | 목적 |
---|---|---|---|
foreach | 최종 연산 | void | 스트림의 각 요소를 소비하면서 람다를 적용한다 |
count | 최종 연산 | Long | 스트림의 요소 개수를 반환한다 |
collect | 최종 연산 | void | 스트림을 리듀스해서 리스트,맵,정수 형식의 컬렉션을 만들어준다. |
public static void main(String[] args) {
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french", true, 530, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("salmon", false, 450, Dish.Type.FISH)
);
List<Dish> collect = menu.stream()
.filter(Dish::isVegetarian) // <= 채식 요리인지 확인하는 메서드이다.
.collect(toList());
System.out.println("collect = " + collect);
}
스트림은 고유 요소로 이루어진 스트림을 반환하는 distinct 메서드를 지원한다.
고유 여부는 스트림에서 만든 객체의 hashCode, equals로 결정된다.
- 따라서 커스텀 클래스를 제작했을 시 hashCode와 equals를 재정의 해주지 않았다면 distict는 정상적으로 작동하지 않는다.
public static void main(String[] args) {
List<Integer> numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4);
numbers.stream()
.filter(i -> i % 2 == 0)
.distinct() // -> 고유 요소로 필터링 해주는 메서드
.forEach(System.out::println);
}
- filter는 조건에 대해 다 검사하며 참인것만 다음으로 넘어간다
- takeWhile은 조건에 대해 참이 아닐경우 바로 거기서 멈추게 된다
//filter 이용시
Stream.of(1,2,3,4,5,6,7,8,9)
.filter(n -> n%2 == 0)
.forEach(System.out::println);
//takeWhile 이용시
Stream.of(2,4,3,4,5,6,7,8,9)
.takeWhile(n -> n%2 == 0)
.forEach(System.out::println)
filter 이용시
2
4
6
8
takeWhile 이용시
2
4
앞서 Dish라는 객체에는 칼로리라는 필드가 존재했다. reduce(영어 번역으로는 : 줄이다 라는 뜻)를 사용하여 총 칼로리를 계산해 보갰다
public static void main(String[] args) {
List<Dish> menu = Arrays.asList(
new Dish("pork", false, 800, Dish.Type.MEAT),
new Dish("beef", false, 700, Dish.Type.MEAT),
new Dish("chicken", false, 400, Dish.Type.MEAT),
new Dish("french", true, 530, Dish.Type.OTHER),
new Dish("pizza", true, 550, Dish.Type.OTHER),
new Dish("salmon", false, 450, Dish.Type.FISH)
);
Integer sumOfCalories = menu.stream()
.map(Dish::getCalories)
.reduce((a, b) -> a + b).get();
// reduce를 사용하면 Optional로 반환됨
System.out.println("sumOfCalories = " + sumOfCalories);
}
reduce()는 인자로 BinaryOperator 객체를 받는데, BinaryOperator는 T 타입의 인자 두개를 받고 T 타입의 객체를 리턴하는 함수형 인터페이스이다. BinaryOperator는 (total, n) -> total + n와 같은 형식으로 인자를 받아서 처리해준다.
리턴타입 void
public static void main(String[] args) {
//student : 이름 / 점수
List<Student> list = Arrays.asList(
new Student("홍길동",82),
new Student("김남준",90),
new Student("주몽",100),
new Student("박혁거세",80),
new Student("온조",70)
);
list.stream().forEach(
student ->{
String name = student.getName();
int age = student.getAge();
System.out.println("이름 : " + name + "나이 : "+age);
}
);
}
출력문 🔽
이름 : 홍길동 나이 : 82
이름 : 김남준 나이 : 90
이름 : 주몽 나이 : 100
이름 : 박혁거세 나이 : 80
이름 : 온조 나이 : 70
- 다음 장에 계속됩니다 JAVA 8 Stream 2