From bf5749270b3c95f9f0564a9a547bbd54ed945b14 Mon Sep 17 00:00:00 2001 From: jeongminneee Date: Sun, 10 Aug 2025 10:40:08 +0900 Subject: [PATCH 1/7] =?UTF-8?q?item24=5F=EB=A9=A4=EB=B2=84=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EB=8A=94=20=EB=90=98=EB=8F=84=EB=A1=9D=20sta?= =?UTF-8?q?tic=EC=9C=BC=EB=A1=9C=20=EB=A7=8C=EB=93=A4=EB=9D=BC=5F=EC=A0=95?= =?UTF-8?q?=EB=AF=BC.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...4\353\235\274_\354\240\225\353\257\274.md" | 132 ++++++++++++++++++ 1 file changed, 132 insertions(+) create mode 100644 "item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" diff --git "a/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" "b/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" new file mode 100644 index 0000000..912e4e0 --- /dev/null +++ "b/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" @@ -0,0 +1,132 @@ +# item 24 + +### 멤버 클래스는 되도록 static으로 만들라 + +- 중첩 클래스란 + +:다른 클래스 안에 정의된 클래스 + +특징1) 자신을 감싼 바깥 클래스에서만 사용되어야 함 + +→그 이외의 쓰임새로 사용 시 해당 클래스를 톱레벨 클래스로 만들어야 함 + +특징 2) 종류로는 **정적 멤버 클래스, (비정적) 멤버 클래스, 익명 클래스, 지역 클래스**가 있음 + +- 정적 멤버 클래스 + +특징 1) 다른 클래스 안에 선언됨 + +특징 2) 바깥 클래스의 private 멤버에도 접근할 수 있음 + +특징3) 다른 정적 멤버와 똑같은 접근 규칙을 적용 받음 + +→ 특정 정적 멤버 클래스의 접근 지정자를 private으로 할 시, 바깥 클래스에서만 접근할 수 있음 + +- 정적 멤버 클래스는 “바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스” + +예제) Operation 열거 타입은 Calculator 클래스의 public 정적 멤버 클래스가 되어야 함. + +→즉, Calculator 라는 바깥 클래스에 Operation이라는 public 정적 멤버 클래스를 선언하게 되면 외부에서 해당 멤버에 접근 시 ‘Calculator.Operation.PLUS’, ‘Calculator.Operation.MINUS’와 같은 형태로 접근할 수 있어서 **Calculator 소속을 명확하게 확인할 수 있음** + + +- 정적 멤버 클래스와 비정적 멤버 클래스의 차이 + +-**구문상 차이**: static 유무 + +-**의미상 차이**:정적 멤버 클래스는 바깥 클래스의 인스턴스와 무관한 반면, 비정적 멤버 클래스의 인스턴스는 바깥 클래스의 인스턴스와 암묵적으로 연결됨 + +→비정적 멤버 클래스에서 **정규화된 this** 키워드를 통해 바깥 인스턴스의 메서드를 호출하거나 바깥 인스턴스를 참조할 수 있음 + +***정규화된 this: ‘클래스명.this’ 형태** + +→중첩 클래스의 인스턴스가 바깥 인스턴스와 독립적으로 존재할 수 있다면 **‘정적 멤버 클래스’**, + +→바깥 인스턴스 없이는 생성할 수 없다면 **‘비정적 멤버 클래스’** + +- 비정적 멤버 클래스의 인스턴스와 바깥 인스턴스 사이의 관계 + + : 비정적 멤버 클래스가 인스턴스화 될 때 확립되며, 변경 불가능 + +**→이 관계 정보는 비정적 멤버 클래스의 인스턴스 안에 만들어져 메모리 공간을 차지하며 생성시간도 더 걸림** + +- 비정적 멤버 클래스의 쓰임새 + + : 어댑터를 정의할 때 자주 사용됨 → 어떤 클래스의 인스턴스를 감싸 마치 다른 클래스의 인스턴스처럼 보이게 하는 뷰로 사용 + +예제) Set, List와 같은 컬렉션 인터페이스 구현에서 자신의 반복자를 구현할 때 비정적 멤버 클래스를 사용함 + +```java +public class MySet extends AbstractSet { +... // 생략 +@Override public Iterator iterator() { +return new Mylterator(); +} +private class Mylterator implements Iterator { +} +} +``` + +→MyIterator은 MySet 안에 존재하는 비정적 멤버 클래스로 iterator()를 통해 MyIterator 클래스의 인스턴스를 생성해 반환함 + +→즉,MyIterator 클래스(비정적 멤버 클래스)는 Myset(바깥 클래스)의 인스턴스에 자동으로 연결되어 MySet 필드에 직접 접근할 수 있음 + +- **바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들자** + +이유1: static을 생략하면 바깥 인스턴스로의 숨은 외부 참조를 갖게 됨→ 시간과 공간이 소비됨 + +이유2: 가비지 컬렉션이 바깥 클래스의 인스턴스를 수거하지 못하는 메모리 누수가 생길 수 있음 + +즉, 참조가 눈에 보이지 않아 문제의 원인을 찾기도 어려워 심각한 상황을 초래할 수도 있음 + +- 이유 1에 대한 예제 + + : 키와 값을 매핑시키는 Map 인스턴스 + +→Map 구현체는 키-값 쌍을 표현하는 엔트리 객체를 가지고 있음. + +→모든 엔트리가 Map과 연관되어 있지만 엔트리의 메서드들이 Map을 직접 사용하지는 않음 + +(바깥 인스턴스에 접근할 일이 없음) + +따라서, 엔트리를 비정적 멤버 클래스로 표현하는 것은 시간과 공간 낭비 + +- 익명 클래스의 개념과 특징 + +:이름이 없는 클래스 + +특징1) 쓰이는 시점에 선언과 동시에 인스턴스가 만들어짐 + +특징2) 코드의 어디서든 만들 수 있음 + +특징3) 오직 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조할 수 있음 + +특징4) 상수 변수 이외의 정적 멤버는 가질 수 없음 + +(상수표현을 위해 초기화된 final 기본타입과 문자열 필드만 가질 수 있음) + +- 익명 클래스의 제약 + +제약1) 선언한 지점에서만 인스턴스를 만들 수 있음 + +제약2) instanceof 검사(객체 타입 확인 시)나 클래스의 이름이 필요한 작업은 수행할 수 없음 + +제약3) 여러 인터페이스를 구현할 수 없으며 인터페이스를 구현하는 동시에 다른 클래스를 상속할 수 없음 + +제약4) 익명 클래스가 상위 타입에서 상속한 멤버 외에는 호출할 수 없음 + +제약5) 익명 클래스는 표현식 중간에 등장하므로 짧지 않으면 가독성이 떨어짐 + +- 익명 클래스의 쓰임새 +1. 자바가 람다를 지원하기 전에 즉석에서 작은 함수 객체나 처리 객체를 만드는 데 주로 사용함 + *람다(lambda)는 익명 클래스보다 훨씬 짧고 읽기 쉬운 방식으로 코드를 작성할 수 있게 해주는 문법 +2. 정적 팩터리 메서드를 구현할 때 사용함 + +- 지역 클래스 + +특징1 ) 지역변수를 선언할 수 있는 곳이면 어디서든 선언 가능 + +특징2 ) 유효 범위도 지역변수와 같음 + +특징3) 멤버 클래스처럼 이름이 있고 반복해서 사용할 수 있음 + +특징4 ) 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있으며, 정적 멤버는 가질수 없고 가독성을 위해 짧게 작성해야 함 \ No newline at end of file From ec5674b73079e0999975323a4b5ee8c41d4ed10b Mon Sep 17 00:00:00 2001 From: jeongminneee Date: Sun, 10 Aug 2025 10:40:50 +0900 Subject: [PATCH 2/7] =?UTF-8?q?item24=5F=EB=A9=A4=EB=B2=84=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EB=8A=94=20=EB=90=98=EB=8F=84=EB=A1=9D=20sta?= =?UTF-8?q?tic=EC=9C=BC=EB=A1=9C=20=EB=A7=8C=EB=93=A4=EB=9D=BC=5F=EC=A0=95?= =?UTF-8?q?=EB=AF=BC.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git "a/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" "b/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" index 912e4e0..c786f98 100644 --- "a/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" +++ "b/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" @@ -57,7 +57,7 @@ ```java public class MySet extends AbstractSet { -... // 생략 + // 생략 @Override public Iterator iterator() { return new Mylterator(); } From 43640be4e98881db307c8b9670496e063bc49210 Mon Sep 17 00:00:00 2001 From: jeongminneee Date: Sun, 10 Aug 2025 10:41:27 +0900 Subject: [PATCH 3/7] =?UTF-8?q?item24=5F=EB=A9=A4=EB=B2=84=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=EB=8A=94=20=EB=90=98=EB=8F=84=EB=A1=9D=20sta?= =?UTF-8?q?tic=EC=9C=BC=EB=A1=9C=20=EB=A7=8C=EB=93=A4=EB=9D=BC=5F=EC=A0=95?= =?UTF-8?q?=EB=AF=BC.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...214\353\223\244\353\235\274_\354\240\225\353\257\274.md" | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git "a/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" "b/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" index c786f98..482dbd5 100644 --- "a/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" +++ "b/item24/item24_\353\251\244\353\262\204 \355\201\264\353\236\230\354\212\244\353\212\224 \353\220\230\353\217\204\353\241\235 static\354\234\274\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" @@ -56,12 +56,12 @@ 예제) Set, List와 같은 컬렉션 인터페이스 구현에서 자신의 반복자를 구현할 때 비정적 멤버 클래스를 사용함 ```java -public class MySet extends AbstractSet { - // 생략 +public class MySet extends AbstractSet { + // 생략 @Override public Iterator iterator() { return new Mylterator(); } -private class Mylterator implements Iterator { +private class MyIterator implements Iterator { } } ``` From 281e75775c4ac16f67d86577eff6197d4d9c48de Mon Sep 17 00:00:00 2001 From: jeongminneee Date: Wed, 13 Aug 2025 10:40:03 +0900 Subject: [PATCH 4/7] =?UTF-8?q?item30=5F=EC=9D=B4=EC=99=95=EC=9D=B4?= =?UTF-8?q?=EB=A9=B4=20=EC=A0=9C=EB=84=A4=EB=A6=AD=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EB=A7=8C=EB=93=A4=EB=9D=BC=5F=EC=A0=95?= =?UTF-8?q?=EB=AF=BC.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...4\353\235\274_\354\240\225\353\257\274.md" | 133 ++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 "item30/item30_\354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \353\251\224\354\204\234\353\223\234\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" diff --git "a/item30/item30_\354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \353\251\224\354\204\234\353\223\234\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" "b/item30/item30_\354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \353\251\224\354\204\234\353\223\234\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" new file mode 100644 index 0000000..7f6f637 --- /dev/null +++ "b/item30/item30_\354\235\264\354\231\225\354\235\264\353\251\264 \354\240\234\353\204\244\353\246\255 \353\251\224\354\204\234\353\223\234\353\241\234 \353\247\214\353\223\244\353\235\274_\354\240\225\353\257\274.md" @@ -0,0 +1,133 @@ +- 제네릭 메서드 +:메서드 내부에서 사용할 타입을 호출 시점에 외부에서 결정할 수 있도록 만든 메서드 +: 제네릭 클래스가 존재하듯, 메서드도 제네릭으로 만들 수 있음 +:정적 유틸리티 메서드는 보통 제네릭 +*정적 유틸리티: 객체생성X, 클래스명으로 호출, static 메서드 +ex) Collections의 알고리즘 메서드(binarySearch, sort)등은 모두 제네릭 메서드 + +- 제네릭 메서드 작성법 + ex1)문제 코드(컴파일은 되지만 경고가 발생) + +```java +public static Set union(Set s1, Set s2) { +Set result = new HashSet(si); +result.addAll(s2); +return result; +} +``` + +→문제 원인: Set과 HashSet을 타입 인자 없이 사용하게 되면서 타입 안정성을 잃음 + +→수정 방법: 해당 메서드를 타입 안전하게 만들어야 함 +1. 메서드 선언에서 세 집합(입력 두개(s1, s2), 반환 1개(result))의 원소 타입을 타입 매개변수로 명시 +2. 메서드 안에서도 이 타입 매개변수만 사용하도록 수정 +3. 타입 매개변수 목록은 메서드의 제한자와 반환 타입 사이에 둠 + *타입 매개변수 목록: 타입 매개변수들을 선언함 + Q)타입 매개변수 목록을 메서드 선언시에 명시하는 이유는? + A)해당 메서드 안에서 E라는 이름의 타입 변수를 사용할 것임을 컴파일러에게 알려주기 위함 + + ex2)수정 코드 + +```java +public static Set union(Set s1, Sets2) { + Set result=new HashSet<>(s1); + result.addAll(s2); + return result; +} +``` + +→타입 매개변수 목록은 이고 반환 타입은 Set + +→union 메서드의 집합 3개(입력 2개, 반환 1개)의 타입이 모두 같아야 함 +이유: 제네릭 메서드 안에서는 메서드 선언부에 명시한 타입 매개변수만 사용할 수 있기 때문 +*이는 한정적 와일드카드 타입을 사용하여 유연하게 개선할 수 있음 + +ex3)수정 코드를 호출한 예제 코드 + +```java +public static void main(String[] args_){ + Set guys = Set.of("톰", "딕","해리"); + Set stooges = Set.of("래리", "모에", "컬리"); + Set aflCio = union(guys, stooges); + System.out.printIn(aflCio); +} +``` + +→프로그램 실행 결과: [모에, 톰, 해리, 래리, 컬리, 딕] + +- 제네릭 싱글턴 팩터리 + 필요성: + 불변 객체를 여러 타입으로 활용할 수 있게 만들어야 할 때, 제네릭은 런타임에 타입 정보가 소거 됨. 따라서 하나의 객체를 어떤 타입으로든 매개변수화할 수 있음 + →이것이 가능하게 하기 위해 요청한 타입 매개변수에 맞게 매번 그 객체의 타입을 바꿔주는 정적 팩터리가 필요, 이를 ‘제네릭 싱글턴 팩터리’ 라고 함 + +- 제네릭 싱글턴 팩터리 사용 예제→항등함수를 담은 클래스 + :항등함수 객체는 상태가 없으므로 매번 새로 생성하는 것은 낭비 + →제네릭의 소거방식을 활용해 제네릭 싱글턴을 사용해 생성할 수 있음 + +:예제 코드1 + +```java +private static UnaryOperator IDENTITY_FN = (t) -> t; + +@SuppressWarnings("unchecked") +public static UnaryOperator identityFunction() { + return (UnaryOperator) IDENTITY_FN; +} +``` + +→IDENTITY_FN을 UnaryOperator로 형변환하면 비검사 형변환 경고가 발생하지만 항등함수(입력 값을 변형없이 그대로 반환함)이기 때문에 T가 어떤 타입 이든 타입 안전성이 깨지지 않아 UnaryOperator를 사용해도 타입 안전 + +→SuppressWarnings 애너테이션을 추가하여 오류나 경고 없이 컴파일 하도록 함 + +:예제 코드 2(위 코드 활용 ver) + +```java +public static void main(String[] args) { +String[] strings = {"삼베","대마", "나일론"}; +UnaryOperator sameString = identityFunction(); +for (String s : strings) +System.out.printIn(sameString.apply(s)); +Number[] numbers = { 1, 2.0, 3L }; +UnaryOperator sameNumber = identityFunction(); +for (Number n : numbers) +System.out.println(sameNumber.apply(n)); +} +``` + +→코드1의 제네릭 싱글턴을 UnaryOperator과 UnaryOperator로 사용 + +→identityFunction() 호출 시 가 각각 String과 Number로 추론 + +→apply(s)와 apply(n) 함수로 입력 문자열과 숫자를 그대로 반환하여 원본 값 출력 + +- 재귀적 타입 한정 + : 자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정할 수 있음 + :예제코드 + + ```java + public interface Comparable { + int compareTo(T o); + } + ``` + + →타입 매개변수 T는 Comparable를 구현한 타입이 비교할 수 있는 원소의 타입을 결정하게 됨 + + →모든 타입은 자신과 같은 타입의 원소와만 비교할 수 있기 때문에, String은 Comparable을 구현하고 Interger는 Comparable을 구현하게 됨 + : 주로 타입의 자연적 순서를 정하는 Comparable 인터페이스와 함께 쓰임 + : Comparable을 구현한 원소의 컬렉션을 입력받는 메서드들은 보통 그 원소들을 정렬, 검색, 최솟값/최댓값 탐색을 위해 사용됨→이를 위해 컬렉션에 담긴 모든 원소가 상호 비교될 수 있어야 함 + :예제 코드 + + ```java + public static > E max(Collection c) { + if (c.isEmptyO) + throw new IllegalArgumentException("컬렉션이 비어 있습니다"); + E result = null; + for (E e : c) + if (result = null || e.compareTo(resuIt) > 0) + result = Objects.requireNonNull(e); + return result; + } + ``` + + →타입 한정인 >는 “모든 타입 E는 자신과 비교할 수 있다”라는 의미 + →컬렉션에 담긴 원소의 자연적 순서를 기준으로 최댓값을 계산하게 됨 \ No newline at end of file From 2915994830c1f9cb6a7e738119ea454f5ace019e Mon Sep 17 00:00:00 2001 From: jeongminneee Date: Wed, 13 Aug 2025 17:26:13 +0900 Subject: [PATCH 5/7] =?UTF-8?q?item30=5F=EC=9D=B4=EC=99=95=EC=9D=B4?= =?UTF-8?q?=EB=A9=B4=20=EC=A0=9C=EB=84=A4=EB=A6=AD=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=EB=A1=9C=20=EB=A7=8C=EB=93=A4=EB=9D=BC=5F=EC=A0=95?= =?UTF-8?q?=EB=AF=BC.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\353\235\274_\354\240\225\353\257\274.md" | 302 ++++++++++++++++++ 1 file changed, 302 insertions(+) create mode 100644 "item34/item34_int \354\203\201\354\210\230 \353\214\200\354\213\240 \354\227\264\352\261\260 \355\203\200\354\236\205\354\235\204 \354\202\254\354\232\251\355\225\230\353\235\274_\354\240\225\353\257\274.md" diff --git "a/item34/item34_int \354\203\201\354\210\230 \353\214\200\354\213\240 \354\227\264\352\261\260 \355\203\200\354\236\205\354\235\204 \354\202\254\354\232\251\355\225\230\353\235\274_\354\240\225\353\257\274.md" "b/item34/item34_int \354\203\201\354\210\230 \353\214\200\354\213\240 \354\227\264\352\261\260 \355\203\200\354\236\205\354\235\204 \354\202\254\354\232\251\355\225\230\353\235\274_\354\240\225\353\257\274.md" new file mode 100644 index 0000000..b4214f8 --- /dev/null +++ "b/item34/item34_int \354\203\201\354\210\230 \353\214\200\354\213\240 \354\227\264\352\261\260 \355\203\200\354\236\205\354\235\204 \354\202\254\354\232\251\355\225\230\353\235\274_\354\240\225\353\257\274.md" @@ -0,0 +1,302 @@ +# item 34 + +### int 상수 대신 열거 타입을 사용하라 + +- 열거 타입 + :일정 개수의 상수 값을 정의한 다음, 그 외의 값은 허용하지 않는 타입 + +- 자바에서 열거 타입을 지원하기 전 + :정수 상수를 한 묶음 선언하는 ‘정수 열거 패턴’을 사용 + :예제 코드 + + ```java + public static final int APPLE_FUJI = 0; + public static final int APPLE_PIPPIN = 1; + public static final int APPLE_GRANNY_SMITH = 2; + + public static final int ORANGE_NAVEL = 0; + public static final int ORANGE_TEMPLE= 1; + public static final int ORANGE_BLOOD = 2; + ``` + + →정수 열거 패턴의 단점 + 1. 타입 안전을 보장할 방법이 없음 + + 2. 표현력이 좋지 않음 + →오렌지와 사과를 동등 연산자(==)로 비교하더라도 컴파일러는 경고 메시지를 출력하지 않음 + 3. 정수 열거 패턴을 위한 별도 이름 공간을 지원하지 않기 때문에 접두어를 써서 이름 충돌을 방지할 수밖에 없음 + 4. 정수 열거 패턴을 사용한 프로그램은 깨지기 쉬움 + →상수를 나열한 것뿐이라 컴파일 시 그 값이 클라이언트 파일에 그대로 새겨짐. 따라서 상수의 값이 바뀌면 클라이언트도 반드시 다시 컴파일 해야 함 + +- 추가 단점⇒정수 상수는 문자열로 출력하기가 까다롭다. + 이유: 디버거 입장에서는 단지 숫자로만 보임→그렇다고 정수 열거 그룹에 속한 모든 상수를 순회하는 것도 쉽지 않음 + 해결책 1) 문자열 열거 패턴 + →정수 대신 문자열 상수를 사용함 + →장점: 상수의 의미를 출력할 수 있음 + →단점1: 문자열 상수의 이름 대신 문자열 값을 그대로 하드코딩하게 만듦 + →단점2: 하드코딩한 문자열에 오타가 있어도 컴파일러는 확인할 방법이 없음→런타임 버그가 발생 + →단점3: 문자열 비교에 따른 성능 저하 + +→결론: 사용하지 말 것 + +해결책 2) 열거 타입 + +- 열거타입 + +```java +public enum Apple { FUJI, PIPPIN, GRANNY_SMITH } +public enum Orange { NAVEL, TEMPLE, BLOOD } +``` + +→특징 1: 열거 타입 자체는 클래스 +→특징 2: 상수 하나당 자신의 인스턴스를 하나씩 만들어 public static final 필드로 공개함 + +→특징 3: 열거 타입은 밖에서 접근할 수 있는 생성자를 제공하지 않으므로 사실상 final + +(따라서 열거 타입 선언으로 만들어진 인스턴스들은 딱 하나씩만 존재함) +(인스턴스가 1개뿐인 타입인 싱글턴은 원소가 하나뿐인 열거타입이라 할 수 있고, 거꾸로 말하면 열거 타입은 싱글턴을 일반화한 형태라고 볼 수 있음) + +→장점 1: 컴파일 타임 타입 안전성을 제공함 +(다른 타입의 값을 넘기게 될 시 컴파일 오류 발생) +→장점 2: 열거타입에는 각자의 이름 공간이 있어서 이름이 같은 상수도 공존가능 +(새로운 상수를 추가하거나 순서를 바꿔도 다시 컴파일하지 않아도 됨) +→장점 3: 열거 타입의 toString 메서드는 출력하기에 적합한 문자열을 내어줌 + +→장점 4: 열거 타입에는 임의의 메서드나 필드를 추가할 수 있고 임의의 인터페이스를 구현할 수도 있음 + +- 장점 4→열거 타입에 메서드나 필드를 추가하는 경우 + 예시 상황 1: Apple과 Orange 각 과일의 색을 알려주거나 과일 이미지를 반환하는 메서드를 추가할 때 열거 타입에는 어떤 메서드도 추가할 수 있음 + 예시 상황 2: 태양계의 여덟 행성에 대한 질량과 반지름을 이용해 표면 중력 계산하는 것과 같은 추상 개념 또한 표현할 수 있음 + + ```java + public enum Planet { + MERCURY(3.302e+23,2.439e6), + VENUS (4.869e+24,6.052e6), + EARTH (5.975e+24, 6.378e6), + MARS (6.419e+23,3.393e6), + JUPITER(1.899e+27, 7.149e7), + SATURN (5.685e+26,6.027e), + URANUS (8.683e+25,2.556e7), + NEPTUNE(1.024e+26,2.477e7); + // 질량(단위: 킬로그램) + // 반지름(단위: 미터) + private final double mass; + private final double radius; + private final double surfaceGravity; // 표면중력(단위: m / s주2) + // 중력상수(단위: 73 / kg sA2) + private static final double G = 6.67300E-11; + // 생성자 + Planet(double mass, double radius) { + this.mass = mass; + this.radius = radius; + surfaceGravity = G * mass / (radius * radius); + } + public double mass() { return mass; } + public double radius() { return radius; } + public double surfaceGravity() { return surfaceGravity; } + public double surfaceWeight(double mass) { + return mass * surfaceGravity; // F = ma + } + } + ``` + + →열거 타입 상수 각각을 특정 데이터와 연결지으려면 생성자에서 데이터를 받아 인스터스 필드에 저장하면 됨 + + →열거 타입은 자신 안에 정의된 상수들의 값을 배열에 담아 반환하는 정적 메서드인 values를 제공하는데, 여러 타입 값의 toString 메서드는 상수 이름을 문자열로 반환하므로 println이나 printf로 출력하기 적합 + +- 열거 타입에서 상수를 하나 제거하게 되면? + case1) 제거한 상수를 참조하지 않는 클라이언트→아무 영향이 없음 + case2) 제거된 상수를 참조하는 클라이언트 + →클라이언트 프로그램을 다시 컴파일하면⇒’상수 없음’을 소스단계에서 잡게되어 컴파일 오류가 발생하게 됨 + →클라이언트 프로그램을 다시 컴파일 하지않으면⇒바뀐 enum과 참조가 어긋나서 런타임에 예외가 발생하게 됨 + +- 열거 타입을 선언한 클래스 혹은 그 패키지에서만 유용한 기능은 private이나 package-private 메서드로 구현하라 + →일반 클래스와 마찬가지로 그 기능을 클라이언트에 노출해야 할 합당한 이유가 없다면 private이나 package-private으로 선언하라 + +- 상수마다 동적이 달라져야 하는 상황에서 열거 타입 + →예시 상황: 사칙 연산에서 연산 종류를 열거 타입으로 선언+ 실제 연산까지 열거 타입 상수가 수행 + 코드1) switch문 사용 + + ```java + public enum Operation { + PLUS, MINUS, TIMES, DIVIDE; + // 상수가 뜻하는 연산을 수행한다. + public double apply(double x, double y) { + switch(this) { + case PLUS: return x + y; + case MINUS: return x - y; + case TIMES: return x * y; + case DIVIDE: return x / y; + } + throw new AssertionError("알 수 없는 연산: " + this); + } + } + ``` + + →문제점1) 마지막의 throw 코드를 생략하게 될 시 컴파일조차 되지 않음 + →문제점2) 깨지기 쉬운 코드(새로운 상수를 추가하면 case문 추가해야함, 하지 않을 시 연산 수행하려 할 때 런타임 오류를 내며 프로그램 종료됨) + + 코드2) 열거 타입을 활용해 상수별 메서드 구현 + : 열거 타입에 apply라는 추상 메서드를 선언하여 각 상수에서 자신에 맞게 재정의하는 방법 + + ```java + public enum Operation { + PLUS {public doubleapply(double x, double y){return x + y;}}, + MINUS {public doubleapply(double x, double y){return x - y;}}, + TIMES {public doubleapply(double x, double y){return x * y;}}, + DIVIDE{public doubleapply(double x, double y){return x / y;}}; + public abstract double apply(double x, double y); + } + ``` + + →장점1) apply 메서드가 상수 선언 바로 옆에 붙어 있어 새로운 상수를 추가할 때 apply도 재정의해야 한다는 사실을 잊지 않을 수 있음 + + →장점2) apply가 추상 메서드이므로 재정의하지 않았다면 **컴파일 오류**로 알려줌 + + +- 상수별 메서드 구현을 상수별 데이터와 결합 + →예시 상황: Operation의 toString을 재정의해 해당 연산을 뜻하는 기호를 반환하도록 함 + + ```java + public enum Operation { + PLUS("+") { + public double apply(double x, double y) { return x + y; } + }, + MINUS("-") { + public double apply(double x, double y) { return x - y; } + }, + TIMES("*") { + public double apply(double x, double y) { return x * y; } + }, + DIVIDE("/") { + public double apply(double x, double y) { return x / y; } + }; + private final String symbol; + Operation(String symbol) { this.symbol = symbol; } + @Override public String toString() { return symbol; } + public abstract double apply(double x, double y); + } + ``` + + ```java + public static void main(String[] args) { + double x = Double.parseDouble(args[0]); + double y = Double.parseDouble(args[1]); + for (Operation op : Operation.values()) + System.out.printf("%f %s %f = %f%n", x,op, y, op.apply(x,y)); + } + ``` + + →실행 결과( 명령줄 인수가 2와 4만 주어도 해당 결과를 얻을 수 있음) + + 2.000000 + 4.000000=6.000000 + 2.000000 - 4.000000=-2.000000 + 2.000000 * 4.000000=8.000000 + 2.000000 / 4.000000=0.500000 + + →해당 코드를 사용해 연산식을 출력하게 될 시, 계산식 출력을 편하게 만들 수 있음 + +- 열거 타입의 toString 메서드를 재정의할 때, fromString 메서드도 함께 제공된다는 점 고려 + →fromString이란? toString이 반환하는 문자열을 해당 열거 타입 상수로 변환해줌 + →예제 코드: 모든 열거 타입에서 사용할 수 있도록 구현한 fromString + + ```java + private static final Map stringToEnum = + Stream.Of(values()).collect ( + toMap(Object::toString, e->e)); + // 지정한 문자열에 해당하는 Operation을 (존재한다면) 반환한다. + public static Optional fromString(String symbol){ + return Optional.ofNullable(stringToEnum.get(symbol)); + } + ``` + + +- 상수별 메서드 구현에는 열거 타입 상수끼리 코드를 공유하기 어렵다. + →예시 상황: + 급여명세서에서 쓸 요일을 표현하는 열거 타입에서는 직원의 기본 임금과 그날 일한 시간에 따른 일당을 계산해주는 메서드가 있음→주중에 오버타임이 발생하게 될 시 잔업 수당이 주말에는 무조건 주어짐 + →예시 코드1: switch 문 사용 + + ```java + enum PayrollDay { + MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY, + SATURDAY, SUNDAY; + private static final int MINS_PER_SHIFT = 8 * 60; + int pay(int minutesWorked, int payRate) { + int basePay = minutesWorked * payRate; + int overtimePay; + switch(this) { + case SATURDAY: case SUNDAY: // 주말 + overtimePay = basePay / 2; + break; + default: // 주중 + overtimePay = minutesWorked <= MINS_PER_SHIFT ? + 0 : (minutesWorked - MINS_PER_SHIFT) * payRate / 2; + } + return basePay + overtimePay; + } + } + ``` + + →간결하기는 하지만, 휴가와 같은 새로운 값을 열거 타입에 추가하게 될 시 case문을 잊지 말고 사용해야 함 + + 예시 코드2) 새로운 상수를 추가할 때 잔업수당 ‘전략’을 선택하도록 하는 것 + : 잔업수당 계산을 private 중첩 열거 타입으로 옮기고 payrollDay 열거 타입의 생성자에서 이 중 적당한 것을 선택하도록 함→ PayrollDay 열거 타입은 잔업수당 계산을 그 전략 열거 타입에 위임하게 되어 switch문이나 상수별 메서드 구현 없이 가능하게 됨 + + ```java + enum PayrollDay { + MONDAY(WEEKDAY), TUESDAY(WEEKDAY), WEDNESDAY(WEEKDAY), + THURSDAY(WEEKDAY), FRIDAY(WEEKDAY), + SATURDAY(WEEKEND), SUNDAY(WEEKEND); + private final PayType payType; + PayrollDay(PayType payType) { this.payType = payType; } + int pay(int minutesWorked, int payRate) { + return payType.pay(minutesWorked, payRate); + } + // 전략 열거 타입 + enum PayType { + WEEKDAY { + int overtimePay(int minsWorked, int payRate) { + return minsWorked <= MINS_PER_SHIFT ? 0 : + (minsWorked - MINS_PER_SHIFT) * payRate / 2; + } + }, + WEEKEND { + int overtimePay(int minsWorked, int payRate) { + return minsWorked * payRate / 2; + } + }; + abstract int overtimePay(int mins, int payRate); + private static final int MINS_PER_SHIFT = 8 * 60; + int pay(int minsWorked, int payRate) { + int basePay = minsWorked * payRate; + return basePay + overtimePay(minsWorked, payRate); + } + } + } + ``` + + +→결론: switch문은 열거 타입의 상수별 동작을 구현하는데 적합하지 않음 +→Q)그렇다면 switch문은 열거 타입에서는 필요가 없을까? +→A) 기존 열거 타입에 상수별 동작을 혼합해 넣을 때는 switch문이 좋은 선택이 될 수 있음 +ex)Operation 열거 타입이 있을 때, 각 연산의 반대 연산을 반환하는 메서드가 필요할 시 + +```java +public static Operation inverse(Operation op) { +switch(op) { +case PLUS: return Operation.MINUS; +case MINUS: return Operation.PLUS; +case TIMES: return Operation.DIVIDE; +case DIVIDE: return Operation.TIMES; +default: throw new AssertionError("알 수 없는 연산: " + op); +} +} +``` + +→switch문을 이용해 원래 열거 타입에 없는 기능을 수행하도록 할 수 있음 + +- 열거 타입에 대한 최종 정리 + 사용 경우: 필요한 원소를 컴파일 타임에 다 알 수 있는 상수 집합이라면 항상 열거 타입을 사용하자 + ex) 태양계 행성, 한 주의 요일, 체스 말 + Q)그렇담 열거 타입에 정의된 상수 개수는 고정적이어야 하는가? + A)Nope, 고정 불변일 필요가 없음, 열거 타입은 나중에 상수가 추가돼도 바이너리 수준에서 호환되도록 설계됨 \ No newline at end of file From 3dd8d95b185914c7181129a5fa36f4ea1df1f312 Mon Sep 17 00:00:00 2001 From: jeongminneee Date: Wed, 24 Sep 2025 15:58:02 +0900 Subject: [PATCH 6/7] =?UTF-8?q?item51=5F=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EC=8B=9C=EA=B7=B8=EB=8B=88=EC=B2=98=EB=A5=BC=20=EC=8B=A0?= =?UTF-8?q?=EC=A4=91=ED=9E=88=20=EC=84=A4=EA=B3=84=ED=95=98=EB=9D=BC=5F?= =?UTF-8?q?=EC=A0=95=EB=AF=BC.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ...0\353\235\274_\354\240\225\353\257\274.md" | 62 +++++++++++++++ ...0\353\235\274_\354\240\225\353\257\274.md" | 76 +++++++++++++++++++ 2 files changed, 138 insertions(+) create mode 100644 "item36/item36_\353\271\204\355\212\270 \355\225\204\353\223\234 \353\214\200\354\213\240 EnumSet\354\235\204 \354\202\254\354\232\251\355\225\230\353\235\274_\354\240\225\353\257\274.md" create mode 100644 "item51/item51_\353\251\224\354\204\234\353\223\234 \354\213\234\352\267\270\353\213\210\354\262\230\353\245\274 \354\213\240\354\244\221\355\236\210 \354\204\244\352\263\204\355\225\230\353\235\274_\354\240\225\353\257\274.md" diff --git "a/item36/item36_\353\271\204\355\212\270 \355\225\204\353\223\234 \353\214\200\354\213\240 EnumSet\354\235\204 \354\202\254\354\232\251\355\225\230\353\235\274_\354\240\225\353\257\274.md" "b/item36/item36_\353\271\204\355\212\270 \355\225\204\353\223\234 \353\214\200\354\213\240 EnumSet\354\235\204 \354\202\254\354\232\251\355\225\230\353\235\274_\354\240\225\353\257\274.md" new file mode 100644 index 0000000..71b5240 --- /dev/null +++ "b/item36/item36_\353\271\204\355\212\270 \355\225\204\353\223\234 \353\214\200\354\213\240 EnumSet\354\235\204 \354\202\254\354\232\251\355\225\230\353\235\274_\354\240\225\353\257\274.md" @@ -0,0 +1,62 @@ +### 비트 필드 대신 EnumSet을 사용하라 + +- 정수 열거 상수(비트 필드 열거 상수) + 열거한 값들이 주로 집합으로 사용될 때, 예전에는 각 상수에 서로 다른 2의 거듭제곱 값을 할당한 **정수 열거 패턴**을 사용해 옴 + →예시코드 + + ```java + public class Text { + public static finalint STYLE_BOLD = 1<<0; // 1 + public static finalint STYLE_ITALIC = 1<<1; // 2 + public static finalint STYLE_UNDERLINE = 1<< 2; // 4 + public static finalint STYLE_STRIKETHROUGH = 1<<3; // 8 + // 매개변수 styles는 0개 이상의 STYLE, 상수률 비트별 OR한 값이다. + public void applyStylesint styles () {} + } + ``` + + +- 비트필드 + + ```java + text.applyStyles(STYLE_BOLD | STYLE_ITALIC): + ``` + + 다음과 같이 비트별 OR를 사용해 여러 상수를 하나의 집합으로 모으게 될 시 만들어지는 집합을 **비트 필드**라고 함 + + 장점: 비트 필드 사용시, 비트별 연산을 사용해 합집합과 교집합 같은 집합 연산을 효율적으로 수행할 수 있음 + 단점1: 비트 값이 그대로 출력되면 단순한 정수 열거 상수를 출력할 때보다 해석하기가 더 어려움 + 단점2: 비트 필드 하나에 녹아 있는 모든 원소를 순회하기 까다로움 + 단점3: 최대 몇 비트가 필요한지를 API 작성 시 미리 예측하여 적절한 타입을 미리 선택해야 함 + →API를 수정하지 않고는 비트 수를 더 늘릴 수 없기 때문 + +- java.util 패키지의 EnumSet 클래스 + 비트 필드의 더 나은 대안으로서 열거 타입 상수의 값으로 구성된 집합을 효과적으로 표현해 줌 + +장점1: Set 인터페이스를 완벽히 구현함 +장점2: 타입 안전함 +장점3: 다른 어떤 Set 구현체와도 함께 사용 가능함 +장점4: EnumSet의 내부는 비트 벡터로 구현되어 있어 비트 필드에 비견되는 성능을 보여줌 +→removeAll과 retainAll 같은 대량 작업은 비트를 효율적으로 처리할 수 있는 산술 연산을 써서 구현됨 +장점5: 비트를 직저 다룰 때 흔히 겪을 수 있는 난해한 작업을 EnumSet이 다 처리해줌 + +- 수정 코드: + + ```java + public class Text { + public enum Style { BOLD, ITALIC, UNDERLINE, STRIKETHROUGH } + // 어떤 Set을 넘겨도 되나, EnumSet이 가장 쫗다. + public void applyStyles(Set