16. [Java] Annotation, 람다식, 스트림 / 20230503
🧑🏻💻 TIL(Today I Learned)
🧑🏻💻 Annotation, 람다식, 스트림
3. 스트림(Stream)
➡️ 배열, 컬렉션의 저장 요소를 하나씩 참조해서 람다식으로 처리할 수 있도록 해주는 반복자
➡️ 다양의 데이터에 복잡한 연산을 수행하면서도 가독성과 재사용성이 높은 코드 작성 가능
➡️ 스트림을 사용하면 '선언형 프로그래밍(Declarative Programming)' 방식으로 데이터 처리 가능
: 인간 친화적이고 직관적인 코드 작성 가능 → "무엇"을 작성할지
명령형 프로그래밍(Imprerative Programming)
→ 코드 한 줄 한 줄의 동작 원리를 이해하고 순차적이고 세세하게 규정하는 방식
→ "어떻게" 코드를 작성할지
➡️ 데이터 소스가 무엇이냐에 관계없이 같은 방식으로 데이터를 가공/처리를 할 수 있음
즉, 하나의 통합된 방식으로 데이터를 다룰 수 있게 됨
🔎 스트림의 특징
1. 스트림의 처리 과정은 생성, 중간 연산, 최종 연산 세 단계의 파이프라인으로 구성될 수 있다.
2. 스트림은 원본 데이터 소스를 변경하지 않는다.(Read-Only)
: 오직 데이터를 읽어올 수만 있고 데이터에 대한 변경과 처리는 생성된 스트림 안에서 수행
3. 스트림은 일회용이다(Onetime-only)
: 마지막 연산이 수행되고 난 후에 스트림은 닫히고 사용 불가, 추가적인 작업은 새로 생성
4. 스트림은 내부 반복자이다.
: 외부 반복자는 개발자가 코드로 직접 컬렉션 요소를 반복해서 가져오는 코드 패턴
(인덱스를 사용하는 for, Iterator를 사용하는 while 대표적)
반대로 내부 반복자는 데이터 처리 코드(람다식)만 주입해서 그 안에서 모든 데이터 처리가 이루어지도록 함
🔎 스트링의 생성
✍🏻 배열 스트림의 생성
➡️ 배열을 데이터 소스로 하는 스트림 생성은 Arrays 클래스의 stream() 메서드 또는 Stream 클래스의 of() 메서드 사용 가능
✍🏻 컬렉션 스트림 생성
➡️ 컬렉션 타입(List, Set 등) 경우 컬렉션의 최상위 클래스인 Collection에 정의된 stream() 메서드를 사용하여 생성 가능
✍🏻 임의의 수 스트림 생성
➡️ 난수를 생성하는 자바의 기본 내장 클래스 Random 클래스 안에는 해당 타입의 난수들을 반환하는 스트림을 생성하는 메서드가 정의되어 있음
➡️ ints() 메서드의 경우 int형의 범위 안에 있는 난수들을 무한대로 생성하여 IntStream 타입의 스트림으로 반환
IntStream vs Stream<Integer>
: IntStream 쓰는 것이 좋음 후자는 박싱 언박싱 과정을 거쳐야함, 수학에서 사용하는 메서드들을 다 사용할 수 있음
- int 형 범위 안에서 출력값이 무한대로 생성됨
→ 무한 스트림(infinite stream) : 스트림의 크기가 정해지지 않은 것
- limit() 사용해서 크기를 제한할 수도 있고 ints() 통해서 제한할 수도 있음
→ 출력값은 그때그때 달라지지만 5개로 제한되어서 나오는 것을 확인할 수 있음
🔎 중간 연산자(Intermediate Operation)
➡️ 중간 연산자의 결과는 스트림을 반환하기 때문에 여러 연산자를 연결하여 원하는 데이터 처리 수행 가능함
✍🏻 필터링(filter(), distinct())
➡️ 조건에 맞는 데이터들만 정제하는 역할을 하는 중간 연산자
- filter() : Stream에서 조건에 맞는 데이터만을 정제하여 더 작은 컬렉션을 만들어냄
매개값으로 조건(Predicate) 주고, 조건이 참이 되는 요소만 필터링 → 조건은 람다식 사용하여 정의 가능 - distinct() : Stream의 요소들에 중복된 데이터가 존재하는 경우, 중복을 제거하기 위해 사용
✍🏻 매핑(map())
➡️ 원하는 필드만 추출하거나 특정 형태로 변환할 때 사용하는 중간 연산자
➡️ 값은 변환하기 위한 조건을 람다식으로 정의
➡️ 이중 배열처럼 뎁스가 깊어지는 경우 : flatMap()
✍🏻 정렬(sorted())
➡️ 정렬할 때 사용하는 중간 연산자
➡️ 괄호 안에 Comparator 라는 인터페이스에 정의된 static 메서드와 디폴트 메서드를 사용하여 간편하게 정렬 작업 수행 가능
(만약 괄호 안에 아무것도 넣지 않으면 기본 정렬[오름차순]으로 정렬)
✍🏻 기타
➡️ skip() : 스트림의 일부 요소 건너뜀
➡️ limit() : 스트림의 일부 자름
➡️ peek() : forEach() 와 마찬가지로 요소들을 순회하며 특정 작업 수행, 하지만 중간 연산자이기 때문에 여러 번 연결해서 사용 가능
🔎 스트림의 최종 연산(Terminal Operation)
➡️ 최종 연산자가 스트림 파이프라인에서 최종적으로 사용되고 나면 해당 스트림은 닫히고 모든 연산 종료
➡️ 중간 연산은 최종 연산자가 수행될 때야 스트림의 요소들이 중간 연산을 거쳐 가공된 후에 최종 연산에서 소모 됨
: 지연된 연산(lazy evaluation)
✍🏻 기본 집계(sum(), count(), average(), max(), min())
➡️ 숫자와 관련된 최종 연산자
import java.util.Arrays;
public class TerminalOperationExample1 {
public static void main(String[] args) {
// int 형 배열 생성
int[] intArray = {1, 2, 3, 4, 5};
// 카운팅
long count = Arrays.stream(intArray).count();
System.out.println("intArr의 전체 요소 개수 : " + count);
// 합계
long sum = Arrays.stream(intArray).sum();
System.out.println("intArr의 전체 요소 합 : " + sum);
// 평균
double avg = Arrays.stream(intArray).average().getAsDouble(); // average() 는 OptionalDouble 객체를 반환하는 메서드
System.out.println("전체 요소 평균값 : " + avg);
// 최대값
int max = Arrays.stream(intArray).max().getAsInt();
System.out.println("최대값 : " + max);
// 최소값
int min = Arrays.stream(intArray).min().getAsInt();
System.out.println("최소값 : " + min);
// 배열의 첫 번째 요소 --> 스트림에서 가장 처음으로 만나는 요소
int first = Arrays.stream(intArray).findFirst().getAsInt();
System.out.println("배열의 첫 번째 요소 : " + first);
}
}
// OptionalDouble, OptionalInt 등은 일종의 래퍼클래스
// null 값으로 인해서 에러가 발생하는 현상을 효율적으로 방지하기 위한 목적으로 도입된 것
// 즉, 래퍼클래스로 객체로 되어있기 때문에 기본형으로 바꿔줘야 함 ! --> getAsDouble(), getAsInt() 사용
// 그저 기본형으로 돌아가기 위한 메서드!
✍🏻 매칭(allMatch(), anyMatch(), noneMatch())
➡️ match() 메서드를 사용하면 조건식 람다 Predicate 를 매개변수로 넘겨 스트림의 각 데이터 요소가 특정한 조건을 충족하지 않는지 검사하여, 그 결과를 boolean 값으로 반환
- allMatch() : 모든 요소가 조건을 만족하는지 여부를 판단
- nonMatch() : 모든 요소가 조건을 만족하지 않는지 여부 판단
- anyMatch() : 하나라도 조건을 만족하는 요소가 있는지 여부 판단
import java.util.Arrays;
public class TerminalOperationExample {
public static void main(String[] args) throws Exception {
// int형 배열 생성
int[] intArray = {2,4,6};
// allMatch()
boolean result = Arrays.stream(intArray).allMatch(element-> element % 2 == 0);
System.out.println("요소 모두 2의 배수인가요? " + result);
// anyMatch()
result = Arrays.stream(intArray).anyMatch(element-> element % 3 == 0);
System.out.println("요소 중 하나라도 3의 배수가 있나요? " + result);
// noneMatch()
result = Arrays.stream(intArray).noneMatch(element -> element % 3 == 0);
System.out.println("요소 중 3의 배수가 하나도 없나요? " + result);
}
}
// 출력값
요소 모두 2의 배수인가요? true
요소 중 하나라도 3의 배수가 있나요? true
요소 중 3의 배수가 하나도 없나요? false
✍🏻 요소 소모( reduce())
➡️ 스트림의 요소를 줄여나가면서 연산을 수행하고 최종적인 결과 반환
➡️ 첫 번째와 두 번째 요소를 가지고 연산 수행하고, 그 결과와 다음 세 번째 요소 가지고 또다시 연산을 수행하는 식으로 연산이 끝낼 때까지 반복
➡️ reduce() 메서드의 매개변수 타입은 함수형 인터페이스 BinaryOperator<T> 로 정의되어 있음
Optional<T> reduce(BinaryOperator<T> accumulator)
T reduce(T identity, BinaryOperator<T> accumulator) : 매개 변수 2개까지 받는 경우
→ identity : 특정 연산 시작할 때 초기값
→ accumulator : 각 요소를 연산하여 나온 누적된 결과값을 생성하는 데 사용되는 조건식
➡️ count()와 sum() 과 같은 집계 메서드 또한 내부적으로 reduce() 사용하여 연산 수행
✍🏻 요소 수집(collect())
➡️ 중간 연산을 통한 요소들의 데이터 가공 후 요소들을 수집하는 최종 처리 메서드
➡️ 스트림의 요소들을 List, Set, Map 등 다른 타입의 결과로 수집하고 싶은 경우에 사용
➡️ Collector 인터페이스 타입의 인자를 받아서 처리 → 직접 구현하거나 미리 제공한 것들 사용 가능
➡️ 요소 그룹핑 및 분할 등 다른 기능들도 제공함
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
public class TerminalOperationExample {
public static void main(String[] args) {
// Student 객체로 구성된 배열 리스트 생성
List<Student> totalList = Arrays.asList(
new Student("김코딩", 100, Student.Gender.Male),
new Student("박해커", 80, Student.Gender.Male),
new Student("이자바", 90, Student.Gender.Female),
new Student("나미녀", 60, Student.Gender.Female)
);
// 스트림 연산 결과를 Map으로 반환
Map<String, Integer> maleMap = totalList.stream()
.filter(s -> s.getGender() == Student.Gender.Male)
.collect(Collectors.toMap(
student -> student.getName(), // Key
student -> student.getScore() // Value
));
// 출력
System.out.println(maleMap);
}
}
class Student {
public enum Gender {Male, Female};
private String name;
private int score;
private Gender gender;
public Student(String name, int score, Gender gender) {
this.name = name;
this.score = score;
this.gender = gender;
}
public String getName() {
return name;
}
public int getScore() {
return score;
}
public Gender getGender() {
return gender;
}
}
// 출력값
{김코딩=100, 박해커=80}
스트림까지 정리 완료! 다시 보니까 새록새록 생각나고 머릿속에서 한 번 더 정리되는 느낌!
'SEB_BE_45 > 공부 정리' 카테고리의 다른 글
[SEB BE] Section 1 회고 / 20230509 (0) | 2023.05.09 |
---|---|
17. [Java] Thread, JVM, Garbage Collection / 20230508 (1) | 2023.05.09 |
16. [Java] Annotation, 람다식, 스트림 / 20230503 (0) | 2023.05.08 |
15. [Java] 컬렉션(Collection) 2 / 20230502 (0) | 2023.05.03 |
14. [Java] 컬렉션 (Collection) 1 / 20230501 (0) | 2023.05.02 |