Skip to content
Original file line number Diff line number Diff line change
@@ -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<E> extends AbstractSet<E> {
// 생략
@Override public Iterator<E> iterator() {
return new Mylterator();
}
private class MyIterator implements Iterator<E> {
}
}
```

→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 ) 익명 클래스처럼 비정적 문맥에서 사용될 때만 바깥 인스턴스를 참조할 수 있으며, 정적 멤버는 가질수 없고 가독성을 위해 짧게 작성해야 함
133 changes: 133 additions & 0 deletions item30/item30_이왕이면 제네릭 메서드로 만들라_정민.md
Original file line number Diff line number Diff line change
@@ -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 <E> Set<E> union(Set<E> s1, Set<E>s2) {
Set<E> result=new HashSet<>(s1);
result.addAll(s2);
return result;
}
```

→타입 매개변수 목록은 <E>이고 반환 타입은 Set<E>

→union 메서드의 집합 3개(입력 2개, 반환 1개)의 타입이 모두 같아야 함
이유: 제네릭 메서드 안에서는 메서드 선언부에 명시한 타입 매개변수만 사용할 수 있기 때문
*이는 한정적 와일드카드 타입을 사용하여 유연하게 개선할 수 있음

ex3)수정 코드를 호출한 예제 코드

```java
public static void main(String[] args_){
Set<String> guys = Set.of("톰", "딕","해리");
Set<String> stooges = Set.of("래리", "모에", "컬리");
Set<String> aflCio = union(guys, stooges);
System.out.printIn(aflCio);
}
```

→프로그램 실행 결과: [모에, 톰, 해리, 래리, 컬리, 딕]

- 제네릭 싱글턴 팩터리
필요성:
불변 객체를 여러 타입으로 활용할 수 있게 만들어야 할 때, 제네릭은 런타임에 타입 정보가 소거 됨. 따라서 하나의 객체를 어떤 타입으로든 매개변수화할 수 있음
→이것이 가능하게 하기 위해 요청한 타입 매개변수에 맞게 매번 그 객체의 타입을 바꿔주는 정적 팩터리가 필요, 이를 ‘제네릭 싱글턴 팩터리’ 라고 함

- 제네릭 싱글턴 팩터리 사용 예제→항등함수를 담은 클래스
:항등함수 객체는 상태가 없으므로 매번 새로 생성하는 것은 낭비
→제네릭의 소거방식을 활용해 제네릭 싱글턴을 사용해 생성할 수 있음

:예제 코드1

```java
private static UnaryOperator<Object> IDENTITY_FN = (t) -> t;

@SuppressWarnings("unchecked")
public static <T> UnaryOperator<T> identityFunction() {
return (UnaryOperator<T>) IDENTITY_FN;
}
```

→IDENTITY_FN을 UnaryOperator<T>로 형변환하면 비검사 형변환 경고가 발생하지만 항등함수(입력 값을 변형없이 그대로 반환함)이기 때문에 T가 어떤 타입 이든 타입 안전성이 깨지지 않아 UnaryOperator<T>를 사용해도 타입 안전

→SuppressWarnings 애너테이션을 추가하여 오류나 경고 없이 컴파일 하도록 함

:예제 코드 2(위 코드 활용 ver)

```java
public static void main(String[] args) {
String[] strings = {"삼베","대마", "나일론"};
UnaryOperator<String> sameString = identityFunction();
for (String s : strings)
System.out.printIn(sameString.apply(s));
Number[] numbers = { 1, 2.0, 3L };
UnaryOperator<Number> sameNumber = identityFunction();
for (Number n : numbers)
System.out.println(sameNumber.apply(n));
}
```

→코드1의 제네릭 싱글턴을 UnaryOperator<String>과 UnaryOperator<Number>로 사용

→identityFunction() 호출 시 <T>가 각각 String과 Number로 추론

→apply(s)와 apply(n) 함수로 입력 문자열과 숫자를 그대로 반환하여 원본 값 출력

- 재귀적 타입 한정
: 자기 자신이 들어간 표현식을 사용하여 타입 매개변수의 허용 범위를 한정할 수 있음
:예제코드

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

→타입 매개변수 T는 Comparable<T>를 구현한 타입이 비교할 수 있는 원소의 타입을 결정하게 됨

→모든 타입은 자신과 같은 타입의 원소와만 비교할 수 있기 때문에, String은 Comparable<String>을 구현하고 Interger는 Comparable<Interger>을 구현하게 됨
: 주로 타입의 자연적 순서를 정하는 Comparable 인터페이스와 함께 쓰임
: Comparable을 구현한 원소의 컬렉션을 입력받는 메서드들은 보통 그 원소들을 정렬, 검색, 최솟값/최댓값 탐색을 위해 사용됨→이를 위해 컬렉션에 담긴 모든 원소가 상호 비교될 수 있어야 함
:예제 코드

```java
public static <E extends Comparable<E>> E max(Collection<E> 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 extends Comparable<E>>는 “모든 타입 E는 자신과 비교할 수 있다”라는 의미
→컬렉션에 담긴 원소의 자연적 순서를 기준으로 최댓값을 계산하게 됨
Loading