[Java] Generic

주재완·2024년 7월 27일
0

Java

목록 보기
13/13
post-thumbnail

목표

  • Generic & Generic Method의 활용
  • 한정형 형인자(bounded type parameter)의 활용
  • 와일드 카드(?)의 활용

공통적으로 사용할 클래스는 아래와 같습니다.

  • Vehicle 클래스
package com.velog.generic;

public class Vehicle {
    private String name;
    private double efficiency;

    public Vehicle() {
    }

    public Vehicle(String name, double efficiency) {
        setName(name);
        setEfficiency(efficiency);
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public double getEfficiency() {
        return efficiency;
    }

    public void setEfficiency(double efficiency) {
        this.efficiency = efficiency;
    }

    @Override
    public String toString() {
        return "name=" + name + ", efficiency=" + efficiency;
    }
}
  • Vehicle을 상속받는 Car 클래스
package com.velog.generic;

public class Car extends Vehicle {
    private int passengers;

    public Car() {
        super();
    }

    public Car(String name, double efficiency, int passengers) {
        super(name, efficiency);
        this.passengers = passengers;
    }

    public int getPassengers() {
        return passengers;
    }

    public void setPassengers(int passengers) {
        this.passengers = passengers;
    }

    @Override
    public String toString() {
        return super.toString() + ", passengers=" + passengers;
    }
}
  • Car를 상속받는 SportsCar 클래스
package com.velog.generic;

public class SportsCar extends Car {
    private String sound;

    public SportsCar() {
        super();
    }

    public SportsCar(String name, double efficiency, int passengers, String sound) {
        super(name, efficiency, passengers);
        this.sound = sound;
    }

    public String getSound() {
        return sound;
    }

    public void setSound(String sound) {
        this.sound = sound;
    }

    @Override
    public String toString() {
        return super.toString() + ", sound=" + sound;
    }
}

기본 활용

Generic은 다양한 타입들을 관리하는 역할을 합니다. 그러면 애초에 다형성으로 Object로 관리해도 될껀데 왜 Generic을 쓰는 것일까 보겠습니다.

  • With Object
package com.velog.generic;

public class ObjectBox {
    private Object vehicle;

    public ObjectBox() {
    }

    public ObjectBox(Object vehicle) {
        this.vehicle = vehicle;
    }

    public Object getVehicle() {
        return vehicle;
    }

    public void setVehicle(Object vehicle) {
        this.vehicle = vehicle;
    }
}
  • With Generic
package com.velog.generic;

public class GenericBox<T> {
    private T vehicle;

    public GenericBox() {
    }

    public GenericBox(T vehicle) {
        this.vehicle = vehicle;
    }

    public T getVehicle() {
        return vehicle;
    }

    public void setVehicle(T vehicle) {
        this.vehicle = vehicle;
    }
}
  • Test
package com.velog.generic;

public class ObjectBoxTest {
    public static void main(String[] args) {
        ObjectBox box = new ObjectBox();

        box.setVehicle("스트링 타입도 들어가네?");
        box.setVehicle(new Vehicle("자동차", 12.0));

        Object object = box.getVehicle();
        if(object instanceof Vehicle v) {
            System.out.println(v);
        }
    }
}
package com.velog.generic;

public class GenericBoxTest {
    public static void main(String[] args) {
        GenericBox<Vehicle> box = new GenericBox<>();

//        box.setVehicle("스트링 타입도 들어가네?"); // 컴파일 에러
        box.setVehicle(new Vehicle("자동차", 12.0));

        System.out.println(box.getVehicle());
    }
}

결정적인 차이는 바로 Object의 경우 정말 무엇이든 담을 수 있기 때문에 런타임 시점에 타입을 판단해야 됩니다. 따라서 번거롭게 instanceof 조건을 주어야 됩니다.

반면, 제네릭의 경우 객체를 처음 생성한 시점에 이미 무엇을 담을지 결정합니다. 즉, 컴파일 시점에 타입을 판단합니다. 그럴 경우 컴파일러가 에러를 바로 잡을 수 있고, 번거롭게 instanceof를 사용하지 않아도 되는 장점이 있습니다.

여기서 T를 특별히 타입 파라미터라 합니다.

주의사항

제네릭의 상속관계

Vehicle 을 상속받는 Car에 대하여,

  • CarVehicle에 들어갈 수 있습니다.
  • 하지만, GenericBox<Car>GenericBox<Vehicle>아무런 상관관계가 없습니다(캐스팅할 시 컴파일 에러 발생)

생성 시점

Generic 타입 파라미터는 객체 생성 시점에 적용됩니다.
즉, static 멤버로는 사용이 불가능 합니다.

static T t; // 컴파일 에러

Generic Method

Generic Method는 파라미터와 리턴타입으로 type parameter를 갖는 메서드입니다. 메서드 리턴 타입 앞에 타입 파라미터 변수를 언언하는 방법입니다.

아래는 Generic Method의 기본 구조입니다.

[제한자] <타입_파라미터, [...]> 리턴_타입 메서드_명(파라미터) {

}

아래는 그 예시입니다.

package com.velog.generic;

public class TypeParameterTest<T> {
    T t;
    
