티스토리 뷰
공부/오늘의공부

제네릭

고여리 2023. 1. 6.

 제네릭은 클래스, 메서드의 코드를 작성할 때 타입을 지정하는 것이 아닌 나중에 지정할 수 있도록 일반화 해두는 것을 의미한다. 매개변수 타입이나, 리턴하는 값의 타입 정의를 외부로 미루게 되고 타입에 대해 유연성과 안전성을 확보하는 기술이다.

class Cart<T>{
    private T food;
    public Cart(T food){
        this.food = food;
    }
}

위에서 T를 타입 매개변수라고 하며 <T>와 같이 꺽쇠 안에 넣어 클래스 이름 옆에 작성해 줌으로서 클래스 내부에서 사용할 타입 매개변수를 선언가능하다. T는 임의의 문자로 어떤 문자를 사용해도 상관없다. 여러개 사용할 경우

클래스명 <T,K,V>와 같이 여러개 선언이 가능하다.

 

다만, 제네릭 클래스를 사용할때는 클래스 변수에는 타입 매개변수를 사용할 수 없다. 클래스 변수는 모든 인스턴스가 공유하는 변수이기 때문인데 클래스 선언후 여러 인스턴스에서 한 클래스 변수에  여러타입을 적용할 경우 클래스변수가 공통된 값을 공유할 수 없는 문제점이 발생하게 된다.

위와 같이 클래스 변수에는 사용이 불가능하다.

위의 제네릭 클래스를 인스턴스로 사용할 때에는 다음과 같이 선언해준다.

Cart<String> cart1 = new Cart<String>("chicken");

단, 타입 매개변수에 치환될 타입으로 기본 타입을 지정할 수는 없다. int,double와 같은 원시 타입을 지정해야하는 경우 Integer,Double 같은 래퍼클래스를 활용한다.

 

제네릭 클래스에 상속과 구현을 통해 다형성을 적용할 수 있다.

class Chicken{}; 
class FriedChicken extends Chicken{} //Chicken클래스를 상속받고 있다.
class Shose{}
class Cart<T>{
    private T food;
    public void setFood(T food) {
        this.food = food;
    }
}
public class main {
    public static void main(String[] args) {
        Cart<Chicken> chickenCart = new Cart<>();
        chickenCart.setFood(new FriedChicken()); //Cart클래스의 Chicken타입이지만 하위클래스 FriedChicken타입을 써도 오류가 나지 않는다.
        chickenCart.setFood(new Shose());// 상속받지 않기 때문에 Shose타입은 오류가 발생한다.
    }
}

위와 같이 상속관계에서 타입을 적절히 지정해 사용할 수 있다.

 

위와 같은 제네릭 클래스에서는 타입을 지정하는데 제한이 없지만 타입 매개변수를 선언할 때 아래와 같이 코드를 작성하면 Cart 클래스를 인스턴스 할때 chicken클래스의 하위 클래스만 지정하도록 제한된다.

class Chicken implements Animal{};
class FriedChicken extends Chicken {...}
class Cart<T extends Chicken>{...}

또한 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한도 가능하다. 이 경우에도 동일하게 extends를 사용한다.

interface Animal{...};
class Chicken implements Animal{...};
class Cart<T extends Animal>{...};

만약 특정 클래스를 상속받으면서 동시에 특정 인터페이스를 구현한 클래스만 타입으로 지정할 수 있도록 제한하려면 아래와 같이 &을 사용하여 코드를 작성한다. 이 경우 클래스를 인터페이스보다 앞에 위치시켜야 한다.

interface Animal{};
class Chicken implements Animal{};
class FriedChicken extends Chicken implements Animal{...}
class Cart<T extends Chicken & Animal>{...}

 

제네릭은 클래스 내부 메서드에서도 사용이 가능하다. 제네릭 메서드의 타입 매개변수 선언은 반환 타입 앞에서 이루어지며, 해당 메서드내에서만 선언한 타입 매개변수를 사용 가능하다.

class Cart {
	public <T> void add(T element){
    }
}

위와 같이 제네릭 메서드를 선언가능하다. 또한 제네릭 메서드의 타입 매개변수는 제네릭 클래스의 타입 매개변수와 별개의 것이다.

class Cart<T>{
    public T food;
    public <T> T buy(T count){
        return count;
    }
}
public class main {
    public static void main(String[] args) {
        Cart<String> cart = new Cart<>();//String타입의 cart인스턴스 생성
        cart.food = "치킨을 구매했습니다.";// 인스턴스변수에 String 할당
        System.out.println(cart.food.getClass().getName()); //getClass().getName()메서드를 통해 해당 변수의 클래스를 확인할 수 있다.
        System.out.println(cart.buy(3).getClass().getName());//메서드도 확인 가능하다.
    }
}
//출력값
//java.lang.String
//java.lang.Integer

동일하게 타입매개변수로 T를 사용한다고 해도 서로 다른 타입매개변수로 간주한다. 또한 클래스 명 옆에 선언한 타입 매개변수는 클래스가 인스턴스화 될 때 타입이 지정되고, 제네릭 메서드의 타입 지정은 메서드가 호출될 때 이루어짐을 알 수 있다. 또한 메서드 타입 매개변수는 static 메서드도 선언하여 사용이 가능하다.

String 클래스의 메서드는 제네릭 메서드를 정의하는 시점에는 사용이 불가능한데 제네릭 메서드를 정의하는 시점에 어떤 타입이 입력되는지 알 수 없기 때문이다.

위와같은 메세지가 나타난다

 

제네릭에서는 알 수 없는 타입을 나타낼 때 와일드 카드를 사용할 수 있다. 와일드 카드는 어떠한 타입으로든 대체될 수 있는 타입 파라미터를 의미하며 기호 ? 로 와일드 카드를 사용 가능하다.

일반적으로 와일드 카드는 extends와 super 키워드를 조합하여 사용한다.

<? extends T>
<? super T>

<? extends T> 는 와일드 카드에 상한 제한을 두는 것으로 T와 T를 상속받는 하위클래스 타입만 타입 파라미터로 받을 수 있도록 지정한다

반면 <? super T>는 와일드 카드에 하한 제한을 두는 것으로, T와 T의 상위 클래스만 타입 파라미터로 받도록 한다.

위의 키워드와 조합하지 않은 와일드카드<?>는 <? extends Object>와 같다.

 

'공부 > 오늘의공부' 카테고리의 다른 글

Iterator  (0) 2023.01.09
예외처리  (0) 2023.01.08
열거형  (0) 2023.01.05
인터페이스  (0) 2023.01.03
추상화  (0) 2023.01.03
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/07   »
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
글 보관함