[공부 내용 정리]
매핑 (map, flatMap)
map : 스트림의 요소를 하나씩 특정 값으로 변환하는 메소드임. 이렇게 변환된 값은 새로운 스트림으로 만들어짐.

List<String> list = Arrays.asList("a", "b", "c", "d", "e");
// 하나씩 대문자로 변환 가능
list.stream().map(x -> x.toUpperCase()).forEach(System.out::println);
flatMap : map과 마찬가지로 스트림의 요소들을 다른 값으로 대체하는 것은 같지만, 대체하는 값이 스트림일 경우에 flatMap을 사용함. 2차원, 2단계 배열 또는 List 타입에 대해서 일괄적으로 하나의 Stream에서 연산할 수 있도록 함.
- flatMap은 한 차원씩 배열을 평면화함.

List<String> list = Arrays.asList("Hello World", "Java stream");
// [Hello, World, Java, stream]
list.stream()
.flatMap(str -> Arrays.stream(str.split(" ")))
.forEach(System.out::println);
정렬 (sorted)
sorted : 스트림의 요소들을 정렬하기 위해 사용하는 메소드. 오름차순 정렬 기본
List<Integer> list = Arrays.asList(12, 4, 2, 8, 11);
// [2, 4, 8, 11, 12]
list.stream()
.sorted()
.forEach(System.out::println);
List<String> list = Arrays.asList("e", "a", "c", "z", "d");
// [a, c, d, e, z]
list.stream()
.sorted()
.forEach(System.out::println);
- 역순으로 (내림차순) 정렬하고 싶다면 Comparator 사용하면 됨.
- Comparator.reverseOrder()
List<String> list = Arrays.asList("e", "a", "c", "z", "d");
// [z, e, d, c, a]
list.stream()
.sorted(Comparator.reverseOrder()) // 역순 정렬
.forEach(System.out::println);
루핑 (peek, forEach)
peek : “가공” 단계에서 사용. 그냥 확인해본다는 단어 뜻처럼 특정 결과를 반환하지 않음. 작업 처리 중간에 결과를 확인할 때 사용할 수 있음.
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
int sum = list.stream()
.mapToInt(n -> n)
.filter(n -> n % 2 == 0)
.peek(n -> System.out.println(n)) // [2, 4]
.sum(); // sum을 사용하지 않으면 peek은 동작하지 않음
System.out.println(sum); // 6
forEach : “결과” 단계에서 사용. 따라서 다른 결과 메소드와 중복해서 사용할 수 없음.
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
list.stream()
.mapToInt(n -> n)
.filter(n -> n % 2 == 0)
.forEach(n -> System.out.println(n)); // [2, 4]
수집 (collect())
collect : 스트림의 최종 단계에서 사용하는 메소드. 스트림으로 가공한 데이터를 최종적으로 컬렉션으로 변환한다고 보면 됨. 자주 사용하는 작업은 Collectors 클래스에서 제공함.
ex) 리스트의 숫자 중에서 짝수만 골라내어 새로운 리스트를 만드는 예제
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5);
// 짝수만 골라내어 새로운 리스트로 collect
List<Integer> newList = list.stream()
.filter(n -> n % 2 == 0)
.collect(Collectors.toList());
newList.stream().forEach(System.out::println); // [2, 4]

그룹핑하여 수집
collect 메소드는 컬렉션의 요소들을 그룹핑해서 Map 객체를 생성하는 기능도 제공함. Collectors의 groupingBy 사용