    public T method1(T t) {
        return t;
    }
    
    public <P> P method2(P p) {
        return p;
    }
}

type parameter T,P를 사용할 때, T는 이미 제네릭 박스 내 TypeParameterTest<T>에 있기 때문에 따로 처리해줄 필요가 없지만, P는 따로 정의해준 것이 없기에 메서드를 만들때마다 <P>와 같이 작성해주어야 합니다.

한정형 형인자(bounded type parameter)

필요에 따라 구체적인 타입을 제한할 필요가 있을 때 사용합니다.
예를 들어, Car 이하의 타입(Car, SportsCar)만으로 제한한다고 할 때, 다음과 같이 작성합니다.

<T extends Car> // T는 Car 또는 이를 상속 받아야 합니다.

인터페이스 역시 제한할 경우 extends를 사용합니다. 그리고 다양한 인터페이스로 제약조건을 걸 경우 & 로 연결합니다.

<T extends Serializable & Comparable<String>>

와일드 카드(?) 자료형

제네릭의 상속관계를 다시 보겠습니다.

Vehicle 을 상속받는 Car에 대하여,

  • CarVehicle에 들어갈 수 있습니다.
  • 하지만, GenericBox<Car>GenericBox<Vehicle>아무런 상관관계가 없습니다(캐스팅할 시 컴파일 에러 발생)

이 때 파라미터로 GenericBox<Car>GenericBox<Vehicle>을 받는 API를 만들 경우를 고려해봅니다.

물론, Object로 받을 수 있는데, 뭔가 조금 더 제한을 주길 원합니다. 이럴 경우 사용하는 것이 ?, 즉 와일드카드라고 합니다.

와일드 카드는 실제 type parameter가 무엇인지 모르거나 구지 신경쓰지 않을 경우 사용합니다. 다음은 그 종류입니다.

표현설명
Generic type<?>타입에 제한이 없음
Generic type<? extends T>T 또는 이를 상속받아야 함
Generic type<? super T>T 또는 그 조상

예를 들어 GenericBox<Car>GenericBox<Vehicle>을 파라미터로 받는 메소드의 경우 다음과 같이 표현할 수 있습니다.

void method(GenericBox<? super Car> box) {}

PECS

PECS(Producer Extends Consumer Super) 는 다음과 같이 와일드 카드로 표현된 자료형에서 활용 방법을 제시해줍니다.

Producer Extends

제네릭 타입을 꺼낸다면 또는 생산(produce)하면 extends를 사용합니다.

void method(GenericBox<? extends Car> box) {}

Consumer Super

제네릭 타입을 넣는다면 또는 소비(consume)하면 super를 사용합니다.

void method(GenericBox<? super Car> box) {}

이 둘을 확인해보면 다음과 같습니다.

package com.velog.generic;

public class WildCard<T> {
    void producerMethod(GenericBox<? extends Car> box) {
        System.out.println(box.getVehicle());
//        box.setVehicle(new Vehicle()); // 컴파일 에러
//        box.setVehicle(new Car()); // 컴파일 에러
//        box.setVehicle(new SportsCar()); // 컴파일 에러
    }

    void consumerMethod(GenericBox<? super Car> box) {
        System.out.println(box.getVehicle());
//        box.setVehicle(new Vehicle()); // 컴파일 에러
        box.setVehicle(new Car());
        box.setVehicle(new SportsCar());
    }
}
package com.velog.generic;

public class WildCardTest {
    public static void main(String[] args) {
        GenericBox<Vehicle> vehicleGenericBox = new GenericBox<>();
        GenericBox<Car> carGenericBox = new GenericBox<>();
        GenericBox<SportsCar> sportsCarGenericBox = new GenericBox<>();

        vehicleGenericBox.setVehicle(new Vehicle("트럭", 10.0));
        carGenericBox.setVehicle(new Car("승용차", 11.0, 4));
        sportsCarGenericBox.setVehicle(new SportsCar("스포츠카", 5.0, 2, "부와앙"));

        WildCard<Car> wildCard = new WildCard<>();

//        wildCard.producerMethod(vehicleGenericBox); // 컴파일 에러
        wildCard.producerMethod(carGenericBox);
        wildCard.producerMethod(sportsCarGenericBox);

        wildCard.consumerMethod(vehicleGenericBox);
        wildCard.consumerMethod(carGenericBox);
//        wildCard.consumerMethod(sportsCarGenericBox); // 컴파일 에러
    }
}
profile
언제나 탐구하고 공부하는 개발자, 주재완입니다.

0개의 댓글

관련 채용 정보