이펙티브 자바 완벽 공략 1부

아이템 14. Comparable을 구현할지 고민하라

아이템 14. Comparable을 구현할지 고민하라


아이템 14. 핵심 정리 1 - Comparable 규약

아이템 14. Comparable을 구현할지 고민하라

ComparableObject가 직접 제공하는 메서드는 아니지만, 자바 컬렉션 프레임워크와 정렬 시스템에서 매우 중요한 역할을 하는 인터페이스다.

특히:

  • TreeSet
  • TreeMap
  • Collections.sort()
  • Arrays.sort()

같은 정렬 기반 기능들은 대부분 Comparable에 의존한다.

즉:

객체의 자연스러운 순서(Natural Order)

를 정의하는 인터페이스라고 이해하면 된다.


Comparable이란?

Comparable은 다음과 같은 형태를 가진다.

public interface Comparable<T> {
    int compareTo(T o);
}

compareTo의 의미

현재 객체와 전달받은 객체를 비교해서:

  • 음수 → 현재 객체가 더 작다
  • 0 → 같다
  • 양수 → 현재 객체가 더 크다

를 의미한다.


예시

BigDecimal n1 = new BigDecimal("1");
BigDecimal n2 = new BigDecimal("2");

System.out.println(n1.compareTo(n2));

결과

음수

중요한 점

여기서 절대:

반드시 -1이 나오겠지

라고 기대하면 안 된다.


Comparable 규약은 이렇게 정의한다

음수 / 0 / 양수

만 보장한다.

즉:

  • -1
  • -100
  • Integer.MIN_VALUE

모두 가능하다.


compareTo 구현 시 반드시 지켜야 하는 규약


1. 반사성(Reflexivity)

자기 자신과 비교하면 같아야 한다.

x.compareTo(x) == 0

의미

자기 자신은 자기 자신과 같은 순서여야 한다.

너무 당연한 규칙이다.


2. 대칭성(Symmetry)

x.compareTo(y)

가 양수라면:

y.compareTo(x)

는 음수여야 한다.


예시

10 > 5

라면:

5 < 10

이어야 한다.


즉 관계가 뒤집혀야 한다

한쪽이 크면 반대쪽은 작아야 한다.


3. 추이성(Transitivity)

가장 중요한 규약 중 하나다.


예시

A > B
B > C

라면:

A > C

여야 한다.


깨지면 어떤 일이 발생할까?

정렬 알고리즘이 무너진다.

즉:

  • TreeSet
  • TreeMap
  • sort()

같은 자료구조가 비정상 동작할 수 있다.


실제로는 매우 위험하다

추이성이 깨진 compareTo는:

  • 무한 루프
  • 잘못된 정렬
  • 데이터 유실

같은 심각한 문제를 만들 수 있다.


4. compareTo와 equals의 일관성

이 부분이 가장 헷갈리는 규약이다.


일반적으로 권장되는 규칙

x.compareTo(y) == 0

이면:

x.equals(y) == true

가 되는 것이 좋다.


하지만 반드시 강제되지는 않는다

대표적인 예외가 바로:

BigDecimal

이다.


BigDecimal 예시

new BigDecimal("2.0")
new BigDecimal("2.00")

compareTo 결과

compareTo == 0

즉 같은 값으로 본다.


equals 결과

equals == false

왜 다를까?

BigDecimalequals는:

스케일(scale)

까지 비교하기 때문이다.

즉:

2.0 != 2.00

로 본다.


그런데 compareTo는?

순수 숫자 값만 비교한다.

즉:

2.0 == 2.00

로 판단한다.


왜 이런 설계를 했을까?

BigDecimal은 단순 숫자가 아니라:

정밀도와 표현 방식

까지 중요한 클래스이기 때문이다.


예시

2.0 / 3  = 0.7
2.00 / 3 = 0.67

처럼 결과 정밀도가 달라질 수 있다.


그래서 Effective Java는 권장한다

만약:

compareTo == 0

