컨디션 조절에 실패하여 오늘의 기분은 매우 안좋다. ㅜ ㅜ
제네릭을 알려주면서 바운드, 언바운드 제네릭에 대해서 새롭게 알아보았다.
물론 와일드카드를 알려준다고 했지만, 와일드 카드를 먼저 알려주면 순서가 뒤죽박죽 일 것 같아
제네릭에 대한 추가적인 설명으로 갈 것 같다.
public class Box<T> {
private T t;
public void set(T t) {
this.t = t;
}
public T get() {
return t;
}
}
여기서 언바운드 제네릭 은 <T> 타입 매개변수로 어떤 것이든 받을 수 있음을 의미 한다.
이전 시간에 제네릭이 뭔가에 대해 배웠지만 하나하나 찾으면 찾을 수록 다양한 것이 나온다.
public class PrintSound<T extends Animal> { <- Animal 이 구현된 클레스만 받겠다는 의미
private T n;
public PrintSound(T n) { this.n = n }
public void sound(){
this.n.hour(); <- 해당 기능을 쓸 수 있는 이유도 Animal 에 hour() 이라는 메서드를 명시했기 때문
//만약 언바운드 였다면 불가능 했을 것.
}
}
public class Cat implement Animal{ <-
@Override
public void hour(){
// 냐용
}
}
public class Dog implement Animal{
@Override
public void hour(){
// 멍멍
}
}
새로운 개념 등장에 머리를 쎄게 맞는 듯한 기분이다.
기분은 좋지 않지만 새로운 지식에 또 한번 즐거움을 느낀다.
public class BoundGeneric <T extends Payment & Comparable<T> & PaymentMethod>{
private T t;
public BoundGeneric(T t) {
this.t = t;
}
public void print(){
t.getAmount(); // 이건 Payment
t.pay(); // 이건 PaymentMethod
t.compareTo(t); // 이건 Comparable
}
}
이런 것도 된다.
만약 클레스를 넣고 싶다면 클레스는 맨 앞에 넣도록하자. 클레스가 인터페이스 뒤로 오게되면 오류를 일으킨다.
이 개념이 살짝 어렵다.
보통 WildCard 에사용 되며 다음과 같은 예시가 있다.
public void addInteger(List<? super Integer> list) {
list.add(100);
}
사실 와일드카드는 다뤄야할 내용이 크다.
그래서 이번에는 다루지 않고, 다음에 다시 다루기로 한다.
런타임에는 타입 정보가 사라진다 (타입 소거, Type Erasure)
Box<String> box1 = new Box<>();
Box<Integer> box2 = new Box<>();
System.out.println(box1.getClass() == box2.getClass()); // true!
String 과 Integer 은 타입이 다른데 런타임하게 되면 타입이 일치한다.
이유 : 하위 호환성을 위해 도입된 설계입니다 (제네릭이 자바 5에서 도입되었을 때 기존 코드와 충돌하지 않게 하기 위해).
정적(static) 영역에서는 제네릭 타입 매개변수를 사용할 수 없다
public class MyClass<T> {
private T value;
// static T instance; // static에서는 T 사용 불가
}
이유 : static은 클래스 전체에서 공유되는 요소이다.
하지만 T는 인스턴스 생성 시점에 결정되는 타입이기 때문에 클래스 수준(static)에서는 어떤 T가 올지 알 수 없다.
배열과 제네릭은 잘 안 맞는다.
List<String>[] arr = new ArrayList<String>[10]; <- 에러가 발생한다.
이유 : 배열은 런타임에 타입을 체크하는 반면,
제네릭은 컴파일 타임에 타입이 사라지는 특성(타입 소거)이 있어
배열과 제네릭을 섞으면 타입 안정성이 깨질 수 있다.
제네릭 타입으로는 예외를 던질 수 없다.
public class MyException<T> extends Exception {
// catch(T e) 불가, throw new T() 불가
}
이유 : 제네릭 타입 T는 런타임에 타입이 사라지기 때문에,
자바 예외 시스템은 구체적인 타입 정보가 있어야 throw, catch 등을 처리할 수 있다.
오버로딩에 제네릭만 다른 경우 충돌 가능
public void print(List<String> list) {}
public void print(List<Integer> list) {} // 타입 소거 때문에 컴파일 에러
이유 : 컴파일 후 타입 소거가 일어나면서 둘 다 print(List list)로 바뀌기 때문에
오버로딩 시 메서드 시그니처가 충돌하게 된다.
자바는 메서드 시그니처를 이름 + 파라미터 타입으로 구분하기 때문에, 충돌이 발생한다.
결론 : 타입소거로 인해서 이와 같은 일이 일어난다.
이렇게만 보면 제네릭이 정말 확장성이 좋고 유연한 도구라고 생각이 될 것이다.
하지만, 모든것에는 과유불급이라고 너무 많이쓰면 안좋다.
쓸데없이 많이 쓰게 되거나 괜히 쓰는 건 코드가 더 복잡해지고 애매해지고 가독성과 유지보수성이 떨어질 수 있다.
제네릭을 "잘" 써야 하는거지 많이 쓰면 안된다는 것이다.
타입 추론이 가능할 때에도 괜히 명시적으로 <T>를 남발하는 경우 <T> void print(T data) {
System.out.println(data);
}
void print(Object data) {
System.out.println(data);
}
지나친 제네릭 선언 public class Something<
T extends Comparable<? super T> & Cloneable,
U extends Map<String, List<T>>
> { ... }
제네릭 썼는데 결국 오브젝트로 할꺼라면? <T> void doSomething(T t) {
Object obj = (Object) t; // ???
}
모든 클래스에 무조건 <T> 붙이는 습관public class UserService<T> {
// 실은 전혀 T 쓸 일이 없음
public void login(String id, String pw) { ... }
}
오케이! 그러면 대체 언제 사용해야할까?
타입에 따라 다르게 작동해야할 때 OK
컨테이너를 만들거나 다뤄야 할 때 OK
타입 일관성이 필요한 복잡한 메서드가 필요할 때 OK
public class GenericUtils {
// 어떤 타입이든 배열을 출력할 수 있음
public static <T> void printArray(T[] array) {
for (T elem : array) {
System.out.print(elem + " ");
}
System.out.println();
}
public static void main(String[] args) {
String[] strArray = {"Java", "is", "fun"};
Integer[] intArray = {1, 2, 3};
printArray(strArray); // String도 가능
printArray(intArray); // Integer도 가능
}
}
public class Box<T> {
private T value;
public void set(T value) { this.value = value; }
public T get() { return value; }
}
public class Main {
public static void main(String[] args) {
Box<String> strBox = new Box<>();
strBox.set("Hello");
System.out.println(strBox.get());
Box<Integer> intBox = new Box<>();
intBox.set(123);
System.out.println(intBox.get());
}
}
public class UselessGeneric<T> {
public void greet(String name) {
System.out.println("Hello, " + name);
}
public static void main(String[] args) {
UselessGeneric<Integer> g = new UselessGeneric<>(); // Integer 왜씀?
g.greet("World");
}
}
public class BadBox<T> {
private Object value;
public void set(T value) {
this.value = value;
}
public T get() {
return (T) value; // 위험한 캐스팅
}
}
public class BoundGeneric <T extends Payment & Comparable<T> & PaymentMethod> 이런식으로도 사용이 된다는 점