> For the complete documentation index, see [llms.txt](https://docs.sysout.co.kr/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.sysout.co.kr/base-language/java/java-3/java-1.8/stream-api.md).

# Stream API

## Java Stream API

### Lambda Expression

기존에 간편하게 사용하던 람다식을 보면 다음과 같다.

> 람다식을 사용하지 않은 경우 스레드 구동 코드

```java
Thread t = new Thread(new Runnable(){ 
        @Override public void run(){ 
            System.out.println("hello"); 
        } 
    }); 
t.start();
```

> 람다식을 사용한 경우 스레드 구동 코드

```java
Thread t = new Thread(()->{ 
    System.out.println("hello"); 
}); 
t.start();
```

java.lang.Runnable 인터페이스는 메소드가 단 한개뿐인 인터페이스이므로 자리만 맞다면 클래스 이름이나, 메소드 이름, 반환형, 매개변수 타입 등은 굳이 적어주지 않아도 컴퓨터가 알 수 있는 정보들이 된다. 물론, **메소드가 단 한개** 일 경우에만 해당되며 두 개 이상일 경우에는 성립하지 않는다.

> java.lang.Runnable 인터페이스 형태

```java
public interface Runnable { 
    public abstract void run(); 
}
```

람다식을 이용하면 다음과 같은 효과를 얻을 수 있다.

* 짧고 간결한 프로그래밍 코드&#x20;
* 병렬 프로그래밍&#x20;
* 메소드 레퍼런스&#x20;
* **함수형 인터페이스 만들기**

함수형 인터페이스는 메소드를 단 한개만 가지고 있어야 하는데, 이를 강제하기 위해 `@FunctionalInterface`라는 주석을 사용한다. 주석이 붙어있을 경우 메소드 개수가 1개가 아니면 예외가 발생하여 함수형 인터페이스를 쉽게 구현할 수 있게 된다.

```java
@FunctionalInterface 
interface Calculator{ 
    int process(int a, int b); 
}
```

> 람다식을 사용하지 않고 Calculator를 이용해 덧셈 계산

```java
Calculator add = new Calculator() { 
    @Override public int process(int a, int b) { 
        return a + b; 
    } 
}; 
int result = add.process(10, 20); 
System.out.println("result = "+result);
```

> 람다식을 사용하여 Calculator를 이용해 덧셈 계산

```java
Calculator add = (a, b)->a+b; 
int result = add.process(10, 20); 
System.out.println("result = "+result);
```

람다식을 사용하는 것이 코드가 더 간편해짐을 확인할 수 있다. 이렇게 직접 만들어 사용해도 좋지만, 자바 8에서 기본적으로 지원해주는 함수형 인터페이스들을 이용하면 다양한 기능들을 쉽게 구현할 수 있다.

### 자바의 주요 함수형 인터페이스

| 인터페이스          | 설명                                    |
| -------------- | ------------------------------------- |
| Consumer       | 반환형이 void이며, 인자 T를 받는 함수형 인터페이스       |
| Predicate      | 인자 T를 받아 boolean을 반환하는 함수형 인터페이스      |
| Supplier       | 인자는 없으며 반환형만 존재하는 함수형 인터페이스           |
| Function       | 인자 T를 받아 반환형 R을 반환하는 전형적 함수형 인터페이스    |
| UnaryOperator  | 인자 T를 받아 동일한 형태인 T를 반환하는 함수형 인터페이스    |
| BinaryOperator | 인자 T를 2개 받아 동일한 형태인 T를 반환하는 함수형 인터페이스 |
| BiPredicate    | 반환형이 void이며 인자 T, U를 받는 함수형 인터페이스     |
| BiFunction     | 인자 T, U 를 받아 반환형 R 형태로 반환하는 함수형 인터페이스 |
| Optional       | null 여부를 검사하여 사용할 수 있는 함수형 인터페이스      |

위의 인터페이스를 이용하여 메소드를 매개변수로 전달할 수 있기 때문에 매우 유용하게 사용할 수 있다.

#### Consumer 사용 예

```java
Consumer<String> printer = text->System.out.println(text); 
printer.accept("this is consumer");
```

#### Predicate의 사용 예

```java
Predicate<String> check = string -> string.isEmpty(); 
System.out.println(check.test(""));//true 
System.out.println(check.test("1"));//false
```

#### Supplier의 사용 예

```java
Supplier<Integer> lotto = () -> (int)(Math.random() * 45) + 1; 
System.out.println(lotto.get());
```

#### Function의 사용 예

```java
Function<Integer, Integer> square = n -> n*n; 
int result = square.apply(11); 
System.out.println("result = "+result);
```

### 메소드 레퍼런스(Method Reference)

앞에에서 살펴봤던 함수형 인터페이스들에 메소드 참조를 설정할 수 있다. 아래 예제를 통해 확인해본다.

#### Consumer의 메소드 레퍼런스 활용

```java
package lambda; 
import java.util.function.Consumer; 
public class Lambda04 { 
    public static void test(String text) { 
        System.out.println(text); 
    } 
    public static void main(String[] args) { 
        Consumer<String> c = Lambda04::test; 
        c.accept("method reference"); 
    } 
}
```

main 메소드에서 c라는 Consumer 레퍼런스를 만들어 Lambda04에 존재하는 static 메소드인 test를 직접 참조하도록 설정하고 있다. 이러한 메소드 참조가 가능해지면 복잡한 코드도 메소드만 명시적으로 전달해줄 수 있으므로 코드의 가독성을 높이며 효율도 증대시킬 수 있다는 장점이 생기게 된다. 또한 기존의 자바에서 사용하던 전형적인 콜백 객체 생성방식에서 벗어날 수 있다.

#### Function 의 메소드 레퍼런스 사용

```java
Function<String, Boolean> check = String::isEmpty; 
System.out.println(check.apply(""));//true 
System.out.println(check.apply(" "));//false
```

`Java 1.8` 에서는 이러한 함수형 인터페이스와 람다, 메소드 레퍼런스를 이용하여 Collection을 제어할 수 있는 Stream api를 제공한다

### Java Collection

자바 버전별로 List에 간단한 데이터를 넣는 방법은 다음과 같이 변해왔다.

> 자바 1.7 이전 (java.util.ArrayList)

```java
List<Integer> list = new ArrayList<>(); 
for(int i = 1; i <= 10; i++) { 
    list.add(i); 
} 
System.out.println(list);
```

list는 확장이 가능하다.

> 자바 1.8 버전 (java.util.Arrays$ArrayList)
>
> ```java
> List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
> System.out.println(list);
> ```

list는 크기가 고정이며 확장이 불가능하다. 물론 1.7 이전의 방식도 사용할 수 있다.

> 자바 9 버전 (java.util.ImmutableCollections$ListN)

```java
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
System.out.println(list);
```

list는 크기가 고정이며 확장이 불가능하다. 이전 방식 모두 사용할 수 있다. 어느 방식으로 생성해도 stream api를 이용할 수 있으므로 앞으로는 List.of() 를 이용하여 간편하게 데이터를 초기화 하는 코드를 사용하기로 한다.

### Java Collection과 Stream API

자바 컬렉션에서 사용하는 Stream API는 배열과 Collection에서 동작하며, .stream() 명령을 이용하여 스트림 참조를 얻는것부터 시작한다.

```java
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
System.out.println(list.stream());
```

형태는 java.util.stream.ReferencePipeline$Head이며, 상태 변경을 위한 각종 명령들을 체이닝 형식으로 사용할 수 있다.

> List의 출력 코드(기존 방식)
>
> ```java
> List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
> for(int i=0; i < list.size(); i++) { 
>     System.out.println(list.get(i)); 
> }
> ```
>
> List의 출력 코드(확장 for 이용)
>
> ```java
> List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
> for(Integer n : list){ 
>     System.out.println(n); 
> }
> ```
>
> List의 출력 코드(Stream API 이용)
>
> ```java
> List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
> list.stream().forEach(d->System.out.println(d));
> ```
>
> List의 출력 코드(Stream API + Method Reference)
>
> ```java
> List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
> list.stream().forEach(System.out::println);
> ```

Stream API를 사용할 수록 코드가 조금 더 간결해지는 것을 확인할 수 있다. 이와 같은 현상은 Collection과 관련한 처리가 많아질 수록 더 심화된다.

> List에서 짝수만 출력하는 코드(기존 방식)

```java
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
for(int i=0; i < list.size(); i++) { 
    if(list.get(i) % 2 == 0) { 
        System.out.println(list.get(i)); 
    } 
}
```

> List에서 짝수만 출력하는 코드(Stream API + Method Reference)

```java
List<Integer> list = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10); 
list.stream()
        .filter(v -> v % 2 == 0) 
        .forEach(System.out::println);
```

> List에서 앞 5개 요소의 합을 구하는 코드(기존 방식)

```java
List<Integer> list = List.of(1, 2 ,3, 4, 5, 6, 7, 8, 9, 10); 
int total = 0; 
for(int i=0; i < list.size(); i++) { 
    if(i < 5) { 
        total += list.get(i); 
    } 
} 
System.out.println("합계 = "+total);
```

> List에서 앞 5개 요소의 합을 구하는 코드(Stream API + Method Reference)

```java
List<Integer> list = List.of(1, 2 ,3, 4, 5, 6, 7, 8, 9, 10); 
int total = list.stream() 
                                .limit(5)//앞에서 5개만 뽑아서 
                                .reduce(0, (a, b) -> a+b);//0을 시작값으로 하여 합산 
System.out.println("합계 = "+total);
```

### Stream API의 명령들

* forEach&#x20;
* count&#x20;
* max&#x20;
* min&#x20;
* findAny
* findFirst
* allMatch
* anyMatch
* noneMatch
* toArray
* reduce
* collect
* sort&#x20;

### Stream API 특징

* 원본을 변경하지 않는다&#x20;
* 반복하여 사용이 불가능하다&#x20;
* 연산이 최종 시점에 합산되어 처리된다
  * 필요시 병렬 스트림을 사용할 수 있다&#x20;

### 간단한 문제

아래 단어들을 List에 저장한 뒤 다음 문제 풀기

* Java, C, Python, Ruby, Perl, Go, Swift&#x20;
* 첫글자가 p인 요소를 대소문자 구분하지 않고 출력&#x20;
* 3글자 이상인 요소를 출력&#x20;

```java
List<String> list = List.of("Java", "C", "Python", "Ruby", "Perl", "Go", "Swift"); 
System.out.println("### 첫글자 p인 요소만 출력 ###"); 
list.stream().map(sub -> sub.toLowerCase()) 
                        .filter(sub -> sub.startsWith("p")) 
                        .forEach(System.out::println); 
System.out.println("### 3글자 이상인 요소만 출력 ###"); 
list.stream().filter(sub -> sub.length() >= 3) 
                        .forEach(System.out::println);
```

### Stream 간의 연결

스트림 연결 스트림을 서로 연결하고 싶은 경우에는 Stream에 있는 concat 메소드를 사용한다

> Stream\ concat(Stream\ s1, Stream\ s2)

```java
List<String> list1 = List.of("h", "e", "l", "l", "o"); 
List<String> list2 = List.of("s", "t", "r", "e", "a", "m"); 
Stream<String> stream = Stream.concat(list1.stream(), list2.stream()); 
stream.distinct().forEach(System.out::println);
```

.distinct()는 중복제거를 수행한다

### 원시형 스트림

Stream API에서는 원시형 데이터에 대한 스트림도 제공한다.

> IntStream을 이용하여 1부터 100까지 담아 출력하는 코드

```java
IntStream stream = IntStream.range(1, 100); 
stream.forEach(System.out::println);
```

> IntStream을 이용하여 1부터 100 사이 난수 100개를 저장하는 코드

```java
Random random = new Random(); 
IntStream stream = random.ints(50/*개*/, 1/*부터*/, 100/*가지*/); 
stream.forEach(System.out::println);
```