인데:

equals == false

인 경우가 있다면:


반드시 문서화하라

실제로 BigDecimal 문서에도 명시되어 있다.


Comparable의 가장 큰 장점


1. 정렬 가능

Collections.sort(list)

가능.


2. TreeSet 사용 가능

TreeSet<PhoneNumber>

가능.


3. 타입 안정성

Comparable은 제네릭 기반이다.

Comparable<PhoneNumber>

장점

컴파일 시점 타입 체크 가능.

즉:

잘못된 타입 비교 방지

가 가능하다.


compareTo 구현 시 추천 방식

예전에는 보통:

if (x < o.x) return -1;
if (x > o.x) return 1;
return 0;

처럼 구현했다.


최신 방식

return Integer.compare(x, o.x);

여러 필드 비교 시

Comparator
    .comparing(Person::getName)
    .thenComparing(Person::getAge)

같은 방식 사용 가능.


핵심 정리

  • Comparable은 객체의 자연 순서를 정의한다
  • compareTo는 음수/0/양수를 반환한다
  • 구체적인 숫자 값 자체에 의존하면 안 된다
  • 반사성/대칭성/추이성을 만족해야 한다
  • compareTo와 equals는 가능하면 일관성 유지
  • BigDecimal은 대표적인 예외 사례다
  • 정렬 컬렉션은 compareTo에 강하게 의존한다

한 줄 정리

Comparable은 단순 비교 기능이 아니라, “객체의 자연 질서(Natural Order)”를 정의하는 계약이다.


아이템 14. 핵심 정리 1 - Comparable 구현 방법 1

아이템 14. Comparable 구현 방법과 컴포지션 활용하기

이전 글에서는 Comparable 인터페이스가 무엇인지, 그리고 compareTo()가 지켜야 하는 규약들에 대해 정리했다. 이번에는 실제로 우리가 만든 클래스에서 어떻게 Comparable을 구현하는지, 그리고 왜 상속보다 컴포지션이 더 좋은 선택이 되는지 살펴보자.


Comparable 구현하기

우리가 만든 클래스에 자연스러운 순서(Natural Order)를 부여하고 싶다면 가장 먼저 해야 할 일은 Comparable 인터페이스를 구현하는 것이다.

예를 들어 PhoneNumber라는 클래스가 있다고 해보자.

public class PhoneNumber implements Comparable<PhoneNumber> {
}

여기서 중요한 점은:

Comparable<PhoneNumber>

처럼 제네릭 타입을 지정한다는 것이다.


Comparable은 제네릭 인터페이스다

Comparable은 다음과 같이 선언되어 있다.

public interface Comparable<T> {
    int compareTo(T o);
}

여기서 T는 특별한 의미를 가진 클래스가 아니다.

그냥:

비교 대상 타입

을 의미하는 제네릭 타입 변수일 뿐이다.


왜 제네릭이 중요한가?

예전 equals()는 이런 형태였다.

boolean equals(Object obj)

즉 항상 Object를 받아야 했다.

그래서:

  • 형변환 필요
  • instanceof 검사 필요
  • 타입 안정성 부족

같은 문제가 있었다.


compareTo는 다르다

int compareTo(PhoneNumber o)

처럼:

비교 대상 타입이 명확하다

라는 엄청난 장점이 있다.

즉:

  • 컴파일 타임 타입 체크 가능
  • 불필요한 형변환 제거
  • 코드 안정성 증가

라는 이점을 가진다.


compareTo 메서드 구현하기

인터페이스를 구현하면 IDE가 자동으로 메서드를 생성해준다.

@Override
public int compareTo(PhoneNumber o) {
    return 0;
}

@Override는 꼭 붙이자

@Override는 단순 장식이 아니다.

컴파일러에게:

이 메서드는 오버라이딩이다

라고 알려주는 역할을 한다.


장점

만약 메서드 시그니처가 틀리면:

comparetoo(...)

