제네릭 타입 (Generic Type)
class GenericClass <T> {
private T value;
}
- 제네릭 클래스: 클래스 명 옆에 <>와 <> 안에 타입 매개 변수를 정의한 클래스를 제네릭 클래스다.
- 타입 매개 변수는 클래스 내부에 타입 매개 변수가 필요한 곳에 사용하면 된다.
- 제네릭 클래스를 사용할 때는 타입을 미리 결정하지 않는다.
- 타입 매개 변수는 이후에 다른 타입으로 변할 수 있다.
GenericClass<Integer> integer = new GenericClass<Integer>(); //타입 직접 입력
GenericClass<Integer> integer2 = new GenericClass<>(); //타입 추론
- 제네릭 클래스의 인스턴스를 생성하는 시점에 <> 안에 원하는 구체적인 타입을 지정한다.
- 제네릭 클래스의 타입 매개 변수가 지정한 타입으로 변경된다.
- 제네릭 클래스의 인스턴스를 생성 시 타입 추론을 통해 타입을 생략할 수 있다.
- 이전에는 양쪽 <>에 모두 타입을 적어줬지만 컴파일러에 의해 생성하는 제네릭 타입을 생략할 수 있다.
- 왼쪽에 변수를 선언할 때의 <>안에 타입을 보고 오른쪽에 객체를 생성할 때 필요한 타입 정보를 얻을 수 있다. 따라서 스스로 타입 정보를 추론하기에 객체 생성 시 타입을 생략할 수 있다.
서로 바뀌면 안된다.
- 왼쪽에 변수를 선언할 때의 <>안에 타입을 보고 오른쪽에 객체를 생성할 때 필요한 타입 정보를 얻을 수 있다. 따라서 스스로 타입 정보를 추론하기에 객체 생성 시 타입을 생략할 수 있다.
- 이전에는 양쪽 <>에 모두 타입을 적어줬지만 컴파일러에 의해 생성하는 제네릭 타입을 생략할 수 있다.
- 제네릭 클래스 사용 시 원하는 타입을 모두 사용할 수 있다.
- 기본형은 사용할 수 없기에 래퍼 클래스로 사용해야 한다.
- 제네릭 클래스에서 <> 안에 여러 타입 매개 변수를 선언할 수 있다.
- 제네릭 클래스를 사용함으로 타입 안전성과 코드 재사용을 할 수 있다.
- 제네릭의 핵심은 사용할 타입을 미리 결정하지 않고 인스턴스 생성 시점에 타입을 결정한다.
- 메소드는 매개 변수에 인자를 전달해서 사용할 값을 결정한다.
- 제네릭 클래스는 타입 매개 변수에 타입 인자를 전달해서 사용할 타입을 결정한다.
- 제네릭 용어 정리
- 제네릭 - 특정 타입에 속한 것이 아닌 일반적으로, 범용적으로 사용할 수 있다는 의미.
- 제네릭 타입 - 클래스나 인터페이스를 정의할 때 타입 매개 변수를 사용하는 것.
- 제네릭 클래스, 제네릭 인터페이스를 합쳐서 제네릭 타입이라고 한다.
- 타입 매개 변수 - 제네릭 타입이나 메소드에서 사용되는 변수로 실제 타입으로 대체된다.
- 타입 인자 - 제네릭 타입을 사용할 때 제공되는 실제 타입이다.
- 제네릭 명명 관례: 일반적으로 대문자를 사용하고 용도에 맞는 단어의 첫 글자를 사용한다.
- E - Element
- K - Key
- N - Number
- T - Type
- V - Value
- S, U, V - 2nd, 3rd, 4th types
GenericClass object = new GenericClass(); //row type
// GenericClass<Object> object = new GenericClass<>(); -> 위와 동일하지만 아래를 권장
- 로 타입 (row type) : <>에 타입 인자를 지정하지 않는 것을 로 타입, 원시 타입이라고 한다.
- 로 타입을 사용하면 내부의 타입 매개 변수가 Object가 사용된다.
- 로 타입은 사용해서는 안된다.
- Object 타입을 사용하려면 로 타입을 사용하기보단 직접 Object를 타입 인자로 넘겨주는 것을 권장한다.
- 로 타입을 지원하는 이유?
- 제네릭이 없던 과거 코드와의 하위 호환을 위해 지원한다.
- 제네릭 클래스를 정의하는 시점에서는 어떤 타입이든 들어올 수 있기에 어떤 타입이든 수용할 수 있는 최상위 부모인 Object 타입으로 가정하고 Object가 제공하는 기능만 사용할 수 있다. (equals, toString....)
- 이런 경우 특정 클래스만 타입 인자로 사용할 수 없다.
- 모든 타입이 다 가능하다. (Animal, Integer, Double, Person ....)
- 이런 경우 특정 클래스만 타입 인자로 사용할 수 없다.
- 만약 Animal 클래스와 같은 특정 클래스와 관련된 하위 클래스들만 타입 인자로 하고 싶을 때는 어떻게 할까?
- 타입 매개 변수에 extends 키워드를 사용하면 해당 클래스와 자식 클래스로 타입 인자를 제한해서 받을 수 있다.
- 타입 인자로 입력될 수 있는 상한을 지정함으로 상위 타입의 원하는 기능까지 사용할 수 있다.
class Hospital <T extends Animal> {...}
- 이 경우 Hospital 제네릭 클래스는 Animal 하위 클래스부터 Animal 클래스까지 타입 인자로 받을 수 있다.
제네릭 메소드 (Generic Method)
- 제네릭 메소드 : 특정 메소드 단위로 제네릭을 도입할 때 사용한다.
- 제네릭 메소드도 제네릭 타입과 같이 extends 키워드로 타입 매개 변수의 상한을 제한할 수 있다.
- 제네릭 메소드 vs 제네릭 타입
//제네릭 타입의 형태
class GenericClass<T> {...}
//타입 인자 전달
new GenericClass<String>();
- 제네릭 타입은 객체를 생성하는 시점에 타입 인자가 전달되서 타입이 정해진다.
//제네릭 메소드 형태
public <T> T genericMethod(T t){...}
//타입 인자 전달
generic.<Integer>genericMethod(10);
- 제네릭 메소드를 선언할 때 <>는 항상 제네릭임을 알려주는 역할이기에 리턴 타입 앞에 선언한다.
- 제네릭 메소드는 메소드를 호출하는 시점에 타입 인자가 전달되서 타입이 정해진다.
- 메소드 호출하기 직전에 <>로 타입 인자를 지정한다. (.연산자 이후 메소드 명 이전에 지정한다.)
- 제네릭 메소드는 인스턴스 메소드, static 메소드에 모두 적용할 수 있다.
class GenericClass <T> {
T instanceMethod(T t){...} //가능
static T staticMethod(T t){...} //불가능
static <Z> Z staticMethod2(Z z){...} //제네릭 메소드 사용 시 가능
}
- 예제와 같이 static 메소드에는 제네릭 타입의 타입 매개 변수를 사용할 수 없다.
- 제네릭 타입은 객체 생성 시점에 타입이 정해지는데 static 메소드는 인스턴스 단위가 아닌 클래스 단위 (객체 생성하지 않고) 로 작동하기 때문에 제네릭 타입과는 무관하다.
- static 메소드에 제네릭을 사용하고 싶다면 제네릭 메소드를 사용해야 한다.
- 제네릭 메소드의 타입 추론
- 컴파일러는 메소드에 전달되는 인자의 타입을 통해 제네릭 메소드 타입 인자를 추론할 수 있다.
- 일반적으로 제네릭 메소드에서는 타입 추론을 통해 타입 인자를 생략해서 사용한다.
- 제네릭 타입과 제네릭 메소드의 타입 매개 변수가 같은 이름을 사용하면 누가 우선 순위가 높을까?
- 제네릭 타입보다 제네릭 메소드가 높은 우선 순위를 가진다.
- 타입 매개 변수 이름이 서로 같더라도 제네릭 타입과 제네릭 메소드의 타입 매개 변수는 서로 관련이 없다.
- 프로그래밍에서 모호한 것은 좋지 않기 때문에 서로 다른 이름으로 만드는 것이 좋다.
와일드 카드
- 와일드 카드 : 제네릭 타입을 조금 더 편리하게 사용할 수 있게 도와주는 것으로 여러 타입이 들어올 수 있다는 의미
- ? 키워드를 사용해서 정의한다.
- 와일드 카드는 제네릭 타입이나 메소드를 선언하는 것이 아닌 이미 만들어진 제네릭 타입을 활용할 때 사용한다.
//제네릭 메소드
static <T> void printGenericV1(Box<T> box) {
System.out.println("T = " + box.get());
}
//일반 메소드
//Box<Dog>, Box<Cat>, Box<Object>....
static void printWildcardV1(Box<?> box) {
System.out.println("? = " + box.get());
}
- 와일드 카드는 제네릭 타입이나 메소드를 정의할 때 사용하는 것이 아니다.
- 와일드 카드는 Box<Dog>, Box<Cat>처럼 타입 인자가 정해진 제네릭 타입을 전달받아서 활용할 때 사용한다.
- 와일드 카드 ?는 모든 타입을 다 받을 수 있다는 의미.
- <?> == <? extends Object>
- 모든 타입을 다 받을 수 있는 와일드 카드를 비제한 와일드카드라고 한다.
- 제네릭 메소드 vs 와일드카드
- 제네릭 메소드 - 타입 매개 변수가 존재하며 특정 시점에 타입 매개 변수에 타입 인자를 전달해서 타입을 결정하는 복잡한 과정을 거친다.
- 와일드카드 - 일반적인 메소드에 사용할 수 있고, 매개 변수로 제네릭 타입을 받을 수 있는 것일 뿐이기에 제네릭 메소드처럼 타입을 결정하거나 복잡한 과정이 없다.
일반 메소드에 제네릭 타입을 받을 수 있는 매개 변수가 하나 있는 것. - 제네릭 메소드와 와일드 카드는 비슷한 기능을 수행할 수 있다.
- 차이점 :
제네릭 메소드는 타입 매개 변수로 선언되어 있기에 타입 인자로 받은 타입을 명확하게 리턴해줄 수 있다.
와일드 카드는 매개 변수로 전달한 타입을 명확하게 리턴할 수 없다. - 메소드 타입들을 특정 시점에 변경하려면 제네릭 타입, 메소드를 사용해야 한다.
- 와일드카드는 이미 만들어진 제네릭 타입을 전달받아서 활용할 때 사용한다.
(메소드 타입들을 인자를 통해 변경할 수 없다.)
- 차이점 :
- 제네릭 타입이나 제네릭 메소드가 꼭 필요한 상황에서만 사용하고 그 외에는 와일드카드를 사용하는 것을 권장한다.
//제네릭 메소드 과정
//1. 전달
printGenericV1(dogBox)
//2. 제네릭 타입 결정 dogBox는 Box<Dog> 타입, 타입 추론 -> T의 타입은 Dog
static <T> void printGenericV1(Box<T> box) {
System.out.println("T = " + box.get());
}
//3. 타입 인자 결정
static <Dog> void printGenericV1(Box<Dog> box) {
System.out.println("T = " + box.get());
}
//4. 최종 실행 메서드
static void printGenericV1(Box<Dog> box) {
System.out.println("T = " + box.get());
}
//와일드카드 실행 과정
//1. 전달
printWildcardV1(dogBox)
//이것은 제네릭 메서드가 아니다. 일반적인 메서드이다.
//2. 최종 실행 메서드, 와일드카드 ?는 모든 타입을 받을 수 있다.
static void printWildcardV1(Box<?> box) {
System.out.println("? = " + box.get());
}
- 상한 와일드카드
- 제네릭 메소드와 마찬가지로 <? extends 타입> 으로 타입에 상한 제한을 할 수 있다.
ex) <? extends Animal> 이 경우 Animal과 그 하위 타입만 입력 받을 수 있다. - 최대 타입의 기능을 사용할 수 있다.
- 제네릭 메소드와 마찬가지로 <? extends 타입> 으로 타입에 상한 제한을 할 수 있다.
- 하한 와일드카드
- <? super 타입> 으로 하한 제한을 할 수 있다.
ex) <? super Animal> 의 경우 Animal을 포함한 상위 타입만 입력 받을 수 있다.
- <? super 타입> 으로 하한 제한을 할 수 있다.
타입 이레이저
- 타입 이레이저: 제네릭은 자바 컴파일 단계에서만 사용되고, 컴파일 이후 런타임에는 제네릭 정보가 삭제된다.
- 제네릭에 사용한 타입 매개 변수가 모두 사라진다.
- 컴파일 전인 .java에는 존재하지만 컴파일 후 바이트 코드인 .class에는 존재하지 않는다.
- 컴파일 시점에 제네릭 정보를 모두 삭제하고 상한 제한이 없는 타입 매개 변수는 Object로 변환된다.
- 타입 매개 변수 리턴받는 경우에도 컴파일러가 자동으로 Object에서 해당 타입에 대해서 다운 캐스팅하는 코드를 추가해준다.
- 상한 제한이 있는 타입 매개 변수는 컴파일 시점에 상한으로 지정한 타입으로 변환된다.
- 자바 제네릭은 단순히 생각하면 개발자가 직접 캐스팅하는 코드를 컴파일러가 대신 처리해주는 것.
- 컴파일 시점에 제네릭을 사용한 코드에 문제가 있는지 완벽하게 검증하기 때문에 컴파일러가 추가하는 다 캐스팅에는 문제가 발생하지 않는다.
public class EraserBox<T> {
//런타임에 타입 매개 변수를 활용하는 코드
public boolean instanceCheck(Object param) {
return param instanceof T; //오류
}
//런타임에 타입 매개 변수를 활용하는 코드
public void create() {
return new T(); //오류
}
}
- 타입 이레이저의 한계
- 런타임에 타입을 활용하는 코드는 작성할 수 없다.
- 생성자에 타입 매개 변수를 사용할 수 없다.
- 위 예시의 경우 T는 런타임에 Object가 되므로 첫 번째 코드는 항상 true를 리턴하게되고
두 번째 코드는 new Object()가 되어버리기에 의도한 것과 달라지게 된다.
- 런타임에 타입을 활용하는 코드는 작성할 수 없다.
출처: [인프런 김영한 실전 자바 - 중급편]
김영한의 실전 자바 - 중급 2편 강의 | 김영한 - 인프런
김영한 | 자바 제네릭과 컬렉션 프레임워크를 실무 중심으로 깊이있게 학습합니다. 자료 구조에 대한 기본기도 함께 학습합니다., 국내 개발 분야 누적 수강생 1위, 제대로 만든 김영한의 실전
www.inflearn.com
'Java > [인프런 김영한 실전 자바 - 중급편]' 카테고리의 다른 글
[인프런 김영한 실전 자바 - 중급편] 컬렉션 프레임워크 - LinkedList (0) | 2024.07.25 |
---|---|
[인프런 김영한 실전 자바 - 중급편] 컬렉션 프레임워크 - ArrayList (0) | 2024.07.25 |
[인프런 김영한 실전 자바 - 중급편] 예외 처리1, 2 (0) | 2024.07.23 |
[인프런 김영한 실전 자바 - 중급편] 중첩 클래스, 내부 클래스 2 (0) | 2024.07.23 |
[인프런 김영한 실전 자바 - 중급편] 중첩 클래스, 내부 클래스1 (2) | 2024.07.22 |