Generic 2

황상익·2024년 10월 8일

Inflearn JAVA

목록 보기
48/61

타입 매개변수 제한1 - 시작

개 병원은 개만, 고양이 병원은 고양이만

public class DogHospital {
   private Dog animal;

    public void setAnimal(Dog animal) {
        this.animal = animal;
    }

    public void checkup(){
        System.out.println("동물이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());
        animal.sound();
    }

    public Dog bigger (Dog target) {
        return animal.getSize() > target.getSize() ? animal : target;
    }
}
public class CatHospital {
    private Cat animal;

    public void setAnimal(Cat animal) {
        this.animal = animal;
    }

    public void checkup(){
        System.out.println("동물이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());
        animal.sound();
    }

    public Cat bigger (Cat target) {
        return animal.getSize() > target.getSize() ? animal : target;
    }
}
public class AnimalHospitalMainV0 {
    public static void main(String[] args) {
        DogHospital dogHospital = new DogHospital();
        CatHospital catHospital = new CatHospital();

        Dog dog = new Dog("멍멍이1", 100);
        Cat cat = new Cat("고양이1", 300);

        dogHospital.setAnimal(dog);
        dogHospital.checkup();

        catHospital.setAnimal(cat);
        catHospital.checkup();

        //타입에 대한 결정을 나중으로 미루면 -> generic을 사용하는 이유

        //문제1 : 개 병원에 고양이 전달
        //dogHospital.setAnimal(cat); 다른 타입 입력 : 컴파일 오류

        //문제2 : 개 타입 반환
        dogHospital.setAnimal(dog);
        Dog biggerDog = dogHospital.bigger(new Dog("멍멍이2", 200));
        System.out.println("biggerDog = " + biggerDog);
    }
}

타입 매개변수 제한 2 - 다형성 시도

public class AnimalHospitalV1 {

    private Animal animal;

    public void setAnimal(Animal animal) {
        this.animal = animal;
    }

    public void checkup(){
        System.out.println("동물 이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());
        animal.sound();
    }

    public Animal getBigger(Animal target){
        return animal.getSize() > target.getSize() ? animal : target;
    }

    //메서드들이 다 animal에서 제공하는 메서드
}
public class AnimalHospitalMainV1 {
    public static void main(String[] args) {
        AnimalHospitalV1 dogHospital = new AnimalHospitalV1();
        AnimalHospitalV1 catHospital = new AnimalHospitalV1();

        Dog dog = new Dog("멍멍이1", 100);
        Cat cat = new Cat("고양이1", 300);

        dogHospital.setAnimal(dog);
        dogHospital.checkup();

        catHospital.setAnimal(cat);
        catHospital.checkup();

        //타입에 대한 결정을 나중으로 미루면 -> generic을 사용하는 이유

        //문제1 : 개 병원에 고양이 전달
        dogHospital.setAnimal(cat); //매개변수 체크 실패 : 컴파일 오류

        //문제2 : 개 타입 반환
        dogHospital.setAnimal(dog);
        //dogHospital.setAnimal(cat); -> castingException 발생

        //반환하는 타입이 Animal, 강제로 downCasting
        Dog biggerDog = (Dog) dogHospital.getBigger(new Dog("멍멍이2", 200));
        System.out.println("biggerDog = " + biggerDog);
    }
}

제네릭 도입과 실패

public class AnimalHospitalV2<T> {

    private T animal;

    public void setAnimal(T animal) {
        this.animal = animal;
    }

    public void checkup(){
        //T의 타입을 메서드를 정의하는 시점에는 알 수 없다. Object의 기능만 사용 가능(toString, equals 등등)
//        System.out.println("동물 이름 : " + animal.getName());
//        System.out.println("동물 크기 : " + animal.getSize());
//        animal.sound();

        animal.toString();
        animal.equals(null);
    }

    public Animal getBigger(Animal target){
       // return animal.getSize() > target.getSize() ? animal : target;
        return null;
    }

    //메서드들이 다 animal에서 제공하는 메서드
}

T에는 어떤 값이 들어올지 모른다. Animal에 대한 타입인자가 들어오길 기대했지만, Animal이라는 코드는 없음, 따라서 Object 타입으로 가정, Object가 제공하는 메서드만 호출 가능
Animal에 대한 기능은 사용이 불가

public class AnimalHospitalMainV2 {
    public static void main(String[] args) {
        AnimalHospitalV2<Dog> dogHospital = new AnimalHospitalV2();
        AnimalHospitalV2<Cat> catHospital = new AnimalHospitalV2();
        AnimalHospitalV2<Integer> integerHospital = new AnimalHospitalV2<>();
        AnimalHospitalV2<Object> objectHospital = new AnimalHospitalV2<>();

    }
}

타입 변수 제한

public class AnimalHospitalV3<T extends Animal> {