같은 오타를 컴파일 타임에 바로 잡아준다.

즉:

컴파일러가 실수를 검증해주는 장치

인 셈이다.


compareTo 구현 방법

PhoneNumber에는:

short areaCode;
short prefix;
short lineNum;

같은 필드가 있다고 가정해보자.


비교 우선순위를 정해야 한다

정렬은 결국:

무엇을 먼저 비교할 것인가?

의 문제다.


전화번호라면 보통

  1. areaCode
  2. prefix
  3. lineNum

순으로 비교하는 것이 자연스럽다.


구현 예시

@Override
public int compareTo(PhoneNumber o) {
    int result = Short.compare(areaCode, o.areaCode);

    if (result == 0) {
        result = Short.compare(prefix, o.prefix);

        if (result == 0) {
            result = Short.compare(lineNum, o.lineNum);
        }
    }

    return result;
}

왜 이렇게 구현할까?

정렬 기준이 여러 개일 때는:

앞의 비교 결과가 같을 때만
다음 필드를 비교

해야 하기 때문이다.


compare 메서드 사용하기

Primitive Wrapper들은 비교 메서드를 제공한다.

예:

Integer.compare(...)
Short.compare(...)
Long.compare(...)

장점

직접:

if (a < b)

같은 코드를 쓰는 것보다:

  • 안전
  • 가독성 좋음
  • overflow 위험 감소

라는 장점이 있다.


상속이 문제를 만드는 순간

이제 중요한 부분이다.


Point 클래스가 있다고 해보자

class Point implements Comparable<Point>

그리고:

x  먼저 비교
y  나중 비교

하도록 구현되어 있다고 하자.


NamedPoint를 만들고 싶다

class NamedPoint extends Point

그리고:

좌표가 같다면 이름까지 비교하고 싶다

고 생각할 수 있다.


문제 발생

이런 생각을 하게 된다.

compareTo(NamedPoint o)

를 새로 만들면 되지 않을까?


하지만 이건 오버라이딩이 아니다

이건:

오버로딩(overloading)

이다.


왜?

상위 클래스는:

compareTo(Point o)

를 가지고 있다.

그런데 하위 클래스는:

compareTo(NamedPoint o)

를 만들었기 때문이다.


파라미터 타입이 다르다

즉:

다른 메서드

가 되어버린다.


결과적으로 다형성이 깨진다

오버라이딩된 메서드만 다형성이 적용된다.

오버로딩은 아니다.

즉:

의도한 compareTo 확장이 실패

한다.


해결 방법: 컴포지션 사용하기

이 문제를 해결하는 가장 좋은 방법이 바로:

컴포지션(Composition)

이다.


상속 대신 필드로 가진다

class NamedPoint implements Comparable<NamedPoint> {

    private final Point point;
    private final String name;
}

장점

이제 NamedPoint는:

Point를 상속받지 않는다

따라서:

Comparable<NamedPoint>

를 자유롭게 구현할 수 있다.


compareTo 구현

@Override
public int compareTo(NamedPoint o) {

    int result = point.compareTo(o.point);

    if (result == 0) {
        result = name.compareTo(o.name);
    }

    return result;
}

훨씬 깔끔하다

이 방식은:

  • equals 규약도 지키기 쉽고
  • compareTo 확장도 자연스럽고
  • 다형성 문제도 발생하지 않는다

왜 Effective Java가 컴포지션을 강조할까?

상속은:

기존 계약(contract)에 묶인다

라는 문제가 있다.

특히:

  • equals
  • hashCode
  • compareTo

같은 규약 기반 메서드는 상속과 굉장히 충돌하기 쉽다.


그래서 Effective Java의 핵심 철학 중 하나

상속보다 컴포지션을 우선하라.


Java 8 이후 더 좋아진 Comparator

Java 8부터는:

Comparator.comparing(...)

같은 기능들이 추가되면서 구현이 훨씬 간결해졌다.

예:

