From b05bec2b4f581de24f3d24953551e6ef58ee1698 Mon Sep 17 00:00:00 2001 From: cyunlee Date: Fri, 3 Jan 2025 11:25:46 +0900 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20=EC=9D=B4=ED=8E=99=ED=8B=B0?= =?UTF-8?q?=EB=B8=8C=20=EC=9E=90=EB=B0=94=20=EC=95=84=EC=9D=B4=ED=85=9C3,?= =?UTF-8?q?=206=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .idea/.gitignore | 3 + .idea/effective-java.iml | 9 ++ .idea/git_toolbox_blame.xml | 6 + .idea/misc.xml | 6 + .idea/modules.xml | 8 ++ .idea/vcs.xml | 6 + ...64\354\246\235\355\225\230\353\235\274.md" | 131 ++++++++++++++++++ ...4_\355\224\274\355\225\230\353\235\274.md" | 113 +++++++++++++++ 8 files changed, 282 insertions(+) create mode 100644 .idea/.gitignore create mode 100644 .idea/effective-java.iml create mode 100644 .idea/git_toolbox_blame.xml create mode 100644 .idea/misc.xml create mode 100644 .idea/modules.xml create mode 100644 .idea/vcs.xml create mode 100644 "2\354\236\245_\352\260\235\354\262\264_\354\203\235\354\204\261\352\263\274_\355\214\214\352\264\264/\354\225\204\354\235\264\355\205\234_03/private_\354\203\235\354\204\261\354\236\220\353\202\230_\354\227\264\352\261\260_\355\203\200\354\236\205\354\234\274\353\241\234_\354\213\261\352\270\200\355\204\264\354\236\204\354\235\204_\353\263\264\354\246\235\355\225\230\353\235\274.md" create mode 100644 "2\354\236\245_\352\260\235\354\262\264_\354\203\235\354\204\261\352\263\274_\355\214\214\352\264\264/\354\225\204\354\235\264\355\205\234_06/\353\266\210\355\225\204\354\232\224\355\225\234_\352\260\235\354\262\264_\354\203\235\354\204\261\354\235\204_\355\224\274\355\225\230\353\235\274.md" diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/effective-java.iml b/.idea/effective-java.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/effective-java.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/git_toolbox_blame.xml b/.idea/git_toolbox_blame.xml new file mode 100644 index 0000000..7dc1249 --- /dev/null +++ b/.idea/git_toolbox_blame.xml @@ -0,0 +1,6 @@ + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..8780e86 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..21a3d9c --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git "a/2\354\236\245_\352\260\235\354\262\264_\354\203\235\354\204\261\352\263\274_\355\214\214\352\264\264/\354\225\204\354\235\264\355\205\234_03/private_\354\203\235\354\204\261\354\236\220\353\202\230_\354\227\264\352\261\260_\355\203\200\354\236\205\354\234\274\353\241\234_\354\213\261\352\270\200\355\204\264\354\236\204\354\235\204_\353\263\264\354\246\235\355\225\230\353\235\274.md" "b/2\354\236\245_\352\260\235\354\262\264_\354\203\235\354\204\261\352\263\274_\355\214\214\352\264\264/\354\225\204\354\235\264\355\205\234_03/private_\354\203\235\354\204\261\354\236\220\353\202\230_\354\227\264\352\261\260_\355\203\200\354\236\205\354\234\274\353\241\234_\354\213\261\352\270\200\355\204\264\354\236\204\354\235\204_\353\263\264\354\246\235\355\225\230\353\235\274.md" new file mode 100644 index 0000000..34dea5c --- /dev/null +++ "b/2\354\236\245_\352\260\235\354\262\264_\354\203\235\354\204\261\352\263\274_\355\214\214\352\264\264/\354\225\204\354\235\264\355\205\234_03/private_\354\203\235\354\204\261\354\236\220\353\202\230_\354\227\264\352\261\260_\355\203\200\354\236\205\354\234\274\353\241\234_\354\213\261\352\270\200\355\204\264\354\236\204\354\235\204_\353\263\264\354\246\235\355\225\230\353\235\274.md" @@ -0,0 +1,131 @@ +# private 생성자나 열거 타입으로 싱글턴임을 보증하라 + +## 싱글턴이란? +> 애플리케이션에서 특정 클래스의 인스턴스를 오직 하나만 생성하도록 보장하는 패턴 + +예를 들어, 무상태(stateless) 객체나 설계상 유일해야 하는 시스템 컴포넌트가 있다. +그런데, **클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트하기가 어려워진다.** + +## 싱글턴을 쓰는 이유 +- 리소스 관리 및 접근 제어 +- 메모리 효율성 +- 공유 상태의 일관성 + +## 싱글턴을 만드는 방법 +### 1. public static final 필드 방식의 싱글턴 +> 싱글턴 객체를 public static final 필드로 직접 선언하는 방법 + +```java +public class Singleton { + public static final Singleton INSTANCE = new Signleton(); + + private Singleton() { + // private 생성자 + } +} +``` + +코드에서 볼 수 있듯이, public, protected 대신 **private 생성자**를 사용했기에 Singleton.INSTANCE를 초기화할 때 딱 한 번 호출된다. +-> 전체 시스템에서 Singleton 클래스의 인스턴스가 하나뿐인 것이 보장된다. + +BUT, AccessibleObject.setAccessible로 private 생성자에 접근하는 예외가 발생할 수 있다. +-> 생성자 내부를 수정하여 예외를 막을 수 있다. + +#### 1번 방법의 장점 +- 싱글턴 클래스임이 API에 명백히 드러난다. + - public static final로 설정했기 때문에 다른 객체를 참조할 수 없다. +- 간결하다. + +### 2. 정적 팩터리 방식의 싱글턴 +> 싱글턴 객체를 제공하기 위해 정적 메서드를 사용하는 방법 + +```java + public class Singleton { + private static final Singleton INSTANCE = new Singleton(); + + private Singleton() { + // private 생성자 -> 외부에서 객체를 생성하지 못하도록 막음 + } + + public static Singleton getInstance() { + return INSTANCE; + } +} +``` + +코드에서 볼 수 있듯에, public static final이 아닌 private static final로 싱글턴 인스턴스를 선언한 것을 볼 수 있다. +-> 싱글턴 인스턴스를 직접 노출하지 않고 정적 팩터리 메서드(getInstance())를 통해 접근한다. + +#### 2번 방법의 장점 +- API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다. +- 정적 팩터리를 제네릭 싱글턴 팩터리로 만들 수 있다. (아이템 30) +- 정적 팩터리의 메서드 참조를 공급자(supplier)로 사용할 수 있다. (아이템 43, 44) + +## 직렬화 & 역직렬화 + +### 직렬화 +> 오브젝트를 파일 형태로 디스크에 저장하는 것 + +직렬화는 JVM의 힙 메모리에 있는 객체 데이터를 바이트 스트림 형태로 바꿔 외부 파일로 내보낼 수 있게 하는 기술이다. + +### 역직렬화 +> 직렬화와 반대로 다시 읽어들이는 것 + +역직렬화는 외부로 내보낸 직렬화 데이터를 다시 읽어들여 자바 객체로 재변환하는 것을 말한다. + +**Serializable** 인터페이스를 구현하면 클래스의 인스턴스를 직렬화와 역직렬화에 사용할 수 있다. +하지만 역직렬화 과정에서 인스턴스를 새로 만들고, 직렬화에 사용한 인스턴스와는 전혀 다른 인스턴스가 되기 때문에 싱글턴이 깨진다. + +모든 필드 변수를 **transient**로 선언하고, **readResolve() 메서드**를 제공해야 역직렬화를 할 때 두 번째 인스턴스가 생성되지 않는다. + +```java + public class Singleton implements Serializable { + + transient String str = ""; + transient ArrayList list = new ArrayList<>(); + + private Singleton() { + } + + private static class SingletonHolder { + private static final Singleton INSTANCE = new Singleton(); + } + + public static Singleton getInstance() { + return SingletonHolder.INSTANCE; + } + + // readResolve 메서드 + private Object readResolve() { + return INSTANCE; + } +} + +``` + +transient로 인스턴스 필드를 설정하면, 직렬화에서 제외된다. +readResolve() 메서드를 사용하면 기존에 역직렬화를 통해 새로 생성된 객체는 가비지 컬렉터 대상이 된다. + +### 3. 열거 타입 방식의 깅글턴 + +대부분 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다. +(단, Enum 클래스가 다른 클래스나 인터페이스를 extends 할 경우 사용할 수 없다.) + +```java +public enum Singleton { + INSTANCE; +} + +``` + +열거 타입 방식으로 싱글턴을 구현하면, 직렬화와 리플렉션 공격에도 안전하다. + ++ **리플렉션**이란? + +객체를 통해 클래스의 정보를 분석하여 런타임에 클래스의 동작을 조작하는 프로그램 기법 + +클래스 파일의 위치나 이름만 있다면 클래스의 정보를 통해 객체를 생성할 수 있다. + +#### Reference +[싱글톤 객체가 깨져버리는 경우(역직렬화/리플렉션)](https://inpa.tistory.com/entry/JAVA-%E2%98%95-%EC%8B%B1%EA%B8%80%ED%86%A4-%EA%B0%9D%EC%B2%B4-%EA%B9%A8%EB%9C%A8%EB%A6%AC%EB%8A%94-%EB%B0%A9%EB%B2%95-%EC%97%AD%EC%A7%81%EB%A0%AC%ED%99%94-%EB%A6%AC%ED%94%8C%EB%A0%89%EC%85%98) +[자바에서 싱글톤 패턴을 적용하는 방법](https://olrlobt.tistory.com/72) \ No newline at end of file diff --git "a/2\354\236\245_\352\260\235\354\262\264_\354\203\235\354\204\261\352\263\274_\355\214\214\352\264\264/\354\225\204\354\235\264\355\205\234_06/\353\266\210\355\225\204\354\232\224\355\225\234_\352\260\235\354\262\264_\354\203\235\354\204\261\354\235\204_\355\224\274\355\225\230\353\235\274.md" "b/2\354\236\245_\352\260\235\354\262\264_\354\203\235\354\204\261\352\263\274_\355\214\214\352\264\264/\354\225\204\354\235\264\355\205\234_06/\353\266\210\355\225\204\354\232\224\355\225\234_\352\260\235\354\262\264_\354\203\235\354\204\261\354\235\204_\355\224\274\355\225\230\353\235\274.md" new file mode 100644 index 0000000..eefbe68 --- /dev/null +++ "b/2\354\236\245_\352\260\235\354\262\264_\354\203\235\354\204\261\352\263\274_\355\214\214\352\264\264/\354\225\204\354\235\264\355\205\234_06/\353\266\210\355\225\204\354\232\224\355\225\234_\352\260\235\354\262\264_\354\203\235\354\204\261\354\235\204_\355\224\274\355\225\230\353\235\274.md" @@ -0,0 +1,113 @@ +# 불필요한 객체 생성을 피하라 + +똑같은 객체를 매번 생성하기 보다, 객체 하나를 재사용하는 것이 좋다. +불변 객체(아이템 17)은 언제든 재사용할 수 있다. + +## 문자열 객체 재사용 + +```java +String s1 = new String("hello"); // - (1) new를 이용한 방식 + +String s2 = "hello"; // - (2) 문자열 리터럴을 이용한 방식 +``` + +1번 방법의 경우, 실행될 때마다 String 인스턴스를 새로 만든다. + +2번 방법의 경우 새로운 인스턴스를 매번 만드는 대신 하나의 String 인스턴스를 사용한다. +-> 같은 가상 머신 안에서 똑같은 문자열을 사용하는 코드는 같은 객체를 재사용함이 보장된다. + +## 생성자 대신 정적 팩터리 메서드 + +**불변 클래스**에서는 생성자 대신 **정적 팩터리 메서드**를 사용해 불필요한 객체 생성을 피할 수 있다. +생성자는 호출할 때마다 새로운 객체를 만들지만, 정적 팩터리 메서드는 그렇지 않다. + +```java +Boolean true1 = Boolean.valueOf("true"); +Boolean true2 = Boolean.valueOf("true"); + +System.out.pritln(true1 == true2); // true +``` + +## 비싼 객체는 캐싱하여 재사용 + +```java +static boolean isRomanNumeral(String s) { + return s.matches(regex); +} +``` + +String.matches 메서드는 성능이 중요한 상황에서 반복해서 사용하기에 부적합하다. +matches 메서드 내부에서 Pattern 인스턴스를 만들고, 한 번 사용된 Pattern 인스턴스는 버려지고 GC의 대상이 된다. + +```java +public class RomanNumerals { + private static final Pattern ROMAN = Pattern.compile(regex); + + static boolean isRomanNumeral(String s){ + return ROMAN.matcher(s).matches(); + } +} +``` + +위와 같이 Pattern 인스턴스를 클래스 초기화 과정에서 `static final`로 직접 생성해 캐싱해두는 것이 좋다. +-> 성능이 개선된다. + +## 불필요한 객체 생성의 예 - 어댑터 +> 실제 작업은 뒷단 객체에 위임하고, 자신은 제2의 인터페이스 역할을 해주는 객체 +> + +## 불필요한 객체 생성의 예 - 오토 박싱 +> 기본 자료형(primitive type)과 래퍼 클래스(wrapper class)를 섞어 쓸 때 자동으로 상호 변환해주는 기술 + +```java + private static long sum() { + Long sum = 0L; + for (long i=0; i 박싱된 기본 타입보다는 기본타입을 사용하자 +-> 의도치 않은 오토박싱이 숨어들지 않도록 주의하자 + +## 주의할 점 +### 객체의 생성과 기능 +**객체 생성은 비싸니 피해야 한다**로 오해하면 안 된다. +객체를 반복적으로 생성하는 것은 기본적으로 성능에 영향을 미칠 수 있지만, 작고 가벼운 객체는 최신 JVM에서 크게 부담 X +프로그램의 명확성, 간결성, 기능을 위해서라면 객체를 추가로 생성해도 괜찮다 + +### 객체 풀을 만들지 말자 +#### 객체 풀 +> 자주 사용되는 객체를 미리 생성하여 재사용하는 기법 + +```java +public class ObjectPool { + private String[] pool; + private int size; + + //객체 풀 초기화 + public ObjectPool(int size) { + this.size = size; + pool = new String[size]; + + //객체를 풀에 미리 채우기 + for(int i=0; i 객체를 복사할 때 원본 객체에 대한 참조를 직접 전달하지 않고, 새로운 객체를 만들어서 전달하는 방법 + +원복 객체를 외부에서 변경하지 못하도록 방어하는 목적이다. +방어적 복사(아이템 50)가 필요한 상황에서 객체를 재사용했을 때의 피해가, 필요 없는 객체를 반복 생성했을 때의 피해보다 훨씬 크다. From 00a634eb465a00362d52c3c830f03c5bcce02fae Mon Sep 17 00:00:00 2001 From: cyunlee Date: Sat, 11 Jan 2025 02:57:06 +0900 Subject: [PATCH 2/2] =?UTF-8?q?chore:=20main=20=EB=B8=8C=EB=9E=9C=EC=B9=98?= =?UTF-8?q?=EC=97=90=20item14=20=EC=98=AE=EA=B8=B0=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...40\353\240\244\355\225\230\353\235\274.md" | 350 ++++++++++++++++++ 1 file changed, 350 insertions(+) create mode 100644 "3\354\236\245_\353\252\250\353\223\240_\352\260\235\354\262\264\354\235\230_\352\263\265\355\206\265_\353\251\224\354\204\234\353\223\234/\354\225\204\354\235\264\355\205\234_14/Comparable\354\235\204_\352\265\254\355\230\204\355\225\240\354\247\200_\352\263\240\353\240\244\355\225\230\353\235\274.md" diff --git "a/3\354\236\245_\353\252\250\353\223\240_\352\260\235\354\262\264\354\235\230_\352\263\265\355\206\265_\353\251\224\354\204\234\353\223\234/\354\225\204\354\235\264\355\205\234_14/Comparable\354\235\204_\352\265\254\355\230\204\355\225\240\354\247\200_\352\263\240\353\240\244\355\225\230\353\235\274.md" "b/3\354\236\245_\353\252\250\353\223\240_\352\260\235\354\262\264\354\235\230_\352\263\265\355\206\265_\353\251\224\354\204\234\353\223\234/\354\225\204\354\235\264\355\205\234_14/Comparable\354\235\204_\352\265\254\355\230\204\355\225\240\354\247\200_\352\263\240\353\240\244\355\225\230\353\235\274.md" new file mode 100644 index 0000000..6d2f451 --- /dev/null +++ "b/3\354\236\245_\353\252\250\353\223\240_\352\260\235\354\262\264\354\235\230_\352\263\265\355\206\265_\353\251\224\354\204\234\353\223\234/\354\225\204\354\235\264\355\205\234_14/Comparable\354\235\204_\352\265\254\355\230\204\355\225\240\354\247\200_\352\263\240\353\240\244\355\225\230\353\235\274.md" @@ -0,0 +1,350 @@ +# 아이템 14. Comparable을 구현할지 고려하라 + +우선 Comparable이 무엇인지 알아보자. + +## ✔️ Comparable + +### 정의 +> 자기 자신과 매개변수 객체를 비교할 수 있도록 만들어주는 인터페이스 + +기본형 타입인 int, double 등의 변수는 단순히 관계 연산자를 통해 비교할 수 있다. + +하지만 여러 변수와 값을 가진 **객체**들은 무엇을 기준으로 비교해야 할까? + +### 객체의 비교를 가능하게! + +name과 score을 멤버 변수로 갖는 Student 클래스를 생각해보자. +name을 기준으로 정렬해야 할까, score을 기준으로 정렬해야 할까? +비교의 기준을 Comparable 인터페이스의 compareTo()를 재정의함으로써 정의할 수 있다. + +```java +public class Student implements Comparable { + private String name; + private int score; // 점수 + + ... + + // Comparable 인터페이스 구현 + @Override + public int compareTo(Student other) { + // 성적을 기준으로 오름차순 정렬 + return Integer.compare(this.grade, other.grade); + } +} + +``` + +Student 클래스의 인스턴스들을 score를 기준으로 정렬하도록 코드를 작성해보았다. + +클래스가 Comparable을 구현했다는 것은 해당 클래스의 인스턴스들 사이에 자연적인 순서가 있음을 의미하며, +순서가 명확한 값 클래스(알파벳, 숫자, 연대)를 작성한다면 반드시 Comparable 인터페이스를 구현해야 한다. + +이처럼, 인스턴스를 비교하기 위한 기준을 정의할 수 있도록 Comparable 인터페이스는 **compareTo()** 를 제공한다. +그리고 compareTo()에서 정의한 기준은 곧, `Collections.sort()`나 `Arrays.sort()` 의 정렬 기준이 된다. + +자바에서는 같은 타입의 인스턴스를 서로 비교해야만 하는 클래스들은 모두 Comparable 인터페이스를 구현하고 있다. +따라서 Boolean을 제외한 래퍼 클래스나, String, Time, Date 클래스의 인스턴스는 모두 정렬이 가능하다. +이 때, 기본 정렬 순서는 **오름차순**이다. + +
+ +## ✔️ compareTo() + +Comparable 인터페이스를 구현한 클래스에서는 **반드시 compareTo()를 정의해야 한다.** (구현하지 않으면 컴파일 오류) +앞서 설명한 Comparable이 제공하는 유일한 메서드인 compareTo()에 대해 알아보자. + +### 정의 +> 객체들을 비교하여 **정렬 순서를 결정**하는 메서드 + +### 예시 + +```java +public interface Comparable { + int compareTo(T o); +} +``` + +### compareTo를 이용한 2가지 비교 방식 - 숫자형 & 문자열 + +```java +// 숫자형 비교 +public int compareTo(NumberSubClass referenceName); + +// 문자열 비교 +public int compareTo(String anotherString); +``` +compareTo()는 위의 코드에서 볼 수 있듯이, **숫자형 비교와 문자열 비교** 2가지 방식을 제공한다. + +#### 1) 숫자형 비교 + +```java +public class Main { + public static void main(String[] args) { + + Integer a = 5; + Integer b = 10; + Double c = 0.8; + int a = 3; + int b = 4; + + System.out.println(a.compareTo(b)); // -1 + System.out.println(c.compareTo(0.1)); // 1 + System.out.pritln(b.compareTo(10)); // 0 + } +} +``` + +- Byte, Double, Integer, Float, Long, Short등과 같은 숫자형 래퍼 클래스를 비교할 수 있다. +- 반환 값 + - `기준 값 < 비교 값` : -1 + - `기준 값 > 비교 값` : 1 + - `기준 값 == 비교 값` : 0 + +#### 2) 문자열 비교 + +```java +public class Main { + public static void main(String[] args) { + String str = "abcd"; + + System.out.println(str.compareTo("abcd")); // 0 (두 문자열이 일치) + System.out.println(str.compareTo("ab")); // 4 - 2 (기준 값의 앞자리와 일치) + System.out.println(str.compareTo("c")); // 'a' - 'c' = -2 + } +} + +``` +- 기준 값과 같은 문자열이 파라미터로 들어올 경우 0을 반환한다. +- 기타 반환 값 + - 기준 값의 앞자리부터 일치하는 문자열을 파라미터가 포함할 경우 : `(기준 문자열 길이 - 파라미터 문자열 길이)` + - 첫 번째 위치의 문자열부터 비교가 실패할 경우 : `(기준 문자열의 첫번째 문자 아스키코드 값 - 파라미터 문자열의 첫번째 문자 아스키코드 값)` + +### compareTo()와 equals()의 차이점 + +| **특징** | **compareTo()** | **equals()** | +|----------------------|----------------------------------------------|----------------------------------------------| +| **용도** | 두 객체의 순서 비교 (정렬 기준) | 두 객체의 동치성(Equality) 확인 | +| **메서드 정의 위치** | `Comparable` 인터페이스 | `Object` 클래스 | +| **반환값** | 음수, 0, 양수 (순서 정보 제공) | `true` 또는 `false` (동치성 결과만 제공) | +| **타입 검사** | 같은 타입의 객체만 비교 가능 (`ClassCastException` 가능) | 모든 타입의 객체와 비교 가능 | +| **주요 사용처** | 정렬: `Collections.sort()`, `Arrays.sort()` | 동치성 검사: `HashMap`, `HashSet`, `List` 검색 등 | + +compareTo()는 비교 대상이 동일한 타입임을 전제로 하며, 타입이 다르면 ClassCastException을 던지며 다른 객체를 신경쓰지 않아도 된다. + +반면, equalsTo()는 타입이 달라도 같은 객체인지를 논리적으로 판단해야 한다. + +
+ +## ✔️ compareTo()의 일반 규약 + +compareTo()의 규약을 지키면서 구현해야 객체들이 올바르게 비교되고 정렬된다. + +### 1️⃣ 기준 객체와 주어진 객체의 비교 + +- 기준 객체 < 주어진 객체 : 음수(-1) 반환 +- 기준 객체 == 주어진 객체 : 0 반환 +- 기준 객체 > 주어진 객체 : 양수(+1) 반환 + +만약 두 객체의 타입이 일치하지 않으면, ClassCastException을 던진다. + +### 2️⃣ 대칭성, 추이성, 반사성을 충족해야 한다 + +#### 대칭성 + +Comparable을 구현한 클래스는 모든 x, y에 대해 sgn(x.compareTo(y)) == -sgn(y.compareTo(x))여야 한다. + +따라서, compareTo(x)가 예외를 던지는 경우 x.compareTo(y)가 예외를 던져야 한다. + +#### 추이성 + +(x.compareTo(y) > 0 && y.compareTo(z) > 0)이면 x.compareTo(z) > 0이어야 한다. + +#### 반사성 + +크기가 같은 객체들끼리는 다른 객체와 비교한 결과가 모두 동일해야 한다. + +x.compareTo(y)==0이면, sgn(x.compareTo(z)) == sgn(y.compareTo(z))여야 한다. + +### 3️⃣ compareTo() 동치성 테스트의 결과를 equals()의 결과와 같게 하라 + +(x.compareTo(y)==0) == (x.equals(y))를 만족해야 한다. (필수는 아니지만 되도록) +만약 이를 만족하지 못한다면 equals 메서드와 일관되지 않음을 명시해야 한다. + +```java +public class Main{ + public static void main(String[] args) { + BigDecimal a = new BigDecimal("1.0"); + BigDecimal b = new BigDecimal("1.00"); + + Set set1 = new HashSet<>(); + Set set2 = new TreeSet<>(); + + set1.add(a); + set1.add(b); + + set2.add(a); + set2.add(b); + + System.out.println(set1.size()); // 2 + System.out.println(set2.size()); // 1 + } +} +``` + +예를 들어 BigDecimal 객체를 원소로 가지는 HashSet과 TreeSet이 있다고 하자. + +HashSet은 equals()를 기준으로, TreeSet은 compareTo를 기준으로 중복 원소를 검사하기 때문에 다르게 동작한다. + +
+ +## ✔️ compareTo() 작성 요령 + +- Comparable은 타입을 인수로 받는 제네릭 인터페이스이므로 compareTo 메서드의 인수 타입은 컴파일 타임에 정해진다. + - 따라서, 타입을 확인하거나 형변환할 필요가 없다. +- 인수 타입이 잘못되면 컴파일 에러가 발생한다. +- null을 인수로 넣어 호출하면 NullPointerException을 던져야 한다. +- compareTo()는 동치가 아니라 순서를 비교한다. +- 객체 참조 필드를 비교하려면 compareTo 메서드를 재귀적으로 호출해야 한다. +- Comparable을 구현하지 않은 필드나 표준이 아닌 순서로 비교할 경우 **Comparator**을 사용한다. + +
+ +## ✔️ 기본 타입 필드 비교 + +기본형 타입인 필드를 비교할 때는 **래퍼 클래스**에 존재하는 **compare()** 를 사용하자. + +예를 들어, `Integer.compare()`, `Double.compare()` 등이 있다. + +```java +public class Main{ + public static void main(String[] args) { + int a = 10; + int b = 20; + + int result = Integer.compare(a, b); + if(result < 0) { + System.out.println("a < b"); + }else if(result > 0) { + System.out.println("a > b"); + }else { + System.out.println("a = b"); + } + } +} +``` + +
+ +## ✔️ 여러 개의 필드를 비교하는 경우 + +### Comparable의 compareTo() 구현 + +클래스에 핵심 필드가 여러 개라면, 비교의 우선순위를 정해 가장 중요한 필드부터 비교해나간다. +비교 결과가 0이 아니라면 순서가 결정되고, 결과를 곧바로 반환하자! + +```java +public int compareTo(Person p) { + int result = Integer.compare(age, p.age); + if(result == 0) { + result = Integer.compare(height, p.height); + } + return result; +} +``` + +### Comparator의 비교자 생성 메서드 구현 + +자바 8에서는 **Comparator** 인터페이스가 제공하는 **비교자 생성 메서드**를 통해 메서드 연쇄 방식으로 compareTo 메서드를 구현할 수 있다. +성능 저하 이슈가 있지만, 아래 코드와 같이 정적 임포트 방식을 적용하면 된다. + +```java +private static final Comparator COMPARATOR = + comparingInt((Person p) -> p.age) + .thenComparingInt(p -> p.height); + +public int compareTo(Person p) { + return COMPARATOR.compare(this, p); +} +``` +처음 comparingInt를 호출할 때 람다를 이용하면서 타입을 명시해줘야 한다. +그러나 thenComparingInt부터는 타입을 명시해주지 않아도 자바가 타입을 추론할 수 있다. + +#### comparingX() + +Comparator는 타입 별로 `Comparator.comparingX()` 보조 생성자 메서드를 지원한다. +- `Comparator.comparingInt()` +- `Comparator.comparingDouble()` +- `Comparator.comparingLong()` + +#### comparing() + +Comparator는 객체 참조를 기반으로 비교하는 `Comparator.comparing()`을 지원한다. + +```java +public class Person { + String name; + int age; + + public Person(String name, int age) { + this.name = name; + this.age = age; + } + + public String getName() { + return name; + } + + @Override + public String toString() { + return name + ": " + age; + } +} + +public class Main{ + public static void main(String[] args) { + List people = new ArrayList<>(); + people.add(new Person("Alice", 25)); + people.add(new Person("Bob", 30)); + + people.sort(Comparator.comparing(Person::getName)); + + System.out.println(people); // [Alice: 25, Bob: 30] + } +} +``` +
+ +## ✔️ 해시코드 값의 차를 기준으로 비교하지 말 것 + +```java +static Comparator hashCodeOrder = new Comparator<>() { + public int compare(Object o1, Object o2) { + return o1.hashCode() - o2.hashCode(); + } +} +``` +위의 방식은 사용하면 안 된다. 대신에 **정적 compare 메서드** 혹은 **비교자 생성 메서드**를 활용한 비교자를 구현하자. + +### 정적 compare 메서드를 활용한 비교자 + +```java +static Comparator hashCodeOrder = new Comparator<>() { + public int compare(Object o1, Object o2) { + return Integer.compare(o1.hashCode(), o2.hashCode()); + } +} +``` + +### 비교자 생성 메서드를 활용한 비교자 + +```java +static Comparator hashCodeOrder = + Comparator.comparingInt(o -> o.hashCode()); +``` +
+ +## 🔗 Reference +[[JAVA] compareTo() 사용법, 문자열/숫자 비교](https://doitdoik.tistory.com/92) + +[TCP SCHOOL](https://www.tcpschool.com/java/java_collectionFramework_comparable) +