Java/Java

제네릭 (Generic)

마손리 2023. 3. 7. 00:02

제네릭(Generic)

제네릭은 임시적인 타입을 정해주는 문법적 요소로 제네릭을 사용하면 클래스내의 필드(클래스변수 제외), 매개변수, 메서드에 구체적인 타입(String, int, char등)을 지정해 주지 않아도 되며 해당 클래스의 인스턴스를 생성할때 구체적인 타입을 정해준다. 즉 이를 이용하여 작성한 클래스 또는 메서드의 코드가 특정 데이터 타입에 얽매이지 않게 해둘수 있다.

 

 

기본 사용 방법

public class Main {
    public static void main(String[] args) {
        Generic<Double> genericDouble = new Generic(1.0); // Double 래퍼클래스(wrapper class)
        Generic<Integer> genericInt = new Generic(1); // Integer 래퍼클래스
        System.out.println(genericDouble.method(10.0));
        System.out.println(genericInt.method(10));
    }
}
class Generic<T>{ // 타입 매개변수 선언 <T,V>
    T field;
    static int staticField; // 클래스 변수에는 타입 매개변수를 사용할수 없다.
    public Generic(T field) {
        this.field = field;
    }
    T method(T var){
            return var;
        }
}

클래스 정의타입매개변수 "T"를 "<>"를 이용하여 클래스명 뒤에 선언해준다. 이후 사용할 변수, 매개변수 혹은 메서드에 다른 데이터타입을 지정해주듯이 사용하면된다. 

 

클래스 정의 이후, 인스턴스 생성시 타입 매개변수에 치환될 타입을 지정해줍니다. 단, 해당 타입은 기본타입(int, double, char 등)으로 지정할수 없으므로 Integer, Double과 같은 래퍼 클래스로 사용합니다. 또한 치환될 타입으로는 어떠한 클래스 및 인터페이스로도 가능하다.

(래퍼클래스란, 특정 기본타입의 참조타입형이며 클래스로 이루어저 특정 메서드들을 호출할수있다.)

 

제네릭 클래스 정의시 주의할 점으로 해당 클래스의 클래스변수에는 타입 매개변수를 사용할수 없다.

해당 클래스의 인스턴스를 생성하면서 타입 매개변수의 타입이 정해지는데 클래스 변수는 인스턴스 생성없이 사용이 가능하기 때문이다.

 

 

타입 매개변수에 다형성적용 클래스 할당

class Flower {}
class Rose extends Flower {}
class RosePasta {}

class Basket<T> {
    void method(T polymorphism){}
}

class Main {
    public static void main(String[] args) {
        Basket<Flower> flowerBasket = new Basket<>();
        flowerBasket.method(new Flower());
        flowerBasket.method(new Rose());      // 다형성 적용
        
        //flowerBasket.method(new RosePasta()); 에러
        Basket<RosePasta> rosePastaBasket = new Basket<>();
        flowerBasket.method(new RosePasta()); 
        //해당 클래스를 타입 매개변수에 참조해주어야 메서드 사용가능
    }
}

위와 같이 부모클래스를 타입 매개변수에 할당해주면 해당 클래스내의 변수, 메서드, 매개변수로 하위클래스를 사용해줄수 있다. 

 

 

제한된 제네릭 클래스의 인스턴스화

interface Plant {}
class Flower implements Plant {}
class Rose extends Flower implements Plant {}

class Tree{}

class Basket<T extends Plant> {} // 타입 매개변수의 다형성 적용

class Main {
    public static void main(String[] args) {
        // 인스턴스화
        Basket<Flower> flowerBasket = new Basket<>();
        Basket<Rose> roseBasket = new Basket<>();
        //Basket<Tree> TreeBasket = new Basket(); 인스턴스화 조차 불가능
    }
}

이전예제와 비슷하지만 다른점은 Basket 클래스의 타입 매개변수에 extends 키워드를 사용하여 해당 클래스의 인스턴스화 조차 제한을 줄수 있다.

 

제한된 제네릭 클래스의 인스턴스화2

interface Plant {}
class Flower{}
class Rose extends Flower implements Plant {}

class Basket<T extends Flower & Plant> {} // 상위클래스가 먼저 위치한뒤 연산자 "&" 이후 인터페이스

