Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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