    private T animal;

    public void setAnimal(T animal) {
        this.animal = animal;
    }

    public void checkup() {

        System.out.println("동물 이름 : " + animal.getName());
        System.out.println("동물 크기 : " + animal.getSize());
        animal.sound();
    }

    public T getBigger(T target) {
        return animal.getSize() > target.getSize() ? animal : target;
    }

    //메서드들이 다 animal에서 제공하는 메서드
}
public class AnimalHospitalMainV3 {
    public static void main(String[] args) {
        AnimalHospitalV3<Dog> dogHospital = new AnimalHospitalV3();
        AnimalHospitalV3<Cat> catHospital = new AnimalHospitalV3();

        Dog dog = new Dog("멍멍이1", 100);
        Cat cat = new Cat("고양이1", 300);

        dogHospital.setAnimal(dog);
        //dogHospital.setAnimal(cat); 다른 타입 입력시 compileError
        dogHospital.checkup();

        catHospital.setAnimal(cat);
        catHospital.checkup();

        dogHospital.setAnimal(dog);
        Dog biggerDog = dogHospital.getBigger(new Dog("멍멍2", 200));
    }
}

<T extends Animal> T를 Animal과 그 자식만 받을 수 있도록 제한

제네릭 메서드

public class GenericMethod {
    public static Object objMethod(Object obj){
        System.out.println("object print: " + obj);
        return obj;
    }

    //특정 타입을 받아서 그대로 사용, 메서드 안에서만 사용하는 한정적
    public static <T> T genericMethod(T t) {
        System.out.println("generic print : " + t);
        return t;
    }

    //특정 타입을 지정, 상한 범위 지정
    public static <T extends Number> T numberMethod(T t){
        System.out.println("bound print : " + t);
        return t;
    }
}
public class MethodMain1 {
    public static void main(String[] args) {
        Integer i = 10;
        Object object = GenericMethod.objMethod(i);

        System.out.println("명시적 타입 인자 전달");
        Integer rst = GenericMethod.<Integer>genericMethod(i);

        //호출 할때 타입 메서드 지정
        Integer integerVal = GenericMethod.<Integer>numberMethod(10);
        Double doubleVal = GenericMethod.<Double>numberMethod(20.0);

        System.out.println("타입 추론");
        Integer rst1 = GenericMethod.genericMethod(i);
        Integer integerVal1 = GenericMethod.numberMethod(10);
        Double doubleVal1 = GenericMethod.numberMethod(20.0);
    }
}

제네릭 타입
GenericClass<T> 객체를 전달하는 시점에 타입 인자 전달

제네릭 메서드
<T> T genericMethod(T t) 메서드를 호출하는 시점
제네릭 메서드는 클래스 전체가 아니라 특정 메서드 단위로 제네릭을 도입

public static <T> T genericMethod(T t)

인스턴스 메서드 static

class Box<T> { //제네릭 타입
static <V> V staticMethod2(V t) {} //static 메서드에 제네릭 메서드 도입
<Z> Z instanceMethod2(Z z) {} //인스턴스 메서드에 제네릭 메서드 도입 가능
}

타입 매개변수 제한

