2월 2주차 스터디 발표 자료📖
2주차 기술면접 주제 중 네번 째 주제입니다!
Generic 개념에 대해 정리하고자 합니다.
참고(10분 테코톡): https://www.youtube.com/watch?v=Vv0PGUxOzq0&t=56
제네릭(Generic)
- 클래스나 메소드에서 사용할 내부 데이터 타입을 외부에서 지정하는 기법이다.
- 제네릭은 불공변(Invariant)을 띈다.
X가 Y의 서브타입이여도, 서로 관련이 없어 X는 X로만 사용할 수 있다. - 제네릭 클래스 생성하기 (예시)
1
2
3
4
5
6
7
8
9
10
class FruitBox<T> { //T는 타입매개변수
List<T> fruits = new ArrayList<>();
public void add(T fruit){
fruits.add(fruit);
}
}
// 외부에서 데이터 타입 지정하기
FruitBox<Apple> appleBox = new FruitBox<>();
제네릭을 사용하는 이유?
1. 타입 안정성
컴파일 타임에 자료형의 오류에 대한 검증을 수행하여, 런타임에 자료형에 안전한 코드를 실행할 수 있다.
원래의 의도? (예시)
과일이라면 종류에 상관없이 과일 박스 객체를 사용할 수 있도록 하고싶으니까, 최상위 클래스 Object로 정의해서 어떤 과일 객체든 접근이 가능하도록 해야겠다!1 2 3 4 5 6 7 8 9 10 11 12
class Apple {} class Banana {} class FruitBox { private Object fruit; public FruitBox (Object fruit){ this.fruit = fruit; } public Object getFruit() { return fruit; } }
- 개발자의 실수!
AppleBox가 아닌 BananaBox에서 Banana객체를 가져와야하는데, 실수로 AppleBox에서 Apple객체를 가져와 Banana 객체로 캐스팅해버렸다.1 2 3 4 5 6 7
public static void main(String[] args){ FruitBox appleBox = new FruitBox(new Apple()); FruitBox bananaBox = new FruitBox(new Banana()); Apple apple = (Apple)appleBox.getFruit(); Banana banana = (Banana)appleBox.getFruit(); // 실수 발생 }
그러나, 문법적으로는 오류가 없어 개발자는 코드를 실행하기 전까지 해당 오류 사실을 몰랐다.
➔ 즉, 자료형에 대한 검증이 컴파일 타임에 이루어지지 않았다. 제네릭을 사용하면 위의 문제를 해결할 수 있다!
자바 컴파일러는 제네릭 코드에 강한 타입체크를 하기때문에, 타입 안정성에 위배된다면 에러를 발생시킨다.1 2 3 4 5 6 7 8 9 10 11 12
class Apple {} class Banana {} class FruitBox<T> { private T fruit; public FruitBox (T fruit){ this.fruit = fruit; } public T getFruit() { return fruit; } }
2. 캐스팅 삭제
제네릭은 컴파일 타임에 미리 타입이 정해지므로, 캐스팅 작업이 필요하지않다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// 비제네릭 클래스
public static void main(String[] args){
FruitBox appleBox = new FruitBox(new Apple());
FruitBox bananaBox = new FruitBox(new Banana());
Apple apple = (Apple)appleBox.getFruit();
Banana banana = (Banana)bananaBox.getFruit();
}
// 제네릭 클래스
public static void main(String[] args){
FruitBox<Apple> appleBox = new FruitBox<>(new Apple());
FruitBox<Banana> bananaBox = new FruitBox<>(new Banana());
Apple apple = appleBox.getFruit();
Banana banana = bananaBox.getFruit();
}
제네릭 메소드
- <> 안에 지정한 타입으로 매개변수의 타입을 지정한다.
- 타입 파라미터의 범위는 메소드 블록 이내로 한정한다.
- 제네릭 클래스가 아니여도 정의할 수 있다.
제네릭 메소드 생성하기 (예시)
1 2 3 4 5 6 7 8 9 10 11 12 13
class Name { public <T> void printClassName(T t){ System.out.println(t.getClass().getName()); } } // ... // 제네릭 메소드 실행하기 public static void main(String[] args){ Name name = new Name(); name.printClassName(1); // java.lang.Integer name.printClassName(3.14); // java.lang.Double }
타입 매개변수의 제한
- 타입 매개변수가 특정 클래스의 하위 클래스만 가능하도록 설정할 수 있을까?
➔ 가능하다!!
1
2
3
4
5
6
7
class FruitBox<T> {
private List<Fruit> fruits = new ArrayList<>();
public void add(T fruit){
fruits.add(fruit); // 컴파일 에러 ( Fruit의 하위클래스가 아닐 수 있기때문에 )
}
}
1. 상한 경계
- T extends Fruit
타입 매개변수의 클래스는 Fruit 클래스이거나 Fruit의 하위 클래스여야 한다. (상위 클래스 x)1 2 3 4 5 6 7
class FruitBox<T extends Fruit> { private List<Fruit> fruits = new ArrayList<>(); public void add(T fruit){ fruits.add(fruit); // 컴파일 OK } }
2. 하한 경계
- T super Fruit
타입 매개변수의 클래스는 Fruit 클래스이거나 Fruit의 상위 클래스여야 한다. (하위 클래스 x)
와일드 카드 vs 제네릭 타입
- 와일드 카드? 어떤 타입인지 모르고, 무슨 타입인지 신경쓰지 않는다. 제네릭과 달리 공변성을 띈다.
제네릭 타입? 어떤 타입인지 모르지만, 타입이 정해지면 그 특성에 맞게 사용한다.
List로 예를 들어보자!
1 2 3 4 5 6 7 8 9 10 11 12 13 14
static void printList1(List<?> list){ /** 1. 와일드 카드는 list에 담긴 원소에 전혀 관심이 없기 때문에, 원소와 관련된 add 메소드를 사용할 수 없다. 2. 단, null은 가능하다. */ list.add(list.get(1)); // 컴파일 에러 } static <T> void printList2(List<T> list){ // 제네릭은 list에 담긴 원소에 관심을 갖기 때문에 원소와 관련된 add 메소드를 사용할 수 있다. list.add(list.get(1)); // 컴파일 성공 }
공변, 반공변, 불공변??
- 공변성?
- A가 B의 하위 타입일 때, List<A>가 List<B>의 하위 타입이다.
- 제네릭은 불공변 상태이기 때문에 한정적 와일드 카드 타입을 통해 공변을 지원한다.
- ex. Collection<? Extends E>로 변경 시, E 타입의 하위 타입을 자바 컴파일러가 인식할 수 있다.
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 32 33 34 35
public class Stack<E> { private final List<E> stack = new ArrayList<>(); private int position = -1; public void push(E element) { position += 1; stack.add(element); } public E pop() { E popElement = stack.remove(position); position -= 1; return popElement; } public boolean isEmpty() { return position == -1; } public int stackSize() { return position + 1; } // 1. 한정적 와일드 카드 사용 전 (불공변) public void pushAllNotWild(Collection<E> elements) { position += elements.size(); stack.addAll(elements); } // 2. 한정적 와일드 카드 사용 후 (공변) public void pushAllWild(Collection<? extends E> elements) { position += elements.size(); stack.addAll(elements); } }
1 2 3 4 5
// Number는 Integer의 상위 타입 Stack<Number> numberStack = new Stack<>(); List<Integer> integers = Arrays.asList(1, 2, 3); numberStack.pushAllNotWild(integers); // 컴파일 에러 발생 (불공변성) numberStack.pushAllWild(integers) // 컴파일 OK
- 반공변성?
- 공변성의 반대 경우이다. A가 B의 하위 타입일 때, List<B>가 List<A>의 하위 타입이다.
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
32
33
34
35
36
37
38
public class Stack<E> {
private final List<E> stack = new ArrayList<>();
private int position = -1;
public void push(E element) {
position += 1;
stack.add(element);
}
public E pop() {
E popElement = stack.remove(position);
position -= 1;
return popElement;
}
public boolean isEmpty() {
return position == -1;
}
public int stackSize() {
return position + 1;
}
public void pushAllWild(Collection<? extends E> elements) {
position += elements.size();
stack.addAll(elements);
}
// 1. 한정적 와일드 카드 사용 전 (불공변)
public void popAllNotWild(Collection<E> dst) {
dst.addAll(stack);
}
// 2. 한정적 와일드 카드 사용 후 (반공변)
public void popAllWild(Collection<? super E> dst) {
dst.addAll(stack);
}
}
1
2
3
4
5
6
7
Stack<Number> numberStack = new Stack<>();
List<Integer> integers = Arrays.asList(1, 2, 3);
numberStack.pushAllWild(integers);
List<Object> dst = new ArrayList<>();
numberStack.popAllNotWild(dst); // 컴파일 에러
numberStack.popAllWild(dst); // Object가 Integer의 상위타입이라 상관없음
➔ Number 타입의 list 원소 값을 Object 타입의 list에 넣을라 함
와일드 카드
1. 비경계 와일드카드(Unbounded Wildcards)
- ? 의 형태로 사용한다. (ex. List<?>)
- 모든 타입이 인자가 될 수 있다.
1 2 3 4 5 6
public static void printList(List<?> list){ for (Object ob : list) { System.out.println(ob + ""); } System.out.println(); }
비경계 와일드카드의 특징
- List<?>에서 Get한 원소는 Object 타입이다.
🤔 이유는??
비경계 와일드카드의 원소는 어떤 타입도 될 수 있으므로, 어떤 타입이든 읽을 수 있게 모든 타입의 조상 클래스인 Object로 반환한다.1 2 3 4
public static void get(List<?> list){ Object ob = list.get(0); String st = list.get(0); // 컴파일 에러 }
- List<?>에는 null만 삽입할 수 있다.
🤔 이유는??
비경계 와일드카드의 원소가 어떤 타입인지 알 수 없으므로 타입 안정성을 지키기 위해 null만 삽입이 가능!
1
2
3
4
5
6
7
8
public static void main(String[] args){
List<Integer> list = new ArrayList<>();
addDouble(list); // Integer 리스트에 Double 변수가 들어가는 모순 발생
}
private static void addDouble(List<?> list){
list.add(3.14); // 만약 모든 타입의 값을 넣는게 허용된다면??
}
2. 상한 경계 와일드카드(Uppder Bounded Wildcards)
- ? extends T 의 형태로 사용한다.
- T 혹은 T의 하위 클래스만 인자로 올 수 있다.
상한 경계 와일드카드의 특징
- List<? extend T>에서 Get한 원소는 T이다.
- 상한 경계 와일드카드의 원소는 T 혹은 T의 하위 클래스이다.
- 원소들의 최고 공통 조상인 T로 읽으면, 어떤 타입이 오든 T로 읽을 수 있다.
1 2 3 4 5 6
public static void printList(List<? extends Fruit> fruits) { for(Fruit fruit : fruits) { System.out.println(fruits + " "); } System.out.println(); }
- List<? extends T>에는 null만 삽입할 수 있다.
- 상한 경계 와일드카드의 원소가 어떤 타입인지 알 수 없다.
1 2 3
List<Apple> apples = new ArrayList<>(); List<? extends Fruit> = apples; fruits.add(new Banana()); // 컴파일 에러(List<Apple>에 바나나가 들어감)
- 상한 경계 와일드카드의 원소가 어떤 타입인지 알 수 없다.
3. 하한 경계 와일드카드 (Lower Bounded Wildcards)
- ? super T의 형태로 사용한다.
- T혹은 T의 상위 클래스만 인자로 올 수 있다.
하한 경계 와일드카드의 특징
- T 하한 경계 와일드카드의 원소는 T의 상위 클래스 중 어떤 타입도 될 수 있다.
- 어떤 타입이 와도 읽을 수 있도록, T들의 공통 조상인 Object로 받는다.
1 2 3 4 5 6
public static void printList(List<? super Fruit> fruits){ for(Object fruit:fruits){ System.out.println(fruits + ""); } System.out.println(); }
- 어떤 타입이 와도 읽을 수 있도록, T들의 공통 조상인 Object로 받는다.
- List<? super T>에는 T 혹은 T의 하위 클래스만 삽입할 수 있다.
- 하한 경계 와일드카드의 원소는 T 혹은 T의 상위 클래스이다.
1 2 3 4
List<? super Fruit> fruits = new ArrayList<>(); fruits.add(new Apple()); fruits.add(new Fruit()); fruits.add(new Food()); // 컴파일 에러 (Fruit의 상위클래스)
- 🤔 이유는??
List<Fruit>, List<Food>, List<Object> 중 어떤 리스트가 올지 fruits 배열은 모른다.
하지만 그 중 어떤 리스트여도, Fruit이나 Fruit의 하위 클래스를 원소로 추가할 수 있으므로
상위 클래스의 원소를 추가할 수 없다.
- 하한 경계 와일드카드의 원소는 T 혹은 T의 상위 클래스이다.