01. Optional Class
Optional<T>는 Java 8에서 도입된, null 안정성을 위한 래퍼 클래스이다. 그렇기에 Optional를 사용하면 NullPointerException(NPE)를 방지하기 위해 null 값을 if 문을 통해 처리했던 과거와 달리, 객체 차원에서 NPE를 방지할 수 있다. 즉, Optional은 값이 존재할 수도 있고, 존재하지 않을 수도 있는 컨테이너이다.
1. 기본 구조
public final class Optional<T> {
private final T value;
}
2. 객체 생성 방법
// 1. of()와 ofNullable()을 통한 객체 생성
Optional<String> opt1 = Optional.ofNullable(null); // null도 허용
Optional<String> opt2 = Optional.ofNullable("123");
// 2. empty()를 통한 빈 객체 생성
Optional<String> opt3 = Optional.<String>empty(); // 명시적 빈 객체 생성
3. 값 조회 및 디폴트 처리
// 1. isPresent(): 값 존재 여부 확인
System.out.println(opt1.isPresent());
// 2. get(): 값 가져오기
Optional<String> optString = Optional.of("java");
System.out.println(optString.get());
// 3. orElse(): 기본 값 지정
String nullName = null;
String name = Optional.ofNullable(nullName).orElse("kimcoding");
System.out.println(name);
4. 메서드 체이닝
Optional 클래스는 스트림처럼 메서드 체이닝으로 사용할 수 있어 가독성이 좋고, Null-Safe한 코드 작성이 가능하다.
// 1. 단일 메서드 체이닝 예제
List<String> languages = Arrays.asList("Ruby", "Python", "Java", "Go", "Kotlin");
Optional<List<String>> listOptional = Optional.of(languages);
int size = listOptional
.map(List::size)
.orElse(0);
System.out.println(size);
// 2. 중첩 메서드 체이닝 예제
// CarClub.java
public class CarClub {
public static void main(String[] args) {
MemberService memberService = new MemberService();
String insuranceName =
memberService
.getMember("jungmin")
.flatMap(Member::getCar)
.flatMap(Car::getInsurance)
.map(Insurance::getCompanyName)
.orElse("Not result");
System.out.println(insuranceName);
}
}
5. 익명 클래스 관련 예제
// MemberService.java
public class MemberService {
public Optional<Member> getMember(String id) {
Optional<Insurance> insurance = Optional.of(new Insurance("Samsung direct"));
Optional<Car> car = Optional.of(new Car("XC-60", 60_000_000, insurance));
return Optional.of(new Member("Jungmin", "jungmin0102", car));
}
}
// Member.java
public class Member {
private String name;
private String id;
private Optional<Car> car;
public Member(String name, String id, Optional<Car> car) {
this.name = name;
this.id = id;
this.car = car;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public Optional<Car> getCar() {
return car;
}
public void setCar(Optional<Car> car) {
this.car = car;
}
}
// Car.java
public class Car {
private String carName;
private int price;
private Optional<Insurance> insurance;
public Car(String carName, int price, Optional<Insurance> insurance) {
this.carName = carName;
this.price = price;
this.insurance = insurance;
}
public String getCarName() {
return carName;
}
public void setCarName(String carName) {
this.carName = carName;
}
public int getPrice() {
return price;
}
public void setPrice(int price) {
this.price = price;
}
public Optional<Insurance> getInsurance() {
return insurance;
}
public void setInsurance(Optional<Insurance> insurance) {
this.insurance = insurance;
}
}
// Insurance.java
public class Insurance {
private String companyName;
public Insurance(String companyName) {
this.companyName = companyName;
}
public String getCompanyName() {
return companyName;
}
public void setCompanyName(String companyName) {
this.companyName = companyName;
}
}
02. 그룹화와 분할
Stream API에서는 collect() 연산과 함께 groupingBy(), partitioningBy()를 사용하여, 데이터를 그룹화 및 조건 분할할 수 있다.
| 함수 | 메서드 | 반환 타입 | |
| 단순 그룹화 | groupingBy() | 데이터를 특정 기준으로 그룹핑할 때 사용 >> SQL의 GROUP BY 절과 유사 |
Map<그룹 키, List<T>> |
| 그룹화 후 변환 | groupingBy() + mapping() | 그룹화와 동시에 요소를 특정 값으로 추출 / 변환하여 저장 | |
| 다중 그룹화 | groupingBy() 중첩 사용 | 2단계 기준으로 서브 그룹화할 때 사용 |
Map<1차Key, Map<2차Key, List<T>>>
|
| 조건 분할 | parititioningBy() | 데이터를 true / false 기분으로 두 개의 그룹으로 분할 | Map<boolean, List<T>> |
public class Student {
private String name;
private String gender;
private int grade;
private int score;
public Student() {
}
public Student(String name, String gender, int grade, int score) {
this.name = name;
this.gender = gender;
this.grade = grade;
this.score = score;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public int getGrade() {
return grade;
}
public void setGrade(int grade) {
this.grade = grade;
}
public int getScore() {
return score;
}
public void setScore(int score) {
this.score = score;
}
@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", gender='" + gender + '\'' +
", grade=" + grade +
", score=" + score +
'}';
}
}
List<Student> studentList = Arrays.asList(
new Student("홍길동", "남", 1, 95),
new Student("강감찬", "남", 2, 80),
new Student("이순신", "남", 1, 85),
new Student("심사임당", "여", 2, 90),
new Student("유관순", "여", 3, 100),
new Student("문성실", "여", 1, 88)
);
/* 1. groupingBy() - 단순 그룹화 */
// 1-1. 학년별로 그룹화
System.out.println("----- groupingBy(학년) -----");
Map<Integer, List<Student>> gradeGroupMap = studentList.stream()
.collect(Collectors.groupingBy(Student::getGrade));
gradeGroupMap.forEach((grade, students) -> {
System.out.println("[ " + grade + "학년 ]");
students.forEach(student -> System.out.println(student));
});
System.out.println("--------------------------------");
// 1-2. 성별로 그룹화
System.out.println("----- groupingBy(성별) -----");
Map<String, List<Student>> genderGroupMap = studentList.stream()
.collect(Collectors.groupingBy(Student::getGender));
genderGroupMap.forEach((gender, students) -> {
System.out.println("[ " + gender + "학생 ]");
students.forEach(student -> System.out.println(student));
});
System.out.println("--------------------------------");
/* 2. groupingBy() - 그룹화 후 매핑 */
// 2-1. 성별로 그룹화하고 학생 이름만 추출
System.out.println("----- groupingBy() - 그룹화 후 매핑 -----");
Map<String, List<String>> genderNameMap2 = studentList.stream()
.collect(Collectors.groupingBy(
Student::getGender,
Collectors.mapping(Student::getName, Collectors.toList())
));
System.out.println("성별로 그룹화: " + genderNameMap2);
System.out.println("--------------------------------");
/* 3. partitioningBy() - 분할 */
// 3-1. 90점 이상인 학생과 아닌 학생 분할
System.out.println("----- partitioningBy -----");
Map<Boolean, List<Student>> scorePartition = studentList.stream()
.collect(Collectors.partitioningBy(student -> student.getScore() >= 90));
System.out.println("==== 90점 이상 학생 ====");
scorePartition.get(true).forEach(System.out::println);
System.out.println("==== 90점 미만 학생 ====");
scorePartition.get(false).forEach(System.out::println);
System.out.println("--------------------------------");
/* 4. 다중 레벨 그룹화 */
// 4-1. 학년별로 그룹화 후 성별로 다시 그룹화
System.out.println("----- 다중 레벨 그룹화 -----");
Map<Integer, Map<String, List<Student>>> gradeAndGenderMap = studentList.stream()
.collect(Collectors.groupingBy(
Student::getGrade,
Collectors.groupingBy(Student::getGender)
));
gradeAndGenderMap.forEach((grade, genderMap) -> {
System.out.println("[ " + grade + "학년 ]");
genderMap.forEach((gender, students) -> {
System.out.println("- " + gender + "학생: ");
students.forEach(student ->
System.out.println(" " + student.getName() + " (" + student.getScore() + "점)")
);
});
});
System.out.println("--------------------------------");
03. 통계 및 집계
| 메서드 | 반환 값 | ||
| 합계 | sum() | 스트림 내 숫자를 전부 더함 | |
| 평균 | average() | 스트림 내 존재하는 모든 숫자의 평균 계산 | OptionalDouble |
| 최댓값 | max() | 최댓값을 반환 | OptionalInt |
| 최솟값 | min() | 최솟값을 반환 | OptionalInt |
| 결합 | reduce() | 스트림의 요소들을 하나의 값으로 결합 >> 매개변수의 개수에 따라 3가지 종류로 나뉨 |
|
| 전체 계산 수행 | summarizingInt() | 합계, 평균, 최대 / 최소를 한 번에 계산 >> 자료형에 따라 Int / Double / Long으로 구분 |
// 1. sum 예제
int totalScore = studentList.stream()
.mapToInt(Student::getScore)
.sum();
System.out.println("총점: " + totalScore);
// 2. average 예제
double average = studentList.stream()
.mapToInt(Student::getScore)
.average()
.orElse(0.0); // OptionalDouble 처리
System.out.println("평균 점수: " + average);
// 3. max 예제
int maxScore = studentList.stream()
.mapToInt(Student::getScore)
.max()
.orElse(0);
System.out.println("최고 점수: " + maxScore);
// 4. min 에제
int minScore = studentList.stream()
.mapToInt(Student::getScore)
.min()
.orElse(0);
System.out.println("최저 점수: " + minScore);
// 5. reduce 예제
int total = studentList.stream()
.map(Student::getScore)
.reduce(0, (a, b) -> a + b);
System.out.println("총합 (reduce): " + total);
// 5-1. 인자가 1개일 경우
OptionalInt reduceOneParam = IntStream.range(1, 4) // 1, 2, 3
.reduce((a, b) -> {
return Integer.sum(a, b);
});
System.out.println("reduceOneParam = " + reduceOneParam.getAsInt());
// 5-2. 인자가 2개일 경우
int reduceTwoParam = IntStream.range(1, 4) // 1, 2, 3
.reduce(100, Integer::sum);
System.out.println("reduceTwoParam = " + reduceTwoParam);
// 5-3. 인자가 3개일 경우
Integer reduceThreeParam = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
.reduce(100,
Integer::sum,
(x, y) -> x + y);
System.out.println("reduceThreeParam = " + reduceThreeParam);
// 6-1. summarizingInt() 예제
IntSummaryStatistics stats = studentList.stream()
.collect(Collectors.summarizingInt(Student::getScore));
System.out.println("총합: " + stats.getSum());
System.out.println("평균: " + stats.getAverage());
System.out.println("최대: " + stats.getMax());
System.out.println("최소: " + stats.getMin());
// 6-2. summarizingDouble() 예제
DoubleSummaryStatistics stats = studentList.stream()
.collect(Collectors.summarizingDouble(Student::getScore));
// 6-3. summarizingLong() 예제
LongSummaryStatistics stats = List.of(100L, 200L, 300L).stream()
.collect(Collectors.summarizingLong(Long::longValue));
System.out.println("합계: " + stats.getSum());
System.out.println("평균(소수 포함): " + stats.getAverage());
int[] intArray = {1,2,3,4,5};
long count = Arrays.stream(intArray).count();
long sum = Arrays.stream(intArray).sum();
double average = Arrays.stream(intArray).average().getAsDouble();
int max = Arrays.stream(intArray).max().getAsInt();
int min = Arrays.stream(intArray).min().getAsInt();
int first = Arrays.stream(intArray).findFirst().getAsInt();
OptionalDouble avgOptional = Arrays.stream(intArray).average();
double result = avgOptional.getAsDouble();
System.out.println("전체 요소의 평균값: " + result);
'[Codeit Review] > JCF와 Stream API' 카테고리의 다른 글
| [04] Stream API의 기본 연산 (0) | 2026.01.13 |
|---|---|
| [03] Stream API의 개념과 구조 (0) | 2026.01.13 |
| [02] 람다 표현식 (Lambda Expression) (0) | 2026.01.13 |
| [01] JCF (Java Collection Framework) (0) | 2026.01.13 |