 public static <T extends Number> T numberMethod(T t){

Number와 하위 자식들만 받을 수 있다.
하지만 계속 하위 자식들의 값을 호출하는 것은 매우 귀찮음..
그래서 타입추론 기능을 통해 가능

제네릭 메서드 활용

public class AnimalMethod {

    //제네릭을 특정 타입으로 지정
    public static <T extends Animal> void checkup(T t){
        System.out.println("동물 이름: " + t.getName());
        System.out.println("동물 크기: " + t.getSize());
        t.sound();
    }

    //제네릭을 특정 타입으로 지정
    public static <T extends Animal> T getBigger(T t1, T t2) {
        return t1.getSize() > t2.getSize() ? t1 : t2;
    }
}
public class MethodMain {
    public static void main(String[] args) {
        Dog dog = new Dog("멍멍이", 100);
        Cat cat = new Cat("고양이", 100);

        AnimalMethod.checkup(dog);
        AnimalMethod.checkup(cat);

        Dog biggerDog = new Dog("큰 멍멍이", 200);

        //반환 타입 지정 됨
        Dog bigger = AnimalMethod.getBigger(dog, biggerDog);
        System.out.println("bigger = " + bigger);
    }
}

제네릭 메서드를 호출할 때 타입 추론을 사용

제네릭 타입과 제네릭 메서드의 우선순위

//제네릭 타입을 생성
public class ComplexBox<T extends Animal>{

    private T animal;

    public void setAnimal(T animal) {
        this.animal = animal;
    }

    public <Z> Z printAnimalReturn(Z z){
        System.out.println("animal.className: " + animal.getClass().getName());
        System.out.println("t.className: " + z.getClass().getName());
        return z;
    }
}

정적 메서드는 제네릭 메서드만 적용할 수 있지만, 인스턴스 메서드는 제네릭 타입도 제네릭 메서드도 둘다 적용

public class MethodMain2 {
    public static void main(String[] args) {
        Dog dog = new Dog("멍멍이", 100);
        Cat cat = new Cat("고양이", 50);

        ComplexBox<Dog> hospital = new ComplexBox<>();
        hospital.setAnimal(dog);

        Cat returnCat = hospital.printAnimalReturn(cat);
        System.out.println("returnCat = " + returnCat);
    }
}

<T> T printAndReturn(T t)
제네릭 타입보다 제넭 메서드가 높은 우선 순위를 갖는다.
제네릭 메서드 타입 매개변수 T에는 상한이 없음 -> Object로 취급

와일드카드 1

와일드카드라는 뜻은 컴퓨터 프로그래밍에서 * , ? 와 같이 하나 이상의 문자들을 상징하는 특수 문자

public class Box<T> {

    private T value;

    public T getValue() {
        return value;
    }

    public void setValue(T value) {
        this.value = value;
    }
}
public class WildcardEx {

    //Box T 라는 값을 받는다.
    static <T> void printGenericV1(Box<T> box) {
        System.out.println("T = " + box.getValue());
    }

    //Box에 상한을 건다.
    static <T extends Animal> void printGenericV2(Box<T> box) {
        T t = box.getValue();
        System.out.println("이름 = " + t.getName());
    }

    //T 타입을 반환
    static <T extends Animal> T printAndReturnGeneric(Box<T> box) {
        T t = box.getValue();
        System.out.println("이름 : " + t.getName());
        return t;
    }


    //? -> 아무거나 다 들어 올 수 있다는 의미
    static void printWildcardV1(Box<?> box) {
        System.out.println("? = " + box.getValue());
    }

    //와일드 카드에 뭐가 들어올지 제한 가능
    static void printWildcardV2(Box<? extends Animal> box){
        Animal animal = box.getValue();
        System.out.println("이름 : " + animal.getName());
    }

    static Animal printAndReturnWildcard(Box<? extends Animal> box) {
        Animal animal = box.getValue();
        System.out.println("이름 = " + animal.getName());
        return animal;
    }
}

와일드 카드는 ? 로 설정

public class WildcardMain2 {
    public static void main(String[] args) {
        Box<Object> objectBox = new Box<>();
        Box<Animal> animalBox = new Box<>();
        Box<Dog> dogBox = new Box<>();
        Box<Cat> catBox = new Box<>();
        dogBox.setValue(new Dog("멍멍이", 100));

        //Animal 포함 상위 타입 전달 가능
        writeBox(objectBox);
        writeBox(animalBox);

        //하한
//        writeBox(dogBox);
//        writeBox(catBox);

        Animal animal = animalBox.getValue();
        System.out.println("animal = " + animal);
    }

    //super를 사용했기에, Animal 보다 상위 타입이 ?에 들어와야 한다.
    static void writeBox(Box<? super Animal> box) {
        box.setValue(new Dog("멍멍이", 100));
    }
}

와일드카드는 제네릭 타입이나, 제네릭 메서드를 선언하는 것이 아니다.
와일드카드는 이미 만들어진 제네릭 타입을 활용할 때 사용

비제한 와일드카드

//이것은 제네릭 메서드이다.
//Box<Dog> dogBox를 전달한다. 타입 추론에 의해 타입 T가 Dog가 된다.
static <T> void printGenericV1(Box<T> box) {
System.out.println("T = " + box.get());
}
//이것은 제네릭 메서드가 아니다. 일반적인 메서드이다.
//Box<Dog> dogBox를 전달한다. 와일드카드 ?는 모든 타입을 받을 수 있다.
static void printWildcardV1(Box<?> box) {
System.out.println("? = " + box.get());
}

와일드카드인 ? 는 모든 타입을 다 받을 수 있다는 뜻.

제네릭 메서드 vs 와일드카드
제네릭 메서드

//1. 전달
printGenericV1(dogBox)
//2. 제네릭 타입 결정 dogBox는 Box<Dog> 타입, 타입 추론 -> T의 타입은 Dog
static <T> void printGenericV1(Box<T> box) {
System.out.println("T = " + box.get());
}
//3. 타입 인자 결정
static <Dog> void printGenericV1(Box<Dog> box) {
System.out.println("T = " + box.get());
}
//4. 최종 실행 메서드
static void printGenericV1(Box<Dog> box) {
System.out.println("T = " + box.get());

와일드카드

//1. 전달
printWildcardV1(dogBox)
//이것은 제네릭 메서드가 아니다. 일반적인 메서드이다.
//2. 최종 실행 메서드, 와일드카드 ?는 모든 타입을 받을 수 있다.
static void printWildcardV1(Box<?> box) {
System.out.println("? = " + box.get());
}

제네릭메서드 Vs 와일드카드
제네릭메서드는 특정 시점에 타입 매개변수에 타입 인자를 전달해 타입을 결정
와일드카드는 일반적 메서드에 사용, 단순히 매개변수로 제네릭 타입을 받ㅇ르 수 있는 것 뿐

와일드카드 2

상한 와일드카드

static <T extends Animal> void printGenericV2(Box<T> box) {
T t = box.get();
System.out.println("이름 = " + t.getName());
}
static void printWildcardV2(Box<? extends Animal> box) {
Animal animal = box.get();
System.out.println("이름 = " + animal.getName());
}

? extends Animal를 지정

타입 매개변수가 꼭 필요한 이유
와일드카드는 제네릭을 정의할 때 사용하는 것이 아니다. Box<Dog> , Box<Cat> 처럼 타입 인자가 전달된 제네릭 타입을 활용할 때 사용.

메서드의 타입들을 특정 시점에 변경하려면 제네릭 타입이나, 제네릭 메서드를 사용
와일드카드는 이미 만들어진 제네릭 타입을 전달 받아서 활용할 때 사용

하한 와일드카드

public class WildcardMain2 {
    public static void main(String[] args) {
        Box<Object> objectBox = new Box<>();
        Box<Animal> animalBox = new Box<>();
        Box<Dog> dogBox = new Box<>();
        Box<Cat> catBox = new Box<>();
        dogBox.setValue(new Dog("멍멍이", 100));

        //Animal 포함 상위 타입 전달 가능
        writeBox(objectBox);
        writeBox(animalBox);

        //하한
//        writeBox(dogBox);
//        writeBox(catBox);

        Animal animal = animalBox.getValue();
        System.out.println("animal = " + animal);
    }

    //super를 사용했기에, Animal 보다 상위 타입이 ?에 들어와야 한다.
    static void writeBox(Box<? super Animal> box) {
        box.setValue(new Dog("멍멍이", 100));
    }
}

Box<? super Animal> box
하한을 Animal로 제한했기 때문에 Animal 타입의 하위 타입인 Box<Dog> 는 전달할 수 없다.

타입 이레이저

제네릭은 자바 컴파일 단계에서만 사용, 컴파일 이후에는 제네릭 정보가 삭제. 제네릭 사용한 타입 매개변수가 모두 사라지는 것은 아님. 컴파일 전인 .java 에는 제네릭의 타입 매개변수가 존재, 컴파일 이후인 자바 바이트코드 .class 에는 타입 매개변수가 존재하지 않는 것

제네릭 타입 선언

public class GenericBox<T> {
private T value;
public void set(T value) {
this.value = value;
}
public T get() {
return value;
}
}

제네릭 타입에 Integer 타입 전달

void main() {
GenericBox<Integer> box = new GenericBox<Integer>();
box.set(10);
Integer result = box.get();
}

자바 컴파일러는 컴파일 시점에 타입 매개변수와 타입 인자를 포함한 제네릭 정보를 활용해 new GenericBox<Integer>()

public class GenericBox<Integer> {
private Integer value;
public void set(Integer value) {
this.value = value;
}
public Integer get() {
return value;
}
}

컴파일이 끝나면 자바는 제네릭과 관련된 정보를 삭제

컴파일 후

public class GenericBox {
private Object value;
public void set(Object value) {
this.value = value;
}
public Object get() {
return value;
}
}
void main() {
GenericBox box = new GenericBox();
box.set(10);
Integer result = (Integer) box.get(); //컴파일러가 캐스팅 추가
}

한계
컴파일 후 제네릭 타입 정보가 존재하지 않는다. .class 자바를 실행하는 런타임에는 지정한 타입 정보가 모두 제거

profile
개발자를 향해 가는 중입니다~! 항상 겸손

0개의 댓글