class Main {
    public static void main(String[] args) {
        // 인스턴스화
        Basket<Rose> roseBasket = new Basket<>();
        // Basket<Flower> flowerBasket = new Basket<>(); Flower는 Plant 인터페이스를 구현하지 않으므로 에러 발생
    }
}

인터페이스를 이용하여 인스턴스 생성을 더 제한시킬수 있다.

 

 

제너릭 클래스

class Flower{}

class Basket<T> {
    public <T> void method(T item){ // 메서드에서도 타입 매개변수 생성 가능, 해당 <T>는 클래스의 <T>와 다름
        item.equals("Object 메서드");
        //item.length(); String 클래스의 메서드 사용불가
        // 매개변수의 타입이 확실히 정히지지 않앗으므로  String, Integer등의 메서드등의 메서드들은 사용불가
        // 최상위 클래스인 Object의 메서드들만 사용가능
    }
}

class Main {
    public static void main(String[] args) {
        // 인스턴스화
        Basket<Flower> roseBasket = new Basket<>();
        
        roseBasket.method( "item");
        roseBasket.method( 123);
        // 메서드 타입 매개변수를 통해 하나의 메서드에 여러타입의 매개변수를 사용할수 있음
    }
}

클래스 뿐만아니라 메서드에도 타입 매개변수를 선언해줄수 있으며 접근 제한자 뒤에 위치한다. 

 

또한 해당 클래스의 타입 매개변수메서드의 타입 매개변수같은 이름을 가지고 있더라도 서로 다른 별개의 것으로 간주된다.

 

 

와일드 카드

와일드 카드는 어떠한 타입으로든 대체될 수 있는 타입 파라미터를 의미하며 기호 "?"로 와일드카드를 사용할수 있다.

<? extends T>
<? super T>
<?> // <? extends Object>와 같다

extends는 T타입을 포함한 하위 클래스의 인스턴스들을 타입 파라미터로 받을수 있으며 super상위 클래스의 인스턴스들을 받을수 있다. <?>의 경우 모든 객체를 받을수 있다.

 

 

예제)

class CarBrand{}

class Domestic extends CarBrand{}
class Foreign extends CarBrand{}

class Hyundai extends Domestic{}
class Kia extends Domestic{}

class Bmw extends Foreign{}
class Benz extends Foreign{}

class Owner<T>{
    public T Car;
    public Owner(T car){
        this.Car = car;
    }
}

class Manufacturer{
    public static Owner<? super Domestic> owner;
    public static void manufactory(Owner<? extends Foreign> owner){
        System.out.println("Europe");
    }
    public static void manyfactory(Owner<? extends Domestic> owner){
        System.out.println("Korea");
    }

    public static void foriegnBranch(Owner<? super Foreign> owner){
        System.out.println("Foreign");
    }
    public static void domesticBranch(Owner<? super Domestic> owner){
        System.out.println("Domestic");
    }

}

class Main {
    
    public static void main(){
        Owner<Foreign> foreign = new Owner<>(new Bmw());
        Owner<Domestic> domestic = new Owner<>(new Kia());

        Manufacturer.manufactory(foreign); //<? extends Foreign> Foreign 클래스를 포함한 모든 하위 클래스
        Manufacturer.manyfactory(domestic);
        //Manufacturer.manufactory(domestic); 오류 발생, Domestic의 하위 클래스이므로 오류 발생

        Owner<CarBrand> foreign2 = new Owner<>(new Benz());

        //Manufacturer.manufactory(foreign2); 오류발생 CarBrand는 Foreign의 상위클래스이다.
        Manufacturer.foriegnBranch(foreign2); // <? super Foreign> Foreign클래스를 포함한 모든 상위클래스

        //Manufacturer.owner = foreign; 오류발생
        Manufacturer.owner = foreign2;
    }
}

 

  • CarBrand
    • Domestic
      • Hyundai
      • Kia
    • Foriegn
      • BMW
      • Benz

위 코드의 상속 계층도이며 상속계층도에 안맞는 코드들은 모두 컴파일 오류를 발생한다.

 

 

'Java > Java' 카테고리의 다른 글

컬렉션 프레임워크 (Collection Framework)  (0) 2023.03.08
try(), catch() - 예외 처리(Exception Handling)  (0) 2023.03.07
열거형 (Enum)  (0) 2023.03.06
의존성 주입(Dependency Injection, DI)  (0) 2023.03.05
복사 생성자와 싱글톤  (0) 2023.03.05