generic 개인공부 1
generic 개인 공부한 것 정리
📜 제목으로 보기
- 기본개념
- 제네릭(generic)이란?
- 제네릭 [타입 지정] 사용 이유
- 제네릭 타입
- 제네릭 메서드
- 제네릭 타입을 모든형이 아닌, 제한된 타입으로 제한
- 파라미터 정의시 사용되는 제네릭타입의 타입제한(와일드 카드)
- 예시1) 제네릭타입 E -> 제네릭메서드에 E(타입매개변수)에 대한 상/하한 제한 with PECS
- 제네릭메서드에 ? extends E(제네릭타입의 타입매개변수)로 상한제한(내부하위필드or하위값)을 하면, 꺼낸 값(E하위)이 내부필드(제타의 내부필드 E고정)에 대해 안전하여 제타E에, 외부에서 들어올 제네릭메서드 파라미터 값들을 안전하게 값 할당 -> 제타필드를 Produce해주기가 가능
- 제네릭메서드에 ? super E(제네릭타입의 타입매개변수)로 하한제한(내부상위필드or상위값)을 하면, 상위필드를 가진 자료구조로 생각하고 내부필드(제타의 내부필드 E고정)가 하위값으로 소비하는 것에 안전하여 외부에서 들어올 제네릭메서드 파라미터 값들(X) 상위필드 자료구조 -> 제타E의 하위값들 안전하게 대입 -> 제타필드를 Consumer해주기가 가능
- 제네릭 타입소거
- 오브젝트
기본개념
제네릭(generic)이란?
- 컴파일타임에 타입체크 하여 코드의 안전성을 높여준다.
-
우리는 항상 사용하고 있다.
- 정의시
<T>
를 타입매개변수라 한다. -
List<String>
자리에 뭔가를 주었다면 매개변수화된 타입이라고 부른다.
제네릭 [타입 지정] 사용 이유
컴파일러가 을 알게되어, 런타임 전에 [다른형 생성(add)시] 컴파일 에러가 나 해결됨구체형>
@DisplayName("")
@Test
void lesson9_techTalk_sadly_runtimeError() {
// 여러타입 가능한 자료구조라 ArrayList내부에 제네릭으로 정의해놨는데
// 1. 제네릭을 지정 안했줬다면?
// 1) 변수추출시, Object로 변<구>제 된다.
final List<Object> list = new ArrayList<>();
list.add("woowacourse");
list.add(1); // 2) 제네릭을 타입 미지정 결과, 아무 형이나 (실수에 의해) 넣을 수 있다.
//3) 타입이 다른 것이 들어갔는데, 확인안하고 하나의 타입으로 지정해서 써버리면, 형의 차이때문에, runtimeError가 발생한다.
//4) Object형이기 때문에 casting해서 하나의 형으로 받아줬다 -> 그 과정에서 실수로 들어간 다른형이, 런타입 에러를 발생시킨다?
final String result = (String) list.get(0) + (String) list.get(1);
//5) 즉, 여러형이 들어갈수 잇는 자료구조에서, 제네릭을 안쓴다면, 꺼낼때마다 확인해서 써야한다.
}
@DisplayName("")
@Test
void lesson9_techTalk_lovely_compileError() {
// 여러타입 가능한 자료구조라 ArrayList내부에 제네릭으로 정의해놨는데
// 2. 제네릭<T>타입매개변수를 지정해서 사용한다면?
// 1) 컴파일러님께서 알 수 있게 되어, runtime 전에 해결된다.
final List<String> list = new ArrayList<>();
list.add("woowacourse");
// 1) runtimeError까지 가기전에, CompileError를 발생시켜 아무 형이나 집어넣을 수 없게 형 검사를 해준다.
list.add(1);
}
컴파일러가 아는 것만 받다보니, [꺼낼 때 (타입검사 후 ->) 캐스팅(타입변환)] 안해줘도됨구체형>
@DisplayName("")
@Test
void lesson10_techTalk() {
final List<Object> list = new ArrayList<>();
list.add("woowacourse");
final Object o1 = list.get(0); // 1) 제네릭 타입미지정시, 캐스팅 안해주면 Object
final String result = (String) list.get(0); // 2) 캐스팅 해줘야 생성했던 형/원하는 형으로 구체형 추출가능
}
@DisplayName("")
@Test
void lesson10_techTalk_2() {
final List<String> list = new ArrayList<>();
list.add("woowacourse");
final String result = list.get(0); // 1) 제네릭 타입지정해줬다면, 컴파일러가 아는 것만 들어가 있어서, 꺼낼때 캐스팅이 필요없다.
}
주의점) 배열과 다르게, 호..제는 변제와 동치이므로, 완전히 동치일때만 가능하다.구>구>
- 우항의
호출시 <구체형> 제한
은 좌항의변.<구>.제
와 완전히 동치이다.- 반면, 우항의
호출시<>. 구체형.지정사용
은 변수에 들어가는 놈일 뿐이다.
- 반면, 우항의
-
즉, 좌항의 변구제와 우항의 호구제는 동일해야만 한다.
- 우항의 호구지와는 다른 개념
@DisplayName("")
@Test
void lesson11_techTalk_3() {
final Object[] arrays = new Integer[1];
List<Object> list1 = new ArrayList<Integer>(); // 호.<구>.제 는 좌변과 완전히 형이 동치여야한다.
// 컴팔일러가 알고 있는 변수와 다른 형이기 때문에 컴파일에러가 난다.
List<Object> list2 = new ArrayList<>(List.of(1)); // 반면 호<>구.지는 object에 들어가는 것으로서 가능하다.
}
제네릭 타입
- **형(T)를 파라미터로 가지는 class or interface를 말한다. **
- class나 인터페이스 이름 뒤에 꺽쇠로 타입매개변수
class<T> or interface<T>
가 붙는다.
- class나 인터페이스 이름 뒤에 꺽쇠로 타입매개변수
제네릭 [T] 사용 이유
-
상태값(필드값) 등을
Object타입을 사용한 것처럼 모든 타입을 받아들여
저장할 수 있게 된다.-
Object타입으로 상태값을 저장할 때
private class Category { private final Object value; private Category(final Object value) { this.value = value; } public Object getValue() { return value; } }
-
제네릭타입(class)를 쓸 때
private class GenericCategory<T> { private final T value; public GenericCategory(final T t) { this.value = t; } public T getValue() { return value; } }
-
-
Object때와는 다르게, 컴파일러에게 타입을 지정해주고 사용하면
-
컴파일러가 알아서 에러를 내준다.
-
타입확인 -> 타입변환 안해줘도 된다.
-
제네릭 메서드
- 메서드 선언부에 제네릭타입이 선언된 것
- 타입매개변수의 범위가 메서드내로 한정되어, 제네릭타입의 변수와 전혀 별개의 변수임.
생성자 대신 주입자 메서드(setter) vs 제네릭 메서드
-
setter는 상태값(필드)에 주입하는 것이고
-
제네릭 메서드는 상태값(필드)와는 별개로 지역변수에서 사용하는 타입매개변수를 사용한다.
-
제네릭 메서드 정의
public <K> void printClassName(K k) { System.out.println("제네릭 타입의 필드 타입매개변수 = " + this.value.getClass().getName()); System.out.println("제네릭 메서드 타입매개변수 = " + k.getClass().getName()); }
- 생성자든, 메서드든,
호<>.구체형.지정사용
하기
//1. 제네릭 타입(class)에 저장하는 T는 Noodle형 객체를 호<>.구.지 -> 변.<구>.제로 자동으로 형 제한 final GenericCategory<Noodle> noodleGenericCategory = new GenericCategory<>(new Noodle()); //2. 제네릭 메서드의 타입은, Pasta형을 호<>.구.지정사용 noodleGenericCategory.printClassName(new Pasta());
- 출력
제네릭 타입의 필드 타입매개변수 = MainTest$Noodle 제네릭 메서드 타입매개변수 = MainTest$Pasta
- 생성자든, 메서드든,
제네릭 타입을 모든형이 아닌, 제한된 타입으로 제한
-
Noodle 및 Noodle 자식형들로만 넣고 싶을때? Coke 타입은 못들어오게 막고 싶을 때가 있을 것이다.
P-E, C-S 원칙 + Extends의 와일드카드를 사용하여 T에는 본인 및 그 자식들만 들오게 -> 나머지는 컴파일에러
-
P-E, C-S 원칙: 생성자/add 등의
Produce
상황에서는<? Extends >
의 와일드카드를 사용하여 T에는 본인 및 그 자식들만 들오도록 상한 경계를 준다.- 컴파일러들이 상한경계+그자식들만 알고 있어 나머지는 컴파일에러를 낸다.
-
와일드카드는 코드속
?
로서, 모든 타입이 다 가능하게 한다 -
그외에
<? super >
의 하한 경계는Consume
을 정의할 때 사용한다.
파라미터 정의시 사용되는 제네릭타입의 타입제한(와일드 카드)
-
제네릭타입(class)는 1개 타입 or 상/하한의 타입을 받아 저장하는 자료구조(?)에 사용된다.
-
제네릭타입 자료구조의
타입 지정(제한)
을 하면서 &&그 제네릭타입의 (생성)기능호출
은 더 바깥에서 Helper (Static)클래스의메서드 파라미터에서 변.<구>.제
로서 정하여 사용한다고 가정한다.- 참고로.. 호<>.구.지정사용시.. 와일드카드를 사용못하네… 상한 지정해주고 -> 정의부에서
? extends 상한
으로 바꿔서 파라미터 정의해주기
- 참고로.. 호<>.구.지정사용시.. 와일드카드를 사용못하네… 상한 지정해주고 -> 정의부에서
해석
상한의 와일드카드 쓴 제네릭 자료구조
-> (외부에선 하위객체위주로 인자대입)
내부 하위class 형 타입을 필드로 가짐 -> 상한class를 setter는 못해줌 But 하위필드를 꺼내 상한(상위class)변수에 할당 및 생성은 가능.
01 생성자or메서드 파라미터에 private static class GenericCategoryHelper {
public static void popNoodle(final GenericCategory<? extends Noodle> extendsNoodleCategory) {
// 1) Noodle 및 그이하가 꺼내지면 -> 하위class로 생각 -> 우항에 꺼낼 수 있다. -> 경계이면서 상위class인 Noodle에 다 받을 수 있다. (모든 경우)
final Noodle noodle = extendsNoodleCategory.getValue();
// 2) 하지만, 내부 하위class 필드를 가진다고 보면 -> 경계이면서 상위class인 Noodle의 값을 우항으로 대입할 수 없다.
// -> 모든 경우가 만족안되서 컴파일 에러가 난다.
// extendsNoodleCategory.setValue(new Noodle());
}
-
제네릭타입은 주로, 해당 자료구조 사용처 메서드에서,
파라미터에서 상하한제한
을 와일드카드로 건다- 파라미터쪽으로
외부에서는 최상위 형이하로 들어온다
-
이하로 들어온다
고 보장받으면꺼내는 것(getter)
,꺼낸 것을 주입하여 생성
하는 것은OK
이다.-
꺼낸 값을
변수가 부모&추상체에 해당하는 상한을 지정해주면
로그 이하를 모저리 받을 수 있기 때문
이다.- 우항: 꺼낸 것 -> Noodle과 Noodle이하
- 좌항: 변수, 주입되서 생성 -> Noodle로 지정해주면 그 이하 다 받아줌
-
꺼낸 값을
- 파라미터쪽으로
하한의 와일드카드 쓴 제네릭 자료구조
-> (외부에선 상위객체위주로 인자대입)
내부 상위class필드를 가짐 -> 하한이하의 하위class 대입(저장) 가능 + 꺼내면, 상위class기 때문에 경계의 하위class에 값꺼내 할당은 불가능
02 생성자or메서드 파라미터에 public static void pushNoodle(final GenericCategory<? super Noodle> superNoodleCategory) {
//1) Noodle 및 그 이상이 꺼내지면 -> 그 자료구조(내부 변수) 상위class 필드에 <- 하위class객체(Noodle)를 setter할 수 있다
superNoodleCategory.setValue(new Noodle());
//2) 하지만, 상위class 필드를 꺼내 -> 하위타입인 하한(Noodle) 담을 순 없다.
//final Noodle noodle = superNoodleCategory.getValue();
}
예시1) 제네릭타입 E -> 제네릭메서드에 E(타입매개변수)에 대한 상/하한 제한 with PECS
제네릭메서드
에 ? extends E(제네릭타입의 타입매개변수
)로 상한제한(내부하위필드or하위값)을 하면, 꺼낸 값(E하위)이 내부필드(제타의 내부필드 E고정)에 대해 안전
하여 제타E에, 외부에서 들어올 제네릭메서드 파라미터 값들을 안전하게 값 할당 -> 제타필드를 Produce
해주기가 가능
제네릭메서드
에 ? super E(제네릭타입의 타입매개변수
)로 하한제한(내부상위필드or상위값)을 하면, 상위필드를 가진 자료구조로 생각하고 내부필드(제타의 내부필드 E고정)가 하위값으로 소비하는 것에 안전
하여 외부에서 들어올 제네릭메서드 파라미터 값들(X) 상위필드 자료구조 -> 제타E의 하위값들 안전하게 대입 -> 제타필드를 Consumer
해주기가 가능
PECS에서 P와 C는 제네릭타입(class, interface)속 필드를 생성(초기화)해주느냐, 갖다쓰느냐의 이야기였다.
제네릭 타입소거
- 컴파일타임에 타입을 검사
- 런타임에는 소거
- 해당 정보를 알 수 없다.
- Java 5에서 등장했기 때문
- 4이전 기존코드도 돌아가게 하기 위해
타입소거
가 등장함.
오브젝트
오브젝트 속 if instanceof(타입검사)를 줄이는 generic
내 생각
- Object가 모든 타입을 받아들였지만, 각 instance검사후 타입캐스팅(타입변환후) 작업하는 것처럼하는것 처럼
- 추상체는 여러 구상체를 받을 수 있지만, 각 구상체마다 하는 일이 달라진다면, 전략패턴을 썼었고
-
여러 구상체를 받는
외부 추상체를 파라미터로 받아
들어오는 구상체마다 instanceof로 자신 내부의 일이 달라지는 클래스
가 있다면- 외부 추상체를 T형으로 받고, class옆에는 ? extends T의 상한제한을 걸어주면
-
외부 추상체
로만 알아서 instanceof를 쓰던 타 클래스에서제네릭 적용
을 통해외부 특정 구상체
를 아는 타 클래스가 되도록 한다.
- 외부 특정 구상체를 아는
제네릭 타클래스
가 만약, 추상클래스 + 템메패턴이라면 -> 자식클래스 또한제네릭 타클래스<T>의 제네릭을 받는 추상클래스
로 선언할 수 있고, new 자식()을 만들어도, 추상클래스로서 {}중괄호로 실시간 다르게 구현해야만 한다.