Comparator
    .comparing(PhoneNumber::getAreaCode)
    .thenComparing(PhoneNumber::getPrefix)
    .thenComparing(PhoneNumber::getLineNum);

이 부분은 이후 Comparator를 다루면서 더 자세히 살펴보게 된다.


핵심 정리

  • Comparable은 자연 순서를 정의하는 인터페이스다
  • 제네릭 기반이라 타입 안정성이 높다
  • compareTo는 음수/0/양수를 반환해야 한다
  • 비교 우선순위를 명확히 정해야 한다
  • 여러 필드는 순차 비교 방식 사용
  • 상속으로 compareTo를 확장하려 하면 문제가 생긴다
  • equals 문제처럼 compareTo도 컴포지션이 훨씬 안전하다
  • Java 8 이후에는 Comparator API 활용이 권장된다

한 줄 정리

Comparable은 단순 비교 기능이 아니라 “객체의 자연 질서”를 정의하는 계약이며, 확장이 필요할 때는 상속보다 컴포지션이 훨씬 안전하다.

아이템 14. 핵심 정리 1 - Comparable 구현 방법 2

아이템 14. Comparator를 활용한 compareTo 구현 방법

이전 글에서는 Comparable 인터페이스를 직접 구현해서 compareTo()를 만드는 방법을 살펴봤다. 이번에는 Java 8부터 추가된 Comparatorstatic 메서드와 default 메서드를 활용해서 조금 더 선언적이고 읽기 좋은 방식으로 비교 로직을 구현하는 방법을 정리해보자.


Java 8 이후 Comparator가 달라졌다

Java 8 이전에는 Comparator를 사용하려면 보통 이렇게 구현했다.

Comparator<Person> comparator = new Comparator<Person>() {
    @Override
    public int compare(Person o1, Person o2) {
        return o1.getName().compareTo(o2.getName());
    }
};

지금 보면 상당히 장황하다.


Java 8 이후의 변화

Java 8부터는 다음 기능들이 인터페이스에도 추가되었다.

  • 람다(Lambda)
  • 메서드 레퍼런스(Method Reference)
  • default method
  • static method

덕분에 Comparator 구현 방식도 훨씬 간결해졌다.


핵심 아이디어

핵심은 이것이다.

Comparator를 먼저 만든 뒤,
compare() 결과를 그대로 compareTo에서 반환한다.

즉:

@Override
public int compareTo(Person o) {
    return COMPARATOR.compare(this, o);
}

처럼 구현할 수 있다.


Comparator 생성하기

가장 대표적인 메서드는 다음이다.

Comparator.comparing(...)

comparing() 사용 예시

Comparator<Person> comparator =
        Comparator.comparing(Person::getName);

의미

이 코드는:

Person 객체에서 name 값을 꺼내서
그 값 기준으로 비교하겠다

라는 뜻이다.


메서드 레퍼런스란?

여기서 사용된:

Person::getName

형태를 메서드 레퍼런스(Method Reference)라고 부른다.


람다와 동일한 의미

다음 코드와 완전히 동일하다.

person -> person.getName()

즉:

  • 람다
  • 메서드 레퍼런스

둘 다 함수형 인터페이스 구현체를 전달하는 방식일 뿐이다.


Natural Order 사용

Comparator.comparing(...)은 꺼내온 값의 자연 순서(Natural Order)를 사용한다.

예를 들어:

Comparator.comparing(Person::getName)

이면 내부적으로:

String.compareTo()

가 호출된다.

왜냐하면 String은 이미 Comparable<String>을 구현하고 있기 때문이다.


Natural Order를 사용하지 않는 방법

두 번째 인자로 별도의 Comparator를 넘길 수도 있다.

Comparator.comparing(
    Person::getName,
    String.CASE_INSENSITIVE_ORDER
);

의미

위 코드는:

대소문자를 무시하고 문자열 비교

를 수행한다.


compareTo 구현 예시

