JAVA/about java

[Generic] 제네릭

dev_rosieposie 2023. 4. 30. 15:39

21 실수를 방지하기 위한 제네릭이라는 것도 있어요

  1. 제네릭이 자바에 추가된 이유는?
    • 타입 형변환에서 발생할 수 있는 문제를 방지하기 위해서
  2. 제네릭 타입의 이름은 T나 E처럼 하나의 캐릭터로 선언해야 하는가?
    • x
  3. 메소드에서 제네릭 타입을 명시적으로 지정하기 애매할 경우에는 <> 안에 어떤 기호를 넣어 주어야 하는가?
    • ?
  4. 메소드에서 제네릭 타입을 명시적으로 지정하기에는 애매하지만 어떤 클래스의 상속을 받은 특정 타입만 가능하다는 것을 나타내려면 <> 안에 어떤 기호를 넣어야 하는가?
    • ? extends 타입
  5. 제네릭 선언시 wildcard라는 것을 선언했을 때 어떤 제약사항이 있는지?
    • 매개변수에 사용해서 값을 얻을 때는 상관없지만, wildcard로 객체를 생성하고, 특정 타입을 세팅할 수는 없다
  6. 메소드를 제네릭하게 선언하려면 리턴 타입 앞에 어떤것을 추가해주면 되는지?
    • 원하는 제네릭 타입을 명시 ex) <T>, <Car>

제네릭이란?

  • 형 변환시 발생할 수 있는 문제를 사전에 방지하기 위해 만들어졌다. 여기서 "사전"이라고 함은, 실행시에 예외가 발생하는 것을 처리하는 것이 아니라 컴파일할 때 점검할 수 있도록 한 것을 의미한다.
  • 명시적으로 타입을 지정할 때 사용하는 것
public void checkCastingDTO() {
    CastingDTO dto1 = new CastingDTO();
    dto1.setObject(new String());
    String temp1 = (String) dto1.getObject();

    CastingDTO dto2 = new CastingDTO();
    dto2.setObject(new StringBuffer());
    StringBuffer temp2 = (StringBuffer) dto2.getObject();
    System.out.println(temp2);

    CastingDTO dto3 = new CastingDTO();
    dto3.setObject(new StringBuilder());
    StringBuilder temp3 = (StringBuilder) dto3.getObject();
    System.out.println(temp3);
}

문제 : 제네릭 사용전에는, 컴파일과 문제없이 실행된다 => Object 클래스는 모든 클래스의 부모 클래스 이므로 어떤 참조 자료형을 넘겨도 상관 없기 때문. 그러나 값을 꺼낼 때의 타입은 Object이므로, 각 타입에 알맞게 캐스팅을 해줬어야 했음

public void checkCastingDTO() {

    CastingGenericDTO<String> dto1 = new CastingGenericDTO<String>();
    dto1.setObject(new String());
    CastingGenericDTO<StringBuffer> dto2 = new CastingGenericDTO<StringBuffer>();
    dto2.setObject(new StringBuffer());
    CastingGenericDTO<StringBuilder> dto3 = new CastingGenericDTO<StringBuilder>();
    dto3.setObject(new StringBuilder());

    String temp1 = dto1.getObject();
    StringBuffer temp2 = dto2.getObject();
    StringBuilder temp3 = dto3.getObject();
}

해결 : 제네릭을 사용하여, 타입을 명시해주면 값을 가져올 때 캐스팅 할 필요 없음

 

<?> 이란

  • 제네릭 사용시, <> 안에 들어가는 타입은 기본적으로 어떤 타입이라도 상관 없다.
  • ?로 명시한 타입을 영어로는 wildcard 타입이라고 한다
public class WildcardSample{

    public static void main(String[] args){
        WildcardSample sample = new WildcardSample();
        sample.callWildcardMethod();
    }

    public void callWildcardMethod(){
        WildcardGeneric<String> wildcard = new WildcardGeneric<String>();
        wildcard.setWildcard("A");
        wildcardStringMethod(wildcard);
    }

    public void wildcardStringMethod(WildcardGeneric<String> c){
        Object value = c.getWildcard();
        System.out.println(value);
    }
}

문제 : 이 메소드의 매개 변수는 반드시 String을 사용하는 WildcardGeneric객체만 받을 수 있다

public void wildcardStringMethod(WildcardGeneric<?> c){
    Object value = c.getWildcard();
    System.out.println(value);
}

해결 : String 대신 ?을 사용하면 어떤 타입이 제네릭 타입이 되더라도 상관 없다. 하지만 메소드 내부에서는 해당 타입을 정확히 모르기 때문에 String으로 값을 받을 수 없고, Object로 처리해야 한다.

public void callWildcardMethod(){
    WildcardGeneric<?> wildcard = new WildcardGeneric<String>();
    wildcard.setWildcard("A");
    wildcardStringMethod(wildcard);
}

주의 : wildcard는 메소드의 매개 변수로만 사용하는 것이 좋다. 왜냐하면, 알 수 없는 타입에 String을 지정할 수 없기 때문이다.

즉, 어떤 객체를 wildcard로 선언하고, 그 객체의 값을 가져올 수는 있지만, wildcard 객체를 선언했을 때는 특정 타입으로 값을 지정하는 것은 불가능하다.

 

제네릭 선언에 사용하는 타입의 범위 지정

  • wildcard로 사용하는 타입을  제한할 수 있다
  • ? extends 타입
  • Bounded Wildcards라고 부른다
public class Car {
    protected String name;
    public Car(String name){
        this.name = name;
    }
    public String toString(){
        return "Car name="+name;
    }
}
public class Bus extends Car{
    public Bus(String name) {
        super(name);
    }
    public String toString(){
        return "Bus name="+name;
    }
}
public class CarWildcardSample {
    public static void main(String[] args){
        CarWildcardSample sample = new CarWildcardSample();
        sample.callBoundedWildcardMethod();
    }
    public void callBoundedWildcardMethod(){
        WildcardGeneric<Car> wildcard = new WildcardGeneric<Car>();
        //wildcard.setWildcard(new Car("Mustang"));
        wildcard.setWildcard(new Car("980"));
        boundedWildcardMethod(wildcard);
    }
    public void boundedWildcardMethod(WildcardGeneric<? extends Car> c){
        Car value = c.getWildcard();
        System.out.println(value);
    }

 

결과

// Car name=Mustang

Bus name=980

  • ? extends Car이라고 함은, 제네릭 타입으로 Car를 상속받은 모든 클래스를 사용할 수 있다는 의미.
    따라서, boundWildcardMethod()의 매개 변수에는 다른 타입을 제네릭 타입으로 선언한 객체가 넘어올 수 없다.
  • 즉, 컴파일시에 에러가 발생하므로 반드시 Car 클래스와 관련되어 있는 상속한 클래스가 넘어와야만 한다. (위에서는 string)

메소드를 제네릭하게 선언하기

  • 메소드 선언시 리턴 타입 앞에 제네릭한 타입을 선언해주고, 그 타입을 매개 변수에서 사용하면 컴파일 문제가 없다. 값 할당도 가능
public class GenericWildcardSample {

    public static void main(String[] args){
        GenericWildcardSample sample = new GenericWildcardSample();
        sample.callGenericMethod();
    }
    public <T> void genericMethod(WildcardGeneric<T> c, T addValue){
        c.setWildcard(addValue);
        T value = c.getWildcard();
        System.out.println(value);
    }
    public void callGenericMethod(){
        WildcardGeneric<String> wildcard = new WildcardGeneric<String>();
        genericMethod(wildcard, "Data");
    }
}