3주차가 되니 1주차에 비해 실습 시간이 많이 늘어난 것이 느껴진다.
지금까지 배운 문법들을 바탕으로 코드를 확장하고,
디자인 패턴을 적용해보면서
현실의 개념을 점점 더 구조적으로 표현해가는 과정이 흥미롭다.
JS와 비슷하면서도 다른 부분들이 있어 헷갈릴 때도 있지만,
복습을 통해 차근차근 익숙해져 가면 될 것 같다.
1. Enum 활용 - 할인 정책을 객체로 표현하기
요구사항 다시 보기
- 할인 정책은 다음 3가지
- FIXED: 고정 1,000원 할인
- PERCENT: 10% 할인
- NONE: 할인 없음
- 각 정책은 서로 다른 할인 로직을 가진다
구현 코드
public enum DiscountPolicy {
FIXED("고정 금액 1000 할인") {
@Override
public int applyDiscount(int price) {
return price - 1000;
}
},
PERCENT("10% 할인") {
@Override
public int applyDiscount(int price) {
return (int) (price * 0.9);
}
},
NONE("할인 없음") {
@Override
public int applyDiscount(int price) {
return price;
}
};
private final String description;
DiscountPolicy(String description) {
this.description = description;
}
public String getDescription() {
return description;
}
public abstract int applyDiscount(int price);
}
2. DiscountPolicy Enum의 의미
위의 Enum은 상수묶음 이상의 역할을 한다.
할인 정책(행동)을 캡슐화한 Enum
- FIXED, PERCENT, NONE는
- 값이면서
- 동시에 서로 다른 로직을 가진 객체
👉 if / switch 없이
👉 정책을 객체로 분리한 구조
헷갈리는 문법: Enum 상수 뒤의 { ... }
FIXED("고정 1,000원 할인") {
@Override
public int applyDiscount(int price) {
return price - 1000;
}
}
이 문법의 정체는? Enum 상수별 익명 클래스(anonymous class)
컴파일러는 아래처럼 이해한다:
DiscountPolicy FIXED =
new DiscountPolicy("고정 1,000원 할인") {
@Override
public int applyDiscount(int price) {
return price - 1000;
}
};
- Enum 상수 하나 = DiscountPolicy의 하위 클래스 인스턴스
- 그래서 상수마다 메서드를 다르게 구현 가능
override 대신 람다로 구현한다면?
가능하다. (사실 이전 포스트에서 다뤘다)
단, 조건이 있는데
행동을 함수형 인터페이스로 분리해야 한다
람다 기반 Enum으로 재작성
import java.util.function.IntUnaryOperator;
public enum DiscountPolicy {
FIXED("고정 1,000원 할인", price -> price - 1000),
PERCENT("10% 할인", price -> (int)(price * 0.9)),
NONE("할인 없음", price -> price);
private final String description;
private final IntUnaryOperator operator;
DiscountPolicy(String description, IntUnaryOperator operator) {
this.description = description;
this.operator = operator;
}
public String getDescription() {
return description;
}
public int applyDiscount(int price) {
return operator.applyAsInt(price);
}
}
3. BigInteger - 아주 큰 정수 다루기
BigInteger란?
- int, long 범위를 초과하는 임의 정밀도 정수
- 메모리가 허용하는 한 크기 제한 없음
- 불변(immutable) 객체
BigInteger big1 = new BigInteger("12345678901234567890");
BigInteger big2 = BigInteger.valueOf(100);
BigInteger sum = big1.add(big2);
BigInteger power = big2.pow(100);
BigDecimal — 정확한 소수 계산
왜 필요한가?
0.1 + 0.2 == 0.3 // false (double)
👉 금융, 금액 계산에서 double은 부정확
BigDecimal 특징
- 정확한 소수 계산
- 스케일(소수점 자리수) 제어 가능
- 불변 객체
- 연산자는 사용 불가 (메서드 호출)
BigDecimal price = new BigDecimal("19.99");
BigDecimal quantity = new BigDecimal("3");
BigDecimal total = price.multiply(quantity);
BigDecimal result =
total.divide(new BigDecimal("7"), 2, RoundingMode.HALF_UP);
주의사항 1 - divide 예외
bd1.divide(bd2);
❌ ArithmeticException 발생
이유
- 나눗셈 결과가 무한 소수
- BigDecimal은 정확한 결과를 기본으로 요구
해결
bd1.divide(bd2, RoundingMode.HALF_UP);
반올림 전략을 명시해야 함
주의사항 2 - equals vs compareTo
BigDecimal n1 = new BigDecimal("1.0");
BigDecimal n2 = new BigDecimal("1.00");
n1.equals(n2); // false
n1.compareTo(n2); // 0
- equals: 값 + 스케일 비교
- compareTo: 수학적 값만 비교
👉 금액 비교는 compareTo 권장
4. Lombok - 보일러플레이트 제거 도구
Lombok이란?
- Getter, Setter, equals 등 반복 코드 자동 생성
- 어노테이션 기반
- 컴파일 시점에 코드 생성
“컴파일 시점에 동작한다”는 의미
- 실행 중에 동작 ❌
- 컴파일 단계에서 소스/바이트코드를 수정
- 애노테이션 프로세서(annotation processor)가 핵심
module-info.java 와 Lombok 충돌 원인
- module-info.java 존재 → 모듈 프로젝트
- Lombok은 전통적인 Classpath + annotation processing 흐름을 기대
- Eclipse에서 모듈 경로 + Lombok 설정이 꼬이면:
- import문이 정상적으로 인식되지 않고
- 어노테이션도 당연히 동작하지 않는다
👉 module-info.java 제거 → Classpath 기반 → 정상 동작
@EqualsAndHashCode
equals()와 hashCode()는 해시 기반 컬렉션(HashSet, HashMap)에서 항상 같이 맞물려 동작한다.
equals()가 true면 hashCode()도 같아야 한다는 규칙을 어기면, 중복 제거/조회가 깨지는 버그가 생길 수 있다.
그래서 Lombok은 두 메서드를 “세트”로 안전하게 자동 생성해주는 @EqualsAndHashCode를 제공한다.
기본 설정은 모든 필드를 비교 기준으로 삼기 때문에 위험할 수 있다.
“pno가 같으면 동일한 Person”이라는 규칙일땐 다음처럼 특정 필드만 비교 기준으로 잡을 수 있다.
@EqualsAndHashCode(of = "pno")
public class Person {
private int pno;
private String name;
private String mbti;
}
Lombok 남용 시 주의점
- 모든 필드 비교 → 도메인 규칙 흐려짐
- 무분별한 Setter → 불변성 깨짐
Lombok이 잘 어울리는 경우
- DTO (Data Transfer Object)
- VO (Value Object)
5. 객체 생성 패턴
1. 정적 팩토리 메서드
- 생성자 대신 static 메서드
- 이름으로 의도 표현
- 반환 타입 유연
// ❌ 생성자만 쓸 때 (의미가 안 보임)
User user = new User("jc", true);
// ✅ 정적 팩토리 메서드 사용
public class User {
private final String name;
private final boolean admin;
private User(String name, boolean admin) {
this.name = name;
this.admin = admin;
}
public static User normal(String name) {
return new User(name, false);
}
public static User admin(String name) {
return new User(name, true);
}
}
// ✅ 직관적인 객체 생성
User user1 = User.normal("jc");
User user2 = User.admin("root");
2. 빌더 패턴
- 필드 많을 때 유용
- @Builder 활용 가능(선언 위치에 따라 통제가 달라짐)
- 클래스에 붙이면 모든 필드를 대상으로 빌더가 생성되며,
- 생성자에 붙이면 “생성자 파라미터로 노출된 필드만” 빌더에 포함
👉 외부 노출을 통제하고 필수값 검증 로직을 생성자에서 설계
public class User {
private final String name;
private final int age;
private final String city;
private User(Builder b) {
this.name = b.name;
this.age = b.age;
this.city = b.city;
}
public static class Builder {
...
public Builder(String name, int age) {
this.name = name;
this.age = age;
}
public Builder city(String city) {
this.city = city;
return this;
}
public User build() {
return new User(this);
}
}
}
}
// ✅ 사용
User user = new User.Builder("jc", 30)
.city("Seoul")
.build();
6. Maven
Maven이란?
- 빌드 + 의존성 관리 도구
- 라이브러리 다운로드/버전 관리 자동화
pom.xml 역할
- 프로젝트 설정 파일
- 의존성, 플러그인, 빌드 설정 정의
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.42</version>
</dependency>
👉 jar 직접 추가보다 훨씬 안전하고 편리
'Learning Log' 카테고리의 다른 글
| [멋사 클라우드 5기] Day 11 - DBMS 기초(2), DQL (0) | 2026.02.05 |
|---|---|
| [멋사 클라우드 5기] Day 10 - 객체 다루기, DBMS 기초(1) (0) | 2026.02.04 |
| [멋사 클라우드 5기] Day 8 - 입출력, 직렬화, 스레드, Enum (0) | 2026.02.02 |
| [멋사 클라우드 5기] Day 7 - Lambda · Stream 심화 + Optional (0) | 2026.01.30 |
| [멋사 클라우드 5기] Day 6 - 자료구조, Lambda, Stream (0) | 2026.01.29 |