private static final Comparator<Person> COMPARATOR =
        Comparator.comparing(Person::getLastName)
                  .thenComparing(Person::getFirstName)
                  .thenComparingInt(Person::getAge);

@Override
public int compareTo(Person o) {
    return COMPARATOR.compare(this, o);
}

thenComparing()

이 부분이 Java 8 Comparator API의 핵심 중 하나다.

.thenComparing(...)

의미

앞 비교 결과가 같다면
다음 기준으로 비교한다

는 뜻이다.


선언형 코드의 장점

기존 방식은:

if (result == 0)

같은 코드가 반복되었다.

하지만 Comparator 체이닝 방식은:

성 → 이름 → 나이 순으로 비교

같은 비교 의도가 코드에 그대로 드러난다.

즉 코드가 훨씬 선언적(Declarative)으로 변한다.


Primitive 타입 전용 메서드

Comparator는 primitive 타입 최적화를 위한 메서드도 제공한다.

comparingInt(...)
comparingLong(...)
comparingDouble(...)

왜 따로 존재할까?

불필요한 Boxing / Unboxing 비용을 줄이기 위해서다.

즉:

Integer
Long
Double

객체 생성 비용을 줄여준다.


static method와 default method

여기서 중요한 개념이 하나 더 나온다.


static method

Comparator.comparing(...)

처럼:

인스턴스 없이 호출

하는 메서드다.


default method

.thenComparing(...)

처럼:

생성된 Comparator 인스턴스 뒤에 체이닝

해서 사용하는 메서드다.


static import 사용 가능

원래는:

Comparator.comparing(...)

이라고 써야 한다.

하지만:

import static java.util.Comparator.comparing;

을 사용하면:

comparing(...)

처럼 더 짧게 작성할 수 있다.


하지만 팀 컨벤션도 중요하다

짧다고 항상 좋은 것은 아니다.

예를 들어:

comparing(...)

만 보면:

이 메서드가 어디서 온 거지?

헷갈릴 수 있다.

그래서 실무에서는 팀 스타일에 따라:

Comparator.comparing(...)

처럼 클래스명을 명시하는 경우도 많다.


성능은 어떨까?

책에서는 이 방식이 직접 구현보다 약간 느릴 수 있다고 설명한다.

이유는:

  • Comparator 객체 생성
  • 람다 호출
  • 메서드 체이닝

같은 추가 작업이 있기 때문이다.


현실에서는?

대부분의 애플리케이션은:

  • DB
  • 네트워크
  • I/O

가 병목이다.

즉 Comparator 체이닝 비용은 대부분 체감하기 어려운 수준이다.


언제 성능을 고민해야 할까?

다음 상황이라면 직접 비교 구현도 고려할 수 있다.

  • 대량 정렬
  • 초고빈도 compare 연산
  • CPU 병목 중심 애플리케이션

이 경우에는 직접 구현과 Comparator 기반 구현을 실제로 벤치마킹해보고 선택하면 된다.


결국 중요한 것은 가독성

Comparator 기반 구현의 가장 큰 장점은:

읽기 쉽다

는 점이다.

.thenComparing(...)

만 봐도:

아, 같으면 다음 필드를 비교하는구나

를 즉시 이해할 수 있다.


Effective Java 관점에서의 핵심

조슈아 블록이 강조하는 핵심은 이것이다.

compareTo는 단순 비교 메서드가 아니라:

객체의 자연 순서를 표현하는 계약(contract)

이라는 점이다.

그리고 Java 8 이후에는 그 계약을 훨씬 더:

  • 안전하게
  • 읽기 좋게
  • 선언적으로

구현할 수 있게 되었다.


핵심 정리

  • Java 8부터 Comparator API가 크게 강화되었다
  • Comparator.comparing()으로 쉽게 비교기 생성 가능
  • thenComparing()으로 비교 기준 체이닝 가능
  • 메서드 레퍼런스를 활용하면 코드가 간결해진다
  • comparingInt() 등 primitive 최적화 메서드도 존재한다
  • static method + default method 조합이 핵심이다
  • 성능보다 가독성과 유지보수성이 더 중요한 경우가 많다
  • compareTo 구현도 점점 선언형 스타일로 진화하고 있다

