728x90
클린코드 8기 과정을 들으면서 필요한 내용을 정리한 글입니다.
Stream
- Data Structure가 아니다
- stream을 데이터를 store할 수 없다
Stream 생성
Stream.of
.stream()
Stream.Builder
- 이미 존재하는 Array, Object, List를 stream으로 생성
Employee[] arrayOfEmps = {
new Employee(1, "Jeff Bezos", 100000.0),
new Employee(2, "Bill Gates", 200000.0),
new Employee(3, "Mark Zuckerberg", 300000.0)
};
Stream.of(arrayOfEmps);
List<Employee> empList = Arrays.asList(arrayOfEmps);
empList.stream();
Stream.of(arrayOfEmps[0], arrayOfEmps[1], arrayOfEmps[2]);
Stream.Builder<Employee> empStreamBuilder = Stream.builder();
empStreamBuilder.accept(arrayOfEmps[0]);
empStreamBuilder.accept(arrayOfEmps[1]);
empStreamBuilder.accept(arrayOfEmps[2]);
Stream<Employee> empStream = empStreamBuilder.build();
Stream Operation
forEach
- terminal operation
- void 리턴
empList.stream().forEach(e -> e.salaryIncrement(10.0));
map
- 새로운 stream 리턴
List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
collect
- mutable fold operation
- 다른 자료 구조로 element를 repacking
- 추가적인 로직, concat 가능
- Collectors 인터페이스를 implement하여 사용하는 것이 좋다
List<Employee> employees = empList.stream().collect(Collectors.toList());
filter
- Predicate 사용
- true일 경우 새로운 배열에 해당 element 포함
List<Employee> employees = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
findFirst
- Optional 리턴
Employee employee = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 100000)
.findFirst()
.orElse(null);
toArray
- Array 반환
- collect()는 List 등 자료 구조 반환
Employee[]::new
creates an empty array of Employee
Employee[] employees = empList.stream().toArray(Employee[]::new);
flatMap
- stream은 Stream<List
>과 같은 복잡한 데이터 구조를 가질 수 있다 - flatMap은 위와 같은 복잡한 데이터 구조를 간단하게 만들어 준다
- 아래 코드는
List<List<String>>
를List<String>
으로 만들어 준다
List<List<String>> namesNested = Arrays.asList(
Arrays.asList("Jeff", "Bezos"),
Arrays.asList("Bill", "Gates"),
Arrays.asList("Mark", "Zuckerberg"));
List<String> namesFlatStream = namesNested.stream()
.flatMap(Collection::stream)
.collect(Collectors.toList());
🔥peek
- 각 element에 특정 연산을 수행
- 새로운 stream 리턴
- intermediate operation
empList.stream()
.peek(e -> e.salaryIncrement(10.0))
.peek(System.out::println)
.collect(Collectors.toList());
Method Type & Pipeline
Stream은 두 가지 operation으로 나누어 진다
- Intermediate (stream 리턴)
- terminal (Object 리턴)
stream pipeline은 stream source로 구성되어 있다
몇몇 operation은 short-circuiting operations로 간주된다
short-circuiting operations
- 무한의 stream을 유한한 시간에서 계산
Stream<Integer> infiniteStream = Stream.iterate(2, i -> i * 2);
List<Integer> collect = infiniteStream
.skip(3)
.limit(5)
.collect(Collectors.toList());
skip(3)
- 첫번째 3개의 element를 skip
limit(5)
- interator로 만들어진 무한한 stream으로 부터 5개의 element로 제한
Stream.iterate(2, i -> i * 2); // 무한대
.skip(3) // 초기 3개 skip
.limit(3) // 5개로 제한
Lazy Evaluation
- lazy evaluation으로 최적화 할 수 있다.
- 한번의 루프를 돌 때, 만족하는 모든 intermediate opration을 적용한다
- 소스 데이터를 계산하는 것은 오직 forEach같은 terminal operation이 초기화 되었을 때 실행된다
- 소스 코드는 오직 필요할 때만 사용된다
- 모든 peek, filter와 같은 intermediate operations는 lazy하다
- 그래서 계산의 결과가 실제로 사용할 때까지 실행되지 않는다
- 여기서 map이 몇번 수행 될까?
- 4번 수행된다
- 왜 4번 수행 될까?
Employee employee = Stream.of(empIds)
.map(employeeRepository::findById)
.filter(e -> e != null)
.filter(e -> e.getSalary() > 100000)
.findFirst()
.orElse(null);
- stream은 map과 두개의 filter를 하나의 element당 한번 수행한다.
- 시작 이후, 1인 id에 대해 모든 operation을 수행한다.
- id가 1인 salary는 100000을 넘지 않는다
- 그러므로 id = 2로 넘어간다
- id = 2
- id가 2인 경우 모든 filter를 만족한다
- 그리고 terminal operation인 findFirst()를 수행하고 return을 반환한다
- id = 3, id = 4
- id = 2에서 terminate되었으므로 id = 3, id =4에 대해서는 수행하지 않는다
- 이렇게 lazy하게
Stream은 terminate operation(forEach, findFirst)을 만나기 전까지는 각 element에 대한 모든 intermediate operation(peek, filter)를 거치며 lazy하게 순회한다. 이를 Lazy Evaluation이라고 한다.
Stream operation 기반의 비교
sorted
- 주어진 comparator를 기반으로 element를 정렬
- short circuiting은 sorted()에는 적용되지 않는다
- 예를 들어 sorted() 다음에 findFirst()가 사용 되면, findFirst()가 실행되기 전에 sorted()이 모두 완료된다
- 왜냐하면 findFirst()는 sorted()가 완료 되기 전까지 어느 element가 첫번째 인지 모르기 때문이다
List<Employee> employees = empList.stream()
.sorted((e1, e2) -> e1.getName().compareTo(e2.getName()))
.collect(Collectors.toList());
min & max
- comparator를 기반으로 최소, 최대 element를 리턴
- Optional을 리턴
- 결과가 있을지 없을지 모르기 때문이다
Employee firstEmp = empList.stream()
.min((e1, e2) -> e1.getId() - e2.getId())
.orElseThrow(NoSuchElementException::new);
- Comparator.comparing()을 사용하여 comparison 로직을 정의하는 것을 피할 수 있다
Employee maxSalEmp = empList.stream()
.max(Comparator.comparing(Employee::getSalary))
.orElseThrow(NoSuchElementException::new);
distinct
- argument 없는 메소드
- 중복인 element를 제외한 결과 리턴
- 내부적으로 equals() 사용
List<Integer> distinctIntList = intList.stream().distinct().collect(Collectors.toList());
allMatch, anyMatch, noneMatch
- predicate 사용
- boolean 리턴
- Short-circuiting 적용
- 답이 결정되면 연산은 멈춘다
boolean allEven = intList.stream().allMatch(i -> i % 2 == 0);
boolean oneEven = intList.stream().anyMatch(i -> i % 2 == 0);
boolean noneMultipleOfThree = intList.stream().noneMatch(i -> i % 3 == 0);
allMatch
- 모든 elements가 true일 경우
anyMatch
- 하나의 element라도 true인 경우
- true값이 나오자 마자 loop을 빠져나온다
noneMatch
- element가 하나도 매칭 안될 경우
Stream Specializations
- Stream은 Object 참조의 stream이다
- 하지만 intStream, LongStream, DoubleStream과 같이 원시 Stream도 존재한다
- 이들은 Stream을 상속받지 않는다
- BaseStream을 상속
Creation
// intStream
stream().mapToInt()
IntStream.range(10,20)
// Stream<Integer
IntStream.of(1,2,3)
stream().map(new Int[])
- Stream객체의 mapToInt()
intStream 반환
empList.stream().mapToInt(Employee::getId)
IntStream.range(10, 20)
Stream
IntStream.of(1,2,3)
stream().map(new Int[])
Specialized Operations
- Specialized Stream은 기존의 Stream에 추가적인 기능을 제공한다
- 특히, 숫자를 다룰 때 편리하다
- sum(), average(), range()
empList.stream()
.mapToDouble(Employee::getSalary)
.average()
Reduction Operation
- fold라고도 불린다
- 연속된 element을 결합하여 하나의 결과를 생성한다
- findFirst(), min(), max()
reduce()
T reduce(T identity, BinaryOperator<T> accumulator)
identity
- 시작값
accumulator
- binary operation
- 계속 여기에 apply한다
Double sumSal = empList.stream()
.map(Employee::getSalary)
.reduce(0.0, Double::sum);
- 시작값 : 0.0
- 각 element에 Double::sum()을 계속 apply한다
DoubleStream.sum()
과 같은 효과
Advanced collect
- Collectors.toList()
Collectors.joining
- 두 String 사이에 delimiter를 삽입한다
- 내부적으로 StringJoiner를 사용한다
String empNames = empList.stream()
.map(Employee::getName)
.collect(Collectors.joining(", "))
.toString();
Collectors.toSet
- Set을 리턴
Set<String> empNames = empList.stream()
.map(Employee::getName)
.collect(Collectors.toSet());
Collectors.toCollection
- element들을 추출하여 다른 컬렉션을 생성
- Supplier
을 사용한다 - 내부적으로 empty collection이 생성된다
- add() 메소드가 각 element에 호출된다.
Vector<String> empNames = empList.stream()
.map(Employee::getName)
.collect(Collectors.toCollection(Vector::new));
summarizing Double
- 각 element에 double-producing mapping을 apply한다
- statical 정보를 가진 class를 반환한다
- DoubleSummaryStatistics
- 결과값의 min, max, average
DoubleSummaryStatistics stats = empList.stream()
.collect(Collectors.summarizingDouble(Employee::getSalary));
DoubleSummaryStatistics stats = empList.stream()
.mapToDouble(Employee::getSalary)
.summaryStatistics();
partitioningBy
- stream을 2개로 나눌 수 있다
- Map으로 key와 value 2개로 나눠진다
- 아래는 숫자를 홀수와 짝수로 나누었다
- { false , 5 }
- { true , [2, 4, 6, 8] }
List<Integer> intList = Arrays.asList(2, 4, 5, 6, 8);
Map<Boolean, List<Integer>> isEven = intList.stream().collect(
Collectors.partitioningBy(i -> i % 2 == 0));
groupingBy
- advance된 나누기
- stream을 2개 이상의 그룹으로 나눈다
- 반환값은 key가 된다
- element는 value가 된다
- 파라미터로 classification 함수를 받는다
- 각 element에 apply된다
Map<Character, List<Employee>> groupByAlphabet = empList.stream().collect(
Collectors.groupingBy(e -> new Character(e.getName().charAt(0))));
mapping
- collocter를 받아 다른 타입으로 반환
- Map<String, List
>라면 - value인 List
를 다른 Collector로 변환한다
- Map<String, List
Map<Character, List<Integer>> idGroupedByAlphabet = empList.stream().collect(
Collectors.groupingBy(e -> new Character(e.getName().charAt(0)),
Collectors.mapping(Employee::getId, Collectors.toList())));
- element인 Employee를 employee id로 매핑했다
- getId()를 매핑 함수로 사용했다
- 이 id는 여전히 employ의 first name의 첫 글자로 group되어 있다
Parallel Streams
- 다른 코드를 추가로 입력하는 것 없이 병렬적으로 stream을 사용할 수 있다
empList.stream().parallel().forEach(e -> e.salaryIncrement(10.0));
salaryIncrement()
- parallel() syntax를 더하여, 복수의 element들을 병렬적으로 실행 시킨다
728x90