소스코드를 보다가 <? extends > 라는 코드를 봤다.
?????
찾아봤다..
제네릭 표현 중 하나라고 한다.
제네릭
클래스 내부에서 사용할 데이터 타입을 나중에 인스턴스를 생성할 때 확정하는 것
객체의 타입을 컴파일 시 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
class Person<T> {
public T info;
}
public class Generic{
public static void main(String[] args){
Person<String> p1 = new Person<String>();
Person<StringBuilder> p2 = new Person<StringBuilder>();
}
}
개인적으로 이 예제가 가장 제네릭을 이해하기 쉬웠다.
Person 클래스의 info 필드의 데이터 타입은 T로 되어있다.
하지만 T라는 데이터 타입은 존재하지 않는다. 이 값은 인스턴스가 생성될 때 정해진다.
따라서 main에서 p1, p2가 생성되었을 때
p1.info 라고 선언을 해서 사용하면 T는 String이 되고
p2.info 라고 선언을 해서 사용하면 T는 StringBuilder가 된다.
인스턴스를 생성할 때 사용한 데이터 타입에 따라서 결정된다.
그렇다면 쓰면 뭐가 좋냐 ??
장점
타입의 안정성을 제공한다.
의도하지 않은 타입의 객체를 저장하는 것을 막고, 저장된 객체를 꺼내올 때 원래의 타입과 다른 타입으로 형변환되어 발생할 수 있는 오류를 줄여준다는 뜻이다.
타입 체크와 형변환을 생략할 수 있으므로 코드가 간결해 진다.
다룰 객체의 타입을 미리 명시함으로써 형변환을 하지 않아도 된다.
컬렉션 클래스 이름 바로 뒤에 저장할 객체의 타입을 적어주면, 컬렉션에는 지정한 타입의 객체만 들어갈 수 있다.
컬렉션 클래스<지정 할 객체의 타입> 변수명 = new 컬렉션 클래스<지정 할 객체의타입>();
ArrayList<Tv> tvList = new ArrayList<Tv>();
//tv객체만 저장할 수 있는 ArrayList 생성
tvList.add(new Tv());
tvList.add(new Radio()); //컴파일 에러
다형성을 사용해야 하는 경우 부모타입을 지정함으로써 여러 종류의 객체를 저장할 수 있다.
class Product{}
class Tv extends Product{}
class Audio extends product {}
//product 클래스의 자손객체들을 저장할 수 있는 ArrayList 생성
ArrayList<Product> list = new ArrayList<Product>();
list.add(new Product());
list.add(new Tv());
list.add(new Audio());
Product p = list.get(0); //형 변환 필요없음
Tv t = (Tv)list.get(i); //형 변환 필요
ArrayList가 Product 타입의 객체를 저장하도록 지정하면, 이들의 자손인 Tv와 Audio 타입의 객체도 저장할 수 있다.
다만, 꺼낼 때 원래의 타입으로 형변환 해야 한다.
Product 클래스가 Tv클래스의 조상이라 해도 아래와 같이는 할 수 없다.
ArrayList<Product> list = new ArrayList<Tv>(); //허용 x
List<Tv> tvList = new ArrayList<Tv>(); //허용
와일드 카드
제네릭에서는 보통 하나의 타입을 지정한다.
하지만 와일드 카드 ' ? ' 를 사용하면 하나 이상의 타입을 지정할 수 있게 해준다.
예를들어 와일드 카드가 있고, 그 타입이 Product의 자손이라고 선언하면, Tv객체를 저장하는 ArrayList<Tv> 또는 Audio 객체를 저장하는 ArrayList<Audio> 를 매개변수로 넘겨 줄 수 있다.
둘다 Product의 자손이기 때문이다.
문법
<?> | 타입 변수에 모든 타입을 사용할 수 있음 |
---|---|
<? extends T> | T타입과 T타입을 상속받는 자손 클래스 타입만 사용할 수 있음 |
<? super T> | T타입과 T타입이 상속받는 조상 클래스 타입만 사용할 수 있음 |
//Product 또는 그 자손들이 담긴 ArrayList를 매개변수로 받는 메소드
public static void printAll(ArrayList<? extends Product> list){
for(Unit u : list){
System.out.println(u);
}
}
복수의 제네릭
클래스 내에 여러개의 제네릭을 필요로 하는 경우가 있다.
제네릭을 나타내는 Type의 규칙은 아래와 같다.
E - Element (used extensively by the Java Collections Framework) K - Key N - Number T - Type V - Value S, U, V etc. - 2nd, 3rd, 4th types
class EmployeeInfo{
public int rank;
EmployeeInfo(int rank){
this.rank = rank;
}
//복수의 제네릭을 사용할 시 ,로 구분한다.
class Person<T, S> {
public T info;
public S id;
Person(T info, S id){
this.info = info;
this.id = id;
}
}
public class MultiGeneric{
public static void main(String[] args){
Person<EmployeeInfo, int> p1 = new Person<EmployeeInfo, int> (new EmployeeInfo(1), 1);
}
}
}
위의 코드는 예외가 발생되지만 형식만 보도록 하자.
예외의 해결은 아래 예제를 보도록 하자.
기본 데이터 타입과 제네릭
제네릭은 참조 데이터 타입에 대해서 사용할 수 있다. 기본 데이터 타입에서는 사용할 수 없다.
wrapper 클래스로 사용할 수 있다. -> 기본 타입을 객체타입으로 만드는 것
그래서 위 코드를 변경해보자.
class EmployeeInfo{
public int rank;
EmployeeInfo(int rank){
this.rank = rank;
}
class Person<T, S> {
public T info;
public S id;
Person(T info, S id){
this.info = info;
this.id = id;
}
}
public class MultiGeneric{
public static void main(String[] args){
EmployeeInfo e = new EmployeeInfo(1);
Integer i = new Integer(10);
Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e,i);
System.out.println(p1.id.intValue());
}
}
}
new Integer는 기본 데이터 타입인 int를 참조 데이터 타입으로 변환해주는 역할을 한다.
이러한 클래스를 wrapper 클래스라고 한다.
제네릭의 생략
제네릭은 생략 가능하다. 아래 두 개의 코드가 있는데 e와 i의 데이터 타입을 알기 때문에 가능하다.
EmployeeInfo e = new EmployeeInfo(1);
Integer i = new Integer(10);
Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e,i);
Person p2 = new Person(e,i); //이렇게 제네릭을 생략해도 가능
메소드에 적용
제네릭은 메소드에도 적용할 수 있다.
class EmployeeInfo{
public int rank;
EmployeeInfo(int rank){
this.rank = rank;
}
}
class Person<T, S>{
public T info;
public S id;
Person(T info, S id){
this.info = info;
this.id = id;
}
// U 데이터 타입은 info라는 매개변수의 데이터타입(EmployeeInfo)이 된다.
public <U> void printInfo(U info){
System.out.println(info);
}
}
public class GenericDemo {
public static void main(String[] args) {
EmployeeInfo e = new EmployeeInfo(1);
Integer i = new Integer(10);
Person<EmployeeInfo, Integer> p1 = new Person<EmployeeInfo, Integer>(e, i);
p1.<EmployeeInfo>printInfo(e);
p1.printInfo(e);// 제네릭 생략이 가능함
}
}
제네릭의 제한
extends
제네릭으로 올 수 있는 데이터 타입을 특정 클래스의 자식으로 제한할 수 있다.
class EmployeeInfo extends Info{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
public int getLevel(){
return this.rank;
}
}
// Info 클래스나 info의 자식 클래스만이 제네릭으로 올 수 있는 데이터 타입이 된다.
class Person<T extends Info> {
}
implements
class EmployeeInfo implements Info{
public int rank;
EmployeeInfo(int rank){ this.rank = rank; }
public int getLevel(){
return this.rank;
}
}
class Person<T extends Info>{// extends는 상속이 무엇인가가 아니라, 부모가 누군가를 확인하는 코드이다.(implement가 아니다.)
public T info;
Person(T info){ this.info = info; }
}
클래스와 인터페이스를 동시에 상속받고 구현해야 한다면 엠퍼센트(&) 기호를 사용하면 된다.
class AnimalList<T extends LandAnimal & WarmBlood> {...}
참고
https://devbox.tistory.com/entry/Java-%EC%A0%9C%EB%84%A4%EB%A6%AD
'나만의 정리' 카테고리의 다른 글
컴파일과 런타임 (0) | 2019.07.14 |
---|---|
[웹] White Domain, RBL, SPF, DKIM (0) | 2019.07.14 |
빌더 패턴(Builder Pattern) (0) | 2019.03.21 |
대용량 트래픽 처리(입문) (1) | 2019.03.04 |
인터넷 용어 정리 (0) | 2019.03.02 |
댓글