- groupingBy 의 예시 (Collecotrs.counting()은 그룹핑 후 집계하는 메소드!!)
List<Integer> list = Arrays.asList(1, 1, 2, 2, 2, 3, 4, 5, 5, 5);
Map<Integer, Long> result = list.stream()
.collect(
Collectors.groupingBy(
n -> n,
Collectors.counting() // 그룹핑 후 집계하는 메소드
)
);
/*
* 1: 2개
* 2: 3개
* 3: 1개
* 4: 1개
* 5: 3개
*/
result.forEach((key, value) -> System.out.println(key + ": " + value + "개"));
- groupingBy의 첫 번째 파라미터는 그룹핑을 위한 키 값임. n -> n은 요소 값 그 자체를 키로 사용한다는 의미임. (?)
- 두 번째 파라미터는 집계 함수임. 그룹핑 후 어떻게 집계할 것인지 정할 수 있는데, 여기서는 그룹 별 카운트를 함.
복습 문제 ) Member에 있는 요소들을 Person으로 옮기기
package chapter13;
public class Member {
private String name;
private int age;
private String color;
public Member(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
public String getColor() {
return color;
}
}
package chapter13;
public class Person {
private String name;
private int age;
public Person (String name, int age) {
this.name = name;
this.age = age;
}
public String getName() {
return name;
}
public int getAge() {
return age;
}
@Override
public String toString() {
return "Person{" +
"name='" + name + '\\'' +
", age=" + age +
'}';
}
}
package chapter13;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
public class Quiz {
public static void main(String[] args) {
List<Member> memberList = Arrays.asList(
new Member("LHJ", 23, "skyblue"),
new Member("LLL", 24, "red")
);
List<Person> personList = memberList.stream()
.map(member -> new Person(member.getName(), member.getAge()))
.collect(Collectors.toList());
System.out.println(personList);
}
}
Optional
NullPointerException
// student 객체가 null일 경우 NullPointerException 발생
Student student = null;
// null 값에 getName이라는 메소드는 존재하지 않기 때문
student.getName();
위 예시처럼 null 값을 가진 객체를 참조하려고 했을 때 NPE가 발생함.
Optional이란?
NPE를 방지할 수 있도록하는 Optional 클래스.

아래와 같이 Optional로 컬렉션의 null 여부를 체크하면 에러를 방지할 수 있음.
List<Integer> list = null;
Optional.ofNullable(list)
.orElseGet(Collections::emptyList)
.forEach(System.out::println);
Optional 객체 생성 메소드
Optional 클래스는 of와 ofNullable 메소드를 지원함.
- of : null 값을 허용하지 않음.
- ofNullable : null 값을 허용함.

// 비어있는 Optional 객체 반환
// null 대신 빈 Optional<T> 객체 사용
Optional<String> empty = Optional.empty();
// null이 아닌 값을 갖는 Optional 객체 반환
Optional<String> of = Optional.of("Optional 객체");
// null을 허용하는 Optional 객체 반환
Optional<String> nullableOpt = Optional.ofNullable("Nullable Optional 객체");
아래 예시에서는 Optional.ofNullable을 사용했기 때문에 list가 null 임에도 불구하고 에러가 발생하지 않음.
List<Integer> list = null;
Optional.ofNullable(list)
.orElseGet(Collections::emptyList)
.forEach(System.out::println);
+) orElseGet 메소드도 Optional에서 제공하는데, 만약 list가 null일 경우 어떤 값을 사용할 것인지 정할 수 있음.
여기서는 list가 null일 경우 비어있는 리스트(emptyList)를 사용함.
Optional 객체 반환
기본적으로 Optional 클래스의 get() 메서드를 사용하여 저장된 값을 반환하지만,
만약 Optional 객체가 null 일 경우엔 예외가 발생함. 따라서 안전하게 객체를 꺼내오기 위해서는 null일 경우 사용할 default 값을 정해줌.
→ 이때 사용하는 메소드가 orElse(), orElseGet() 메소드임.
Optional.ofNullable(stringList)
.get() // stringList가 null일 경우 NoSuchElementException 예외 발생!!
List<String> newList = Optional.ofNullable(list)
.orElse(Collections.emptyList());
List<String> newList = Optional.ofNullable(list)
.orElseGet(Collections::emptyList); // 람다 표현식을 넣는 경우
→ 보통 get() 보단 예외 처리를 해주는 안전한 orElse(), orElseGet() 를 더 많이 사용함.

