기본형(primitive type)과 래퍼 클래스(wrapper class)의 차이
🔹 double (기본형, primitive type)
- 8바이트(64비트) 크기의 부동소수점(floating-point) 타입.
- 메모리 사용이 적고 연산 속도가 빠름.
- null을 저장할 수 없음.
- Java의 기본 데이터 타입이라서 객체가 아니라 값 자체를 저장함.
예제:
double pi = 3.14;
double result = pi * 2;
double은 산술 연산이 빠르지만, 객체처럼 사용하지 못함.
🔹 Double (래퍼 클래스, Wrapper Class)
- double을 객체로 감싸는 래퍼 클래스.
- Java의 객체 타입으로 null을 저장할 수 있음.
- double과 달리 컬렉션(List, Map 등)에 저장 가능.
- 메서드를 제공 (Double.valueOf(), Double.parseDouble() 등).
예제:
Double num = 3.14; // 오토 박싱
Double nullValue = null; // 가능
double primitive = num; // 오토 언박싱
🔹 주요 차이점 정리
구분 | double (기본형) | Double (래퍼 클래스) |
메모리 사용 | 적음 | 많음 (객체 할당) |
연산 속도 | 빠름 | 느림 (객체 관리) |
null 허용 여부 | ❌ 불가능 | ✅ 가능 |
객체 컬렉션 저장 | ❌ 불가능 | ✅ 가능 |
메서드 지원 | ❌ 없음 | ✅ parseDouble(), valueOf() 등 |
🔹 언제 double vs Double 사용해야 할까?
- 연산이 많거나 성능이 중요한 경우 → double 사용
- 객체가 필요하거나 null 값이 가능해야 하는 경우 → Double 사용 (예: 데이터베이스 값)
보통 기본적으로 double을 사용하고, null이 필요한 경우나 컬렉션에 저장해야 할 때만 Double을 사용하면 됨.
Double(래퍼 클래스)이 필요한 경우 예시
1. 데이터베이스 연동 (nullable 허용)
- 데이터베이스에서 NULL 값을 허용해야 하는 경우, double(기본 타입)으로 선언하면 null을 저장할 수 없음.
- Double(래퍼 클래스)을 사용하면 null 처리가 가능함.
예시: JPA 엔티티에서 Double 사용
@Entity
public class Product {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
// 가격이 없는 경우 NULL을 저장할 수 있도록 Double 사용
private Double price;
// 기본 타입 사용 시 NULL을 허용하지 않음 (컴파일 오류 발생)
// private double price;
}
- 가격 정보가 없을 수도 있는 경우(null 허용) → Double 사용
2. JSON 직렬화/역직렬화 (null 허용)
- Spring Boot에서 REST API를 만들 때, double은 기본값이 0.0으로 설정되지만,
Double을 사용하면 null로 유지할 수 있음.
예시: JSON 요청 처리 (Double 사용)
@RestController
@RequestMapping("/products")
public class ProductController {
@PostMapping
public ResponseEntity<String> createProduct(@RequestBody ProductDTO productDTO) {
return ResponseEntity.ok("Product created with price: " + productDTO.getPrice());
}
}
@Data
public class ProductDTO {
private String name;
private Double price; // 클라이언트가 price를 생략할 경우 null이 될 수 있음
}
- {"name": "Laptop"} → price가 생략된 경우 null 유지 가능
3. 컬렉션(List, Map)에서 값이 없을 경우 null 허용
- double을 사용하면 기본적으로 0.0이 들어가기 때문에, 값이 없는 경우를 표현할 수 없음.
- Double을 사용하면 null을 명확하게 표현 가능함.
예시: 평균 점수 계산 (null 허용)
public class Student {
private String name;
private Double averageScore; // null 허용
public Student(String name, Double averageScore) {
this.name = name;
this.averageScore = averageScore;
}
public Double getAverageScore() {
return averageScore;
}
}
public class Main {
public static void main(String[] args) {
List<Student> students = List.of(
new Student("Alice", 85.5),
new Student("Bob", null), // 시험 미응시
new Student("Charlie", 92.0)
);
for (Student student : students) {
System.out.println(student.getAverageScore());
}
}
}
- Bob의 평균 점수가 null로 유지될 수 있음.
4. Optional과 함께 사용하여 값이 없는 경우 처리
- Optional<Double>을 사용하면 null 체크를 좀 더 안전하게 할 수 있음.
예시: Optional로 평균 점수 처리
public Optional<Double> getStudentScore(Long studentId) {
// 데이터베이스에서 조회했을 때 점수가 없을 수도 있음
Double score = findScoreById(studentId);
return Optional.ofNullable(score);
}
private Double findScoreById(Long studentId) {
// DB 조회 로직 (점수가 없으면 null 반환)
return null;
}
- double을 사용하면 null을 표현할 수 없지만, Double을 사용하면 Optional.ofNullable()로 감싸서 처리 가능
정리
사용 케이스 | double (기본 타입) | Double (래퍼 클래스) |
값이 null일 수 있음 | ❌ (기본값 0.0) | ✅ (null 가능) |
DB 연동 (@Entity) | ❌ (null 허용 불가) | ✅ (null 허용 가능) |
JSON 직렬화 (@RequestBody) | ❌ (0.0으로 처리됨) | ✅ (null 유지 가능) |
컬렉션(List, Map)에서 null 허용 | ❌ (0.0 저장됨) | ✅ (null 저장 가능) |
Optional 사용 가능 여부 | ❌ | ✅ (Optional<Double> 사용 가능) |
결론
- Double(래퍼 클래스)은 null을 허용해야 하는 경우 (DB 연동, JSON 처리, Optional 사용 등)에서 사용.
- double(기본 타입)은 절대 null이 될 수 없고, 성능상 원시 타입이 필요한 경우에 사용.
백엔드에서는 nullable한 데이터 처리 시에는 Double을 기본적으로 고려해야 함.
📌 빌더 패턴(Builder Pattern)이란?
빌더 패턴(Builder Pattern) 은 객체의 생성 과정에서 생성자의 매개변수가 많거나, 일부 값만 선택적으로 설정해야 할 때 유용한 패턴 입니다.
1️⃣ 왜 빌더 패턴을 사용할까? (생성자 vs. Setter vs. 빌더 비교)
1) 생성자를 이용한 객체 생성 (문제점)
public class Pizza {
private String size;
private boolean cheese;
private boolean pepperoni;
private boolean mushrooms;
public Pizza(String size, boolean cheese, boolean pepperoni, boolean mushrooms) {
this.size = size;
this.cheese = cheese;
this.pepperoni = pepperoni;
this.mushrooms = mushrooms;
}
}
// 객체 생성
Pizza pizza = new Pizza("Large", true, false, true);
🔹 문제점: 매개변수 개수가 많아지면 가독성이 떨어지고, 순서를 헷갈릴 위험이 있음.
2) Setter를 이용한 객체 생성 (문제점)
public class Pizza {
private String size;
private boolean cheese;
private boolean pepperoni;
private boolean mushrooms;
public void setSize(String size) { this.size = size; }
public void setCheese(boolean cheese) { this.cheese = cheese; }
public void setPepperoni(boolean pepperoni) { this.pepperoni = pepperoni; }
public void setMushrooms(boolean mushrooms) { this.mushrooms = mushrooms; }
}
// 객체 생성
Pizza pizza = new Pizza();
pizza.setSize("Large");
pizza.setCheese(true);
pizza.setPepperoni(false);
pizza.setMushrooms(true);
🔹 문제점: 객체가 완전히 생성되기 전에 불완전한 상태로 남을 수 있음 (Immutable X)
🔹 문제점: 객체의 일관성이 깨질 위험 이 있음.
3) 빌더 패턴을 이용한 객체 생성 (해결책)
public class Pizza {
private String size;
private boolean cheese;
private boolean pepperoni;
private boolean mushrooms;
// Private 생성자 (외부에서 직접 호출 X)
private Pizza(Builder builder) {
this.size = builder.size;
this.cheese = builder.cheese;
this.pepperoni = builder.pepperoni;
this.mushrooms = builder.mushrooms;
}
// Builder 클래스 (정적 내부 클래스)
public static class Builder {
private String size;
private boolean cheese = true; // 기본값
private boolean pepperoni = true;
private boolean mushrooms = true;
public Builder size(String size) {
this.size = size;
return this;
}
public Builder cheese(boolean cheese) {
this.cheese = cheese;
return this;
}
public Builder pepperoni(boolean pepperoni) {
this.pepperoni = pepperoni;
return this;
}
public Builder mushrooms(boolean mushrooms) {
this.mushrooms = mushrooms;
return this;
}
public Pizza build() {
return new Pizza(this);
}
}
public static Builder builder() {
return new Builder();
}
@Override
public String toString() {
return "Pizza{" +
"size='" + size + '\'' +
", cheese=" + cheese +
", pepperoni=" + pepperoni +
", mushrooms=" + mushrooms +
'}';
}
}
// 객체 생성
Pizza pizza = Pizza.builder()
.size("Large")
.cheese(true)
.pepperoni(false)
.mushrooms(true)
.build();
System.out.println(pizza);
✔ 장점:
✅ 가독성이 좋다 (어떤 값을 설정하는지 명확함)
✅ 불변성(Immutable) 보장 (객체 생성 후 값 변경 불가능)
✅ 필수값과 선택값을 분리 가능 (필수값만 설정하고, 나머지는 기본값 사용 가능)
2️⃣ 빌더 패턴이 유용한 경우
🔹 매개변수가 많을 때 → 생성자의 매개변수가 많으면 가독성이 떨어지므로 빌더 패턴이 적합함.
🔹 객체의 불변성을 유지해야 할 때 → 객체 생성 이후 값을 변경하지 않도록 할 때 유용함.
🔹 선택적 매개변수를 제공할 때 → 어떤 값만 선택적으로 설정하고 싶을 때 유용함.
3️⃣ Lombok의 @Builder 사용 (더 간단하게!)
Lombok을 사용하면 자동으로 빌더 패턴을 적용 할 수 있음.
import lombok.Builder;
import lombok.ToString;
@Builder
@ToString
public class Pizza {
private String size;
@Builder.Default
private boolean cheese = true;
@Builder.Default
private boolean pepperoni = true;
@Builder.Default
private boolean mushrooms = true;
}
// 객체 생성
Pizza pizza = Pizza.builder()
.size("Large")
.cheese(true)
.pepperoni(false)
.mushrooms(true)
.build();
System.out.println(pizza);
✔ 장점:
✅ 코드가 훨씬 짧아짐
✅ 빌더 자동 생성 → @Builder만 붙이면 됨
4️⃣ 정리 (빌더 패턴을 써야 하는 이유)
방식 | 장점 | 단점 |
생성자 | 한 번에 객체 생성 가능 | 매개변수가 많아지면 가독성↓, 실수 위험 |
Setter | 개별적으로 값 설정 가능 | 불변성 X, 일관성 깨질 위험 |
Builder | 가독성↑, 불변성 유지, 선택적 매개변수 설정 가능 | 코드가 조금 길어질 수 있음 |
✔ 결론: 매개변수가 많거나, 객체의 불변성을 유지하고 싶다면 빌더 패턴이 가장 좋은 선택임.
'멋쟁이사자처럼_부트캠프 > Spring' 카테고리의 다른 글
[멋쟁이사자처럼 부트캠프 TIL 회고] 백엔드 부트캠프 13기: Java 53일차 Spring Security (0) | 2025.02.27 |
---|---|
[멋쟁이사자처럼 부트캠프 TIL 회고] 백엔드 부트캠프 13기: Java 47일차 Spring Data JPA (0) | 2025.02.19 |
[멋쟁이사자처럼 부트캠프 TIL 회고] 백엔드 부트캠프 13기: Java 44일차 JPA (1) | 2025.02.14 |
[멋쟁이사자처럼 부트캠프 TIL 회고] 백엔드 부트캠프 13기: Java 43일차 EJB (0) | 2025.02.12 |
[멋쟁이사자처럼 부트캠프 TIL 회고] 백엔드 부트캠프 13기: Java 41일차 웹 프로그램 실습-1 (1) | 2025.02.07 |