Skip to content

Commit

Permalink
[#45][B팀]스트림은 주의해서 사용하라
Browse files Browse the repository at this point in the history
[#45][B팀]스트림은 주의해서 사용하라
  • Loading branch information
ksy90101 authored Feb 28, 2021
2 parents 368d31f + 51db34c commit e1d9de3
Showing 1 changed file with 169 additions and 0 deletions.
169 changes: 169 additions & 0 deletions 7장/45_스트림은_주의해서_사용하라_박소정.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
# 스트림은 주의해서 사용하라

---

스트림은 다량의 데이터 처리 작업을 돕고자 java 8 에 추가되었다.

## 💡스트림의 핵심 개념 2가지

- 데이터 원소의 무한 또는 유한 시퀀스(순서있는 나열)
- 스트림 파이프 라인 : 원소들로 수행하는 연산 단계

## 스트림 파이프 라인

- 소스 스트림 - 중간 연산(스트림을 변환 ex: 조건, 함수 적용) - 종단 연산
- 지연 평가(lazy evaluation) : 종단 연산이 수행될 때 평가된다 - 종단 연산이 수행되지 않으면 아무일도 일어나지 않는다.
- 일단 한 값을 다른 값에 매핑하고 나면 원래의 값은 잃는 구조

## 지연 평가(lazy evaluation)

그때 그때 값을 평가하지 않고, 정말 결과값이 필요한 시점까지 평가를 미루는 것

1. 필요할 때만 평가가 되므로 메모리를 효율적으로 사용할 수 있다.
2. 무한 자료구조를 만들 수 있음
3. 런타임 에러를 방지 할 수 있다. ( 컴파일 시 에러를 체킹)
4. 컴파일러 최적화 가능

[지연 평가 링크](http://filimanjaro.com/blog/2014/introducing-lazy-evaluation/)

```java
public static void main(String[] args) {
List<Integer> numList = Arrays.asList(10, 20, 30, 40, 50, 60);
System.out.println(numList.stream()
.filter(num -> {
System.out.println("num < 50");
return num < 50;
})
.filter(num ->{
System.out.println("num < 30");
return num < 30;
})
.map(num ->{
System.out.println("num * 100");
return num * 100;
})
.collect(Collectors.toList())
);
}
```

```java
/**
num < 50
num < 30
num * 100
num < 50
num < 30
num * 100
num < 50
num < 30
num < 50
num < 30
num < 50
num < 50
[1000, 2000]
**/
```

## 반복문의 장점(코드 블럭 사용의 장점)

스트림 → 함수 객체 사용

반복문 → 코드 블록 사용

- 범위 안의 지역 변수를 읽고 수정이 가능
- break, continue를 활용하여 반복문을 종료, 반복을 뛰어 넘을 수 있음
- 메서드 선언에 예외 처리가 가능함

## 스트림을 사용을 추천하는 경우

- 원소들의 시퀀스를 일관되게 변환하는 경우
- 원소들의 시퀀스를 필터링하는 경우
- 원소들의 시퀀스를 하나의 연산을 사용해 결합하는 경우
- 원소들의 시퀀스를 컬렉션에 모으는 경우(공통된 속성을 기준으로)
- 원소들의 시퀀스에서 특정 조건을 만족하는 원소를 찾는 경우

---

## 스트림을 사용하여 가독성이 높아진 예제

```java
public static void main(String[] args) {
File dectionary = new File(args[0]);
int minGroupSize = Integer.parseInt(args[1]);

Map<String, Set<String>> groups = new HashMap<>();
try (Scanner s = new Scanner(dectionary)) {
while(s.hasNext()) {
String word = s.next();
groups.computeIfAbsent(alphabetize(word),
(unused) -> new TreeSet<>()).add(word);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
}

for(Set<String> group : groups.values()) {
if(group.size() >= minGroupSize) {
System.out.println(group.size() + ": " + group);
}
}
}

private static String alphabetize(String s) {
char[] a = s.toCharArray();
Arrays.sort(a);
return new String(a);
}
```

- computeIfAbsent 함수: 맵 안에 키가 있는지 찾은 후, 있으면 단순히 키가 매핑된 값을 반환한다.

```java
public static void main(String[] args) throws IOException {
File dectionary = new File(args[0]);
int minGroupSize = Integer.parseInt(args[1]);

try(Stream<String> words = Files.lines(dectionary.toPath())) {
words.collect(groupingBy(word -> alphabetize(word)))
.values().stream()
.filter(group -> group.size() >= minGroupSize)
.forEach(group -> System.out.println(group.size() + ": " + group));
}
}
```

😡스트림을 과용하면 프로그램을 읽거나 유지보수하는 것이 어려워 진다.

💡람다 사용시 매개변수의 변수를 잘 지어야 스트림 파이프 라인의 가독성이 올라간다.

---

## 스트림과 반복 중 어떤 쪽을 써야할지 알기 어려운 경우

데카르트 곱 : 가능한 모든 조합을 구하는 경우

```java
private static List<Card> newDeck() {
List<Card> result = new ArrayList<>();
for(Suit suit : Suit.values())
for(Rank rank : Rank.values())
result.add(new Card(suit, rank));
return result;
}
```

```java
private static List<Card> newDeck() {
return Stream.of(Suit.values())
.flatMap(suit -> Stream.of(Rank.values())
.map(rank -> new Card(suit, rank)))
.collect(toList());
}
```

두 가지 경우가 있는데, 제 기준 반복을 활용한 코드가 더 가독성이 높았습니다.

---

## 🤷‍♀️Stream과 반복중 어느 쪽이 더 유리한지 확신이 서지 않을 경우, 둘 다 해보고 더 나은 쪽을 선택하기!

0 comments on commit e1d9de3

Please sign in to comment.