한 줄 정리

Java 8 이후의 Comparator API는 compareTo 구현을 “조건문 중심 코드”에서 “의도를 표현하는 선언형 코드”로 바꾸어 놓았다.

아이템 14. 완벽 공략

이번 시간은 Comparable 아이템의 마지막 정리 시간이다. 앞에서 Comparable, Comparator, compareTo() 구현 방식까지 살펴봤다면, 이번에는 그 배경에 깔린 중요한 개념들을 조금 더 깊게 정리해보자.

특히 이번 내용은 단순히 Comparable만의 이야기가 아니라:

  • 컴파일 타임 vs 런타임
  • 제네릭(Generic)
  • 타입 추론(Type Inference)
  • 오버플로우(Overflow)
  • 부동소수점(Floating Point)

같은 자바의 핵심 개념들과도 연결된다.


Comparable의 진짜 장점은 제네릭이다

책에서도 강조하는 내용이 있다.

Comparable은 제네릭 인터페이스다

이게 왜 중요할까?


compareTo의 인수 타입은 컴파일 타임에 결정된다

Comparable은 이렇게 선언되어 있다.

public interface Comparable<T> {
    int compareTo(T o);
}

예를 들어:

class PhoneNumber implements Comparable<PhoneNumber>

라고 선언하면:

compareTo(PhoneNumber o)

만 허용된다.


이것이 왜 중요한가?

핵심은:

문제를 실행 전에 발견할 수 있다

는 점이다.


컴파일 타임 vs 런타임

이 개념은 굉장히 중요하다.


컴파일 타임(Compile Time)

코드가 실행되기 전
컴파일되는 시점

런타임(Run Time)

프로그램이 실제로 실행되는 시점

가능한 한 문제는 컴파일 타임에 발견되어야 한다

왜냐하면:

  • 더 빠르게 발견 가능
  • IDE가 즉시 알려줌
  • 실행 전에 수정 가능
  • 디버깅 비용 감소

하기 때문이다.


equals(Object)의 한계

기존 equals는:

boolean equals(Object obj)

형태였다.

즉:

모든 타입 허용

이다.


그래서 내부에서 이런 코드가 필요했다

if (!(obj instanceof PhoneNumber))

문제점

잘못된 타입이 들어와도:

컴파일은 성공

한다.

즉 문제를:

런타임에서야 발견 가능

하다.


하지만 Comparable은 다르다

Comparable<PhoneNumber>

를 선언하는 순간:

PhoneNumber끼리만 비교 가능

해진다.


잘못된 타입 전달 시

phone.compareTo(person)

같은 코드는:

컴파일 에러

가 발생한다.


즉 제네릭의 핵심 장점

타입 안정성(Type Safety)

이다.


IDE가 더 강력해진다

IntelliJ 같은 IDE가:

  • 자동완성
  • 타입 추론
  • 실시간 에러 검출

을 훨씬 잘 해줄 수 있다.


타입 추론(Type Inference)

책에서는 타입 추론 이야기도 나온다.


타입 추론이란?

컴파일러가:

문맥(Context)

을 보고 타입을 스스로 유추하는 기능이다.


예시

Comparator.comparing(PhoneNumber::getPrefix)

여기서 컴파일러는:

아 이건 PhoneNumber구나

를 스스로 추론한다.


왜 가능할까?

이미 앞 문맥에서:

Comparable<PhoneNumber>

같은 정보가 존재하기 때문이다.


타입 추론의 장점

예전 자바는 타입을 너무 많이 반복해야 했다.

하지만 타입 추론 덕분에:

  • 코드 길이 감소
  • 가독성 증가
  • 중요한 로직 집중 가능

해졌다.


하지만 타입 추론은 완벽하지 않다

어떤 경우에는:

