티스토리 뷰
제네릭
- 다양한 타입의 객체를 다루는 메서드나 클래스에 컴파일 시의 타입체크를 해주는 기능
- 컴파일러가 타입체크 해주기 때문에 type-safe한 프로그래밍 가능
- e.g.
List<String> list = new ArrayList();
로 선언하면 list에 integer가 들어가는 코드를 방지할 수 있음.
- e.g.
- 객체의 타입을 미리 명시해줌으로써 번거로운 형변환을 생략할 수 있음
- 제네릭 이전에는 다양한 종류의 타입을 다루는 메소드의 매개변수, 리턴타입으로 Object 타입의 참조변수를 사용한 뒤 형변환하는 방식을 활용.
//제네릭 이전
class Box {
Object item;
void setItem(Object item) {
this.item = item;
}
public Object getItem() {
return item;
}
}
//제네릭 사용
class Box<T> {
T item;
void setItem(T item) {
this.item = item;
}
public T getItem() {
return item;
}
}
Box<String> b = new Box<String>();
String item = b.getItem(); //형변환이 필요 없음
제네릭 용어
class Box<T> {}
- Box
: 제네릭 클래스 - T : 타입변수 or 타입매개변수
- Box : 원시타입 (raw type)
- T 에 String 을 대입한 경우, String : 매개변수화된 타입(parameterized type)
class Apple extends Fruit {
@Override
public String toString() {
return "Apple{}";
}
}
class Grape extends Fruit {
@Override
public String toString() {
return "Grape{}";
}
}
class Toy {
@Override
public String toString() {
return "Toy{}";
}
}
public class FruitBotEx1 {
public static void main(String[] args) {
Box<Fruit> fruitBox = new Box<>();
Box<Apple> appleBox = new Box<>();
Box<Toy> toyBox = new Box<>();
fruitBox.add(new Fruit());
fruitBox.add(new Apple()); // OK 다형성
fruitBox.add(new Grape());
appleBox.add(new Apple());
//appleBox.add(new Toy()); 컴파일 에러
System.out.println("fruitBox = " + fruitBox);
System.out.println("appleBox = " + appleBox);
System.out.println("toyBox = " + toyBox);
}
}
class Box<T> {
ArrayList<T> list = new ArrayList<>();
void add(T item) {
list.add(item);
}
T get(int i) {
return list.get(i);
}
int size() {
return list.size();
}
ArrayList<T> getList() {
return list;
}
@Override
public String toString() {
return list.toString();
}
}
제네릭 주의사항
- static 멤버에 타입변수를 사용할 수 없음
class Box<T> { static T item; //에러 static int compare(T t1, T t2) {} //에러 }
- static 멤버는 타입변수에 지정된 타입의 종류와 관계없이 늘 동일해야 하기 때문. JVM 메모리에 static 멤버 정보는 오직 하나만 올라간다. 따라서 Box<Apple>.item , Box<Grape>.item 과 같이 서로 다른 정보는 static 영역에 올릴 수 없음.
- 달리 설명하면, 제네릭 클래스의 타입변수는 객체를 생성할 때 결정되기 때문에 static 멤버가 만들어지는 시점에는 타입변수에 어떤 클래스가 올지 알 방법이 없음. 그래서 static 멤버에는 타입매개변수를 사용할 수 없음
## 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
### 바운디드 타입
- 타입 매개변수 T에 지정할 수 있는 타입의 종류를 제한
```java
// Fruit의 자손만 매개변수타입으로 지정 가능
class FruitBox<T extends Fruit> {
ArrayList<T> list = new ArrayList<T>();
//...
}
FruitBox<Apple> appleBox = new FruitBox<Apple>(); //OK
FruitBox<Toy> toyBox = new FruitBox<Toy>(); // 에러
- 만약 매개변수타입을 특정 인터페이스를 구현한 클래스로 제한하고 싶다면 이때도 ‘extends’ 를 사용한다. ‘implements’를 사용하지 않는다는 것을 주의.
interface Eatable {}
class FruitBox<T extends Eatable> { … }
//Fruit의 자손이면서 Eatable 인터페이스를 구현해야 하는 경우
class FruitBox<T extends Fruit & Eatable> { … }
와일드 카드
<?>
- 와일드카드
- 어떤 타입이든 될 수 있음. Object 타입과 다를 게 없음.
<? extends T>
- T와 그 자손들만 가능
<? super T>
- T와 그 조상들만 가능
- e.g.
static <T> void sort(List<T> list, Comparator<? super T> c)
class Juice {
String name;
public Juice(String name) {
this.name = name + " juice";
}
@Override
public String toString() {
return name;
}
}
class Juicer {
static Juice makeJuice(FruitBox<? extends Fruit> box) {
String tmp = "";
for (Fruit fruit : box.getList()) {
tmp += fruit + " ";
}
return new Juice(tmp);
}
}
public class FruitBoxEx3 {
public static void main(String[] args) {
FruitBox<Fruit> fruitBox = new FruitBox<>();
FruitBox<Apple> appleBox = new FruitBox<>();
fruitBox.add(new Apple());
fruitBox.add(new Grape());
appleBox.add(new Apple());
appleBox.add(new Apple());
System.out.println(Juicer.makeJuice(fruitBox));
System.out.println(Juicer.makeJuice(appleBox));
}
}
제네릭 메소드 만들기
메서드 선언부에 제네릭 타입이 선언된 메서드
제네릭 클래스에 정의된 타입매개변수와 제네릭 메서드에 정의된 타입매개변수는 별개의 것. 같은 타입문자 T를 사용하더라도 같은 것이 아님
- 메서드에 선언한 제네릭 타입은 지역 변수를 선언한 것과 같다고 생각하자
e.g.
제네릭 메소드 사용 전
static Juice makeJuice(FruitBox<? extends Fruit> box)
제네릭 메소드 사용 후
static <T extends Fruit> Juice makeJuice(FruitBox<T> box)
제네릭 메소드 호출할 때는 타입변수에 타입을 대입해야 한다.
e.g.
System.out.println(Juicer.<Apple>makeJuice(appleBox));
그러나 대부분의 경우 컴파일러가 타입을 추정할 수 있기 때문에 타입을 생략.
System.out.println(Juicer.makeJuice(appleBox));
(주의) 제네릭 메소드에 대입된 타입이 있을 경우, 같은 클래스 내에 있는 멤버에서 제네릭 메소드를 호출할 때
this.
,클래스이름.
을 생략할 수 없음System.out.println(<Fruit>makeJuice(appleBox));
: 컴파일에러System.out.println(this.<Fruit>makeJuice(appleBox));
: OKSystem.out.println(Juicer.<Fruit>makeJuice(appleBox));
: OK
public static void printAll(ArrayList<? extends Fruit> list, ArrayList<? extends Fruit> list2) {
for (Fruit fruit : list) {
System.out.println("fruit = " + fruit);
}
}
//제네릭 메소드를 이용해서 매개변수 타입 간소화
public static <T extends Fruit> void printAll(ArrayList<T> list, ArrayList<T> list2) {
for (Fruit fruit : list) {
System.out.println("fruit = " + fruit);
}
}
Erasure(소거) : 제네릭 타입의 제거
- 컴파일러는 제네릭 타입을 이용해서 소스파일을 체크한 뒤 필요한 곳에 형변환을 넣어준다. 그리고 제네릭 타입을 제거한다. 따라서 클래스 파일에는 제네릭 타입에 대한 정보가 없다.
- 이유 : 제네릭을 지원하지 않는 자바 하위 버전과의 호환성을 위해.
기선님 리뷰 영상
- GenericDao : spring JPA 에서
implements Repository<K,V>
을 붙이는 이유- 직접 GenericDao 만들어보면 좋을 듯.
- 브릿지 메소드
- 런타임 시 대입된 제네릭 클래스 타입을 알 수 있다! (Erasure 되지만 메타데이터는 남는다.)
Class<E> parameterizedType = (Class<E>)((ParameterizedType)this.getClass().getGenericSuperClass()).getActualTypeArguments()[0];
와일드카드 심화
- wildCard를 사용하는 케이스
- 타입매개변수에 어떤 클래스가 오든지 상관 없어. 관심 없어.
- e.g. List<?> 를 정의하는 것은, list에 정의된 size() 와 같이 element 타입과는 독립적인 메서드만 사용할 것임을 의미. 이때는 add() 와 같이 element 타입에 종속적인 메서드는 사용할 수 없다.
- Collections 의 메서드를 보면 wildCard를 쓰는 경우, 타입매개변수를 쓰는 경우가 혼재되어 있음.
public class Generics2 {
public static void main(String[] args) {
List<Integer> integerList = new ArrayList<>();
List<Number> numberList;
numberList = integerList; //컴파일 에러
List<? extends Number> wildCardNumberList;
wildCardNumberList = integerList; //OK
ArrayList<Integer> integerArrayList = new ArrayList<>();
List<Integer> integerList1;
integerList1 = integerArrayList;
}
}
References
- 자바의 정석
- 토비의 봄 TV
- 백기선님 자바스터디 영상
'JAVA' 카테고리의 다른 글
[자바 스터디] 13주차 : 람다식 (0) | 2022.03.20 |
---|---|
[자바 스터디] 11주차 : I/O (0) | 2022.03.06 |
[자바 스터디] 10주차(2) : Annotation (0) | 2022.02.26 |
[자바 스터디] 10주차(1) : Enum (0) | 2022.02.26 |
[자바 스터디] 9주차 : 멀티쓰레드 프로그래밍 (0) | 2022.02.20 |
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
링크
TAG
- OOP
- 코딩테스트
- 템플릿콜백
- 데코레이터패턴
- 프록시패턴
- SOLID
- 프록시
- gracefulshutdown
- 토비의스프링
- 코테
- 디자인패턴
- 메서드레퍼런스
- 자바
- provider
- BOJ
- 카카오
- AOP
- 객체지향
- c++
- 백기선
- 서비스추상화
- ec2
- 토비
- 김영한
- 예외처리
- java
- 자바스터디
- 토비의봄TV
- 스프링
- 프로그래머스
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
글 보관함