Optional을 이용한 Null 처리 방법 예시
아래의 예시는 컬렉션이 null은 아니지만 아무런 값도 가지고 있지 않은 경우이다.
List<Integer> list = new ArrayList<>();
// NoSuchElementException 예외 발생
double avg = list.stream()
.mapToInt(Integer::intValue)
.average()
.getAsDouble();
System.out.println(avg);
예외를 피하기 위한 첫 번째 방법은 Optional 객체를 얻어 값이 실제로 존재할 때만 평균을 출력하는 것이다. Optional의 isPresent는 값이 존재하는지 여부를 반환하는 메소드임.
List<Integer> list = new ArrayList<>();
OptionalDouble optional = list.stream()
.mapToInt(Integer::intValue)
.average();
// 값이 실제로 존재하는지 체크
if (optional.isPresent()) {
System.out.println(optional.getAsDouble());
}
두 번째 방법은 값이 없을 경우 디폴트 값을 정하는 Optional의 orElse를 사용함.
List<Integer> list = new ArrayList<>();
double avg = list.stream()
.mapToInt(Integer::intValue)
.average()
.orElse(0.0); // 평균 값이 존재하지 않을 경우 0을 사용
System.out.println(avg);
세 번째 방법은 스트림에서 ifPresent 메소드를 사용하는 것임. Optional의 ifPresent에는 값이 실제로 존재할 경우에만 동작하는 코드를 작성할 수 있음.
List<Integer> list = new ArrayList<>();
list.stream()
.mapToInt(Integer::intValue)
.average()
.ifPresent(avg -> System.out.println(avg)); // 평균 값이 있을 경우에만 출력
Daily Quiz
💡 스트림과 컬렉션을 사용하여 코드를 작성해주세요.
스트림으로 해결한 버전과, 스트림으로 해결하지 않은 버전 모두 작성해주세요.
1. 큰 수 출력하기
N(1<=N<=100)개의 정수를 입력받아, 자신의 바로 앞 수보다 큰 수만 출력하는 프로그램을 작성하세요. (첫 번째 수는 무조건 출력합니다)
**입력설명**
첫 줄에 자연수 N이 주어지고, 그 다음 줄에 N개의 정수가 입력됩니다.
**출력설명**
자신의 바로 앞 수보다 큰 수만 한 줄로 출력합니다.
**입력예제 1**
6
7 3 9 5 6 12
**출력예제 1**
7 9 6 12
<코드 작성>
public class PrintMoreBigNumber {
public List<Integer> solution(int n, int[] array) {
List<Integer> answer = new ArrayList<>();
// 코드 작성
return answer;
}
public static void main(String[] args) {
PrintMoreBigNumber printMoreBigNumber = new PrintMoreBigNumber();
Scanner scanner = new Scanner(System.in);
int number = scanner.nextInt();
int[] array = new int[number];
for (int i = 0; i < number; i++) {
array[i] = scanner.nextInt();
}
for (int x : printMoreBigNumber.solution(number, array)) {
System.out.print(x + " ");
}
}
}
→ 스트림 X
public class PrintMoreBigNumber {
public List<Integer> solution(int n, int[] array) {
List<Integer> answer = new ArrayList<>();
answer.add(array[0]);
for (int i = 1; i < n-1; i++) {
if (array[i] < array[i+1]) {
answer.add(array[i+1]);
}
}
return answer;
}
}
→ 스트림 O
List<Integer> answer = new ArrayList<>();
// Stream
answer.add(array[0]);
List<Integer> collect = IntStream.range(1, n) // 1부터 n-1까지의 범위에서 스트림 생성
.filter(index -> array[index] > array[index-1]) // 필터링으로 index를 뽑아줌
.mapToObj(x -> array[x]) // 각 인덱스에 해당하는 배열의 요소를 가져와서 객체 스트림으로 변환
.collect(Collectors.toList()); // 객체 스트림을 리스트로 변환
answer.addAll(collect); // 기존 생성한 리스트에 앞서 생성한 collect 리스트의 모든 요소를 추가함.
return answer;
- mapToObj : IntStream의 요소를 객체 스트림으로 반환하는 역할을 함. 정수 x에 대해 배열 array에서 해당 인덱스의 값을 가져오는 람다식임. 일반적으로 mapToObj 는 기본 데이터 유형(int)을 박싱하여 해당 데이터 유형의 객체(Integer)로 매핑함.
2. 두 배열 합치기
오름차순으로 정렬이 된 두 배열이 주어지면 두 배열을 오름차순으로 합쳐 출력하는 프로그램을 작성해보세요.
**입력설명**
첫 번째 줄에 첫 번째 배열의 크기 N(1<=N<=100)이 주어집니다.
두 번째 줄에 N개의 배열 원소가 오름차순으로 주어집니다.
세 번째 줄에 두 번째 배열의 크기 M(1<=M<=100)이 주어집니다.
네 번째 줄에 M개의 배열 원소가 오름차순으로 주어집니다.
각 리스트의 원소는 int형 변수의 크기를 넘지 않습니다.
**출력설명**
오름차순으로 정렬된 배열을 출력합니다.
**입력예제 1**
3
1 3 5
5
2 3 6 7 9
**출력예제 1**
1 2 3 3 5 6 7 9
<코드 작성>
public class AscendingSort {
public int[] solution(int[] array1, int[] array2) {
int[] result = new int[array1.length + array2.length];
// 코드 작성
return result;
}
public static void main(String[] args) {
AscendingSort ascendingSort = new AscendingSort();
Scanner scanner = new Scanner(System.in);
int number = scanner.nextInt();
int[] array = new int[number];
for (int i = 0; i < number; i++) {
array[i] = scanner.nextInt();
}
int number2 = scanner.nextInt();
int[] array2 = new int[number2];
for (int i = 0; i < number2; i++) {
array2[i] = scanner.nextInt();
}
for (int x : ascendingSort.solution(array, array2)) {
System.out.print(x + " ");
}
}
}
→ 스트림 X
public int[] solution(int[] array1, int[] array2) {
int[] result = new int[array1.length + array2.length];
/*
System.arraycopy 메서드 : arraycopy(Object src, int srcPos, Object dest, int destPos, int length)
src (원본 배열): 복사할 대상이 되는 배열입니다.
srcPos (원본 배열에서 복사를 시작할 위치): 원본 배열에서 복사를 시작할 인덱스입니다.
dest (대상 배열): 복사된 요소가 저장될 대상 배열입니다.
destPos (대상 배열에서 복사를 시작할 위치): 대상 배열에서 복사를 시작할 인덱스입니다.
length (복사할 요소의 수): 복사할 요소의 개수입니다.
*/
// array1 복사
System.arraycopy(array1, 0, result, 0, array1.length);
// array2 복사
System.arraycopy(array2, 0, result, array1.length, array2.length);
Arrays.sort(result);
return result;
}
+) 스트림X, for문으로
int[] result = new int[array1.length + array2.length];
for (int i = 0; i < array1.length; i++) {
result[i] = array1[i];
}
for (int i = 0; i < array2.length; i++) {
result[i+ array1.length] = array2[i];
}
Arrays.sort(result);
return result;
+) 스트림X, while문으로
int[] result = new int[array1.length + array2.length];
int i = 0;
int j = 0;
int index = 0;
while(i < array1.length && j < array2.length) {
if (array1[i] < array2[j]) {
result[index] = array1[i];
index++; i++;
} else {
result[index] = array2[j];
index++; j++;
}
}
while (i < array1.length) {
result[index] = array1[i];
index++; i++;
}
while (j < array2.length) {
result[index] = array2[j];
index++; j++;
}
return result;
→ 스트림 O
'Back-End > Java # 교육' 카테고리의 다른 글
[Java 교육] 람다식 마무리/스트림 (다시 복습 필수) (1) | 2024.02.19 |
---|---|
[Java 교육] LIFO,FIFO 컬렉션/람다식 (다시 복습 필수 - 람다식) (1) | 2024.02.16 |
[Java 교육] Collection/List/Set/Map (0) | 2024.02.15 |
[Java 교육] 제한된 타입/와일드카드 타입/제네릭 상속, 구현 (1) | 2024.02.14 |
[Java 교육] 자동 리소스 닫기/중간정리/제네릭 (1) | 2024.02.08 |