컴파일러가 추론 실패

를 한다.

그럴 땐 우리가 직접 타입을 명시해야 한다.


즉 중요한 건 균형이다

컴파일러가 충분히 이해 가능한 코드

를 작성하는 것이 중요하다.


compareTo 구현 시 가장 위험한 실수

책에서 매우 중요하게 경고하는 부분이다.


절대 이렇게 구현하지 마라

return this.value - o.value;

얼핏 보면 좋아 보인다

  • 음수 → 작다
  • 양수 → 크다
  • 0 → 같다

니까.


하지만 치명적인 문제가 있다

바로:

정수 오버플로우(Integer Overflow)

다.


예시

int x = Integer.MIN_VALUE;
int y = 10;

계산

x - y

문제 발생

Integer.MIN_VALUE는 이미 표현 가능한 최소값이다.

여기서 더 작아지면:

오버플로우 발생

한다.


결과

원래는:

음수

가 나와야 하는데:

양수

가 나올 수도 있다.


그러면 compareTo 규약이 깨진다

즉:

실제로는 더 작은 객체인데
더 크다고 판단

할 수도 있다.


해결 방법

반드시:

Integer.compare(x, y)

사용.


장점

  • overflow 안전
  • 가독성 좋음
  • 의도가 명확함

Long.compare도 마찬가지

Long.compare(a, b)

사용 권장.


부동소수점(Floating Point) 문제

이 부분도 실무에서 굉장히 중요하다.


float / double은 정확한 숫자가 아니다

많은 사람들이 착각하는 부분이다.


왜 오차가 생길까?

컴퓨터 메모리는 유한하다.

즉:

모든 실수를 정확히 저장 불가능

하다.


대표적인 예시

System.out.println(0.1 + 0.2);

결과

0.30000000000000004

왜?

0.1 자체를 이진수로 정확히 표현할 수 없기 때문이다.


compareTo 구현 시 문제점

return (int)(a - b);

같은 방식은:

  • 정밀도 손실
  • 반올림 문제
  • 오차 누적

을 만들 수 있다.


금융 계산에서는 절대 사용 금지

예:

  • 돈 계산
  • 주식 가격
  • 코인 가격
  • 정산 시스템
  • PG 시스템

왜 위험할까?

아주 작은 오차가:

수백만 건 누적

되면 엄청난 문제가 된다.


해결 방법

BigDecimal

사용.


왜 BigDecimal인가?

  • 임의 정밀도 지원
  • 정확한 소수 계산 가능
  • 금융 계산 안정성 확보

실무에서 정말 중요하다

특히:

  • 결제 시스템
  • PG
  • 정산
  • 환율
  • 포인트 시스템

에서는 거의 필수 수준이다.


이번 아이템의 진짜 핵심

사실 이번 아이템은 단순 Comparable 이야기가 아니다.


결국 배우는 것은 이것들이다

  • 타입 안정성
  • 제네릭
  • 컴파일 타임 검증
  • 안전한 비교
  • 오버플로우
  • 부동소수점 오차
  • 실무적인 숫자 처리

Effective Java가 좋은 이유

단순 API 사용법만 알려주지 않는다.

왜 그렇게 해야 하는가?

를 설명한다.


핵심 정리

  • Comparable은 제네릭 기반 인터페이스다
  • compareTo 타입은 컴파일 타임에 결정된다
  • 컴파일 타임 검증은 런타임 오류를 크게 줄인다
  • 타입 추론은 자바 코드를 훨씬 간결하게 만든다
  • compareTo를 단순 뺄셈으로 구현하면 위험하다
  • Integer.compare 사용이 안전하다
  • float/double은 정확한 계산이 아니다
  • 금융 계산에는 BigDecimal 사용 권장

한 줄 정리

Comparable은 단순 정렬 기능이 아니라, 타입 안정성과 안전한 비교 연산까지 포함하는 자바의 중요한 계약이다.


© 2020. All rights reserved.

SIKSIK