기초단계/JAVA

2023.02.21-1 Java복습

춘핑이 2023. 2. 21. 18:16

13 제네릭

13.1 제네릭이란

Box클래스 선언하려고한다. Box에 넣을 내용물로 content필드를 선언하려고 할대 타입을 무엇으로 해야할까?
Box는 다양한 내용물을 저장해야하므로 특정클래스 타입으로 선언할 수 없다.
그래서 Object타입으로 선언한다.
public Object content

Object타입은 모든 클래스의 최상위 부모 클래스이다. 그래서 모든 객체는 부모타입인 Object로 자동타입 변환이 되므로 어떤 객체든 대입이 가능하다.

문제는 Box안의 내용물을 얻을때마다 content는 Object타입이므로 어떤 객체가 대입되어있는지 확실하지 않다.
이때 대입된 내용물의 타입을 안다면 꺼낼때마다 강제 타입변환을 거쳐 얻을 수 있다.

어떤 내용물이 저장되어 있느닞 모른다면 instanceof 연산자로 타입을 조사할 수 는 있지만
모든 종류의 클래스를 대상으로 조사할수는 없다.
따라서 Object타입으로 content필드를 선언하는 것은 좋은 방법이 아니다.

Box를 생성하기 전 우리는 어떤 내용물을 이미 알고있다.
따라서 Box를 생성할때 저장할 내용물의 타입을 미리 알려주면 Box는 contnet에 무엇이 대입되고
읽을때 어떤 타입으로 제공할지를 알게 된다. 이것이 제네릭이다.

즉, 제네릭이란 결정되지 않은 타입을 파라미터로 처리하고 실제 사용할때 파라미터를 구체적인 타입으로 대체시키는 기능이다.

public class Box<T> {
    public T content;
}

<T>는 T가 타입파라미터임을 뜻하는 기호로 타입이 필요한 자리에 T를 사용할 수 잇음을 알려주는 역할을 한다.
여기서 Box클래스는 T를 content필드타입으로 사용하였다.
즉 박스클래스는 T가 무엇인지를 모르지만 박스객체가 생성될 시점에 다른 타입으로 대체된다는 것을 알고있다.

Box<포장객체> box = new Box<>();로 생성할 수 있다.

public class Box<T> {
    public T content;
}

public class GenericExample {
    public static void main(String[] args) {
        // Box<String> box1 = new Box<String>();
        Box<String> box1 = new Box<>();
        box1.content = "안녕하세요";
        String str = box1.content;
        System.out.println(str); //안녕하세요

        Box<Integer> box2 = new Box<>();
        box2.content = 100;
        int value = box2.content;
        System.out.println(value); //100
    }
}

13.2 제네릭타입

제네릭 타입은 결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
제네릭타입은 선언부에 <>부호가 붙고 그 사이에 타입파라미터들이 위치한다.

타입파라미터는 변수명과 동일한 규칙에 따라 작성할 수 있지만 일반적으로 대문자 알파벳 한글자로 표현한다.
외부에서 제네릭 타입을 사용하려면 타입파라미터에 구체적인 타입을 지정해야한다.
만약 지정하지 않으면 Object타입이 암묵적으로 사용된다.

public class Product<K, M> {
    //필드
    private K kind;
    private M model;

    //메소드
    public K getKind() {return kind;}
    public M getModel() {return model;}
    public void setKind(K kind) {this.kind = kind;}
    public void setModel(M model) {this.model = model;}
}

public class GenericExample {
    public static void main(String[] args) {
        //K는 Tv로 대체 M은 String으로 대체
        Product<Tv, String> product1 = new Product<>();

        //Setter 매개값은 반드시 Tv와 String을 제공
        product1.setKind(new Tv());
        product1.setModel("스마트 Tv");

        //Getter 리턴값은 Tv와 String
        Tv tv = product1.getKind();
        String tvModel = product1.getModel();

        //-----------------------------------------

        //K는 Car로 대체 M은 String으로 대체
        Product<Car, String> product2 = new Product<>();

        //Setter 매개값은 반드시 Car와 String을 제공
        product2.setKind(new Car());
        product2.setModel("SUV자동차");

        //Getter 리턴값은 Car와 String
        Car car = product2.getKind();
        String carModel = product2.getModel();
    }
}

인터페이스를 제네릭타입으로 선언해보자 다양한 대상을 렌트하기 위해 rent()메소드의 리턴타입을 타입파라미터로 선언한다.

public interface Rentable<P> {
    P rent();
}

public class Car {
    public void run() {
        System.out.println("자동차가 달립니다.");
    }
}

public class Home {
    public void turnOnLight() {
        System.out.println("전등을 켭니다.");
    }
}

public class HomeAgency implements Rentable<Home> {
    @Override
    public Home rent() {
        return new Home();
    }
}

public class CarAgency implements Rentable<Car> {
    @Override
    public Car rent() {
        return new Car();
    }
}

public class GenericExample {
    public static void main(String[] args) {
        HomeAgency homeAgency = new HomeAgency();
        Home home = homeAgency.rent(); //Home객체를 리턴하는 메소드
        home.turnOnLight(); //전등을 켭니다.

        CarAgency carAgency = new CarAgency();
        Car car = carAgency.rent();
        car.run(); //자동차가 달립니다.
    }
}

Rentable은 rent()라는 추상메소드를 가지고 있다.
CarAgency HomeAgency는 각각 rent()의 타입으로 객체인 Car와 Home을 반환타입으로 사용한다.
오버라이딩된 메소드를 이용하면 각각의 객체를 반환한다.

Home home = homeAgency.rent();하게되면 새로운 객체를 반환하기 때문에
Home home = new Home(); 한것이다.
그래서 Home객체의 turnOnLight()메소드를 사용할 수 있게 되는 것이다.

13.3 제네릭 메소드

제네릭 메소드는 타입 파라미터를 가지고 있는 메소드를 말한다.
타입파라미터가 메소드 선언부에 정의된다는 점에서 제네릭 타입과 차이가 있다.
제네릭 메소드는 리턴타입 앞에 <>기호를 추가하고 타입파라미터를 정의한 뒤 리턴타입과 매개변수 타입에서 사용한다.

다음은 T를 내용물로 갖는 Box객체이다.
public <T> Box<T> boxing(T t) {....}
<T>는 제네릭이 들어간다는 표시 Box<T>이 리턴타입 boxing(T t)매개변수에 제네릭이 들어간다

타입파라미터 T는 매개값이 어떤 타입이냐에 따라 컴파일 과정에서 구체적인 타입으로 대체된다.

public class Box<T> {
    //필드
    private T t;

    //Getter메소드 타입이 제네릭
    public T get() {
        return t;
    }

    //Setter메소드 매개변수가 제네릭
    public void set(T t) {
        this.t = t;
    }
}

public class GenericExample {
    //제네릭 메소드
    public static <T> Box<T> boxing(T t){
        Box<T> box = new Box<>();
        box.set(t);
        return box;
    }

    public static void main(String[] args) {
        //제네릭 메소드 호출
        //Box객체를 만들고 set하는 메소드
        Box<Integer> box1 = boxing(100);
        int intValue = box1.get();
        System.out.println(intValue); //100

        //제네릭 메소드 호출
        Box<String> box2 = boxing("홍길동");
        String strValue = box2.get();
        System.out.println(strValue); //홍길동
    }
}

13.4 제한된 타입 파라미터

경우에 따라서 타입파라미터를 대체하는 구체적인 타입을 제한할 필요가 있다.
숫자를 연산하는 제네릭 메소드는 대체 타입으로 Number또는 자식 클래스(Byte Short Integer Long Double)로 제한할 필요가 있다.
이처럼 모든 타입으로 대체할 수없고 특정 타입과 자식또는 구현관계에 있는 타입만 대체할수 있는 타입파라미터를 제한된 타입파라미터라고 한다.

public <T extends 상위타입> 리턴타입 메소드(매개변수) {...}

상위타입은 클래스뿐만 아니라 인터페이스도 가능하다. 인터페이스 라고 해서 implements를 사용하지는 않는다.

public class GenericExample {
    public static <T extends Number> boolean compare(T t1, T t2) {
        // T타입을 출력
        System.out.println("compare(" + t1.getClass().getSimpleName() + ", " + t2.getClass().getSimpleName() + ")");

        // Number의 메소드 사용
        double v1 = t1.doubleValue();
        double v2 = t2.doubleValue();

        return (v1 == v2);
    }

    public static void main(String[] args) {
        //제네릭 메소드 호출
        boolean result1 = compare(10, 20);
        System.out.println(result1);
        System.out.println();

        //제네릭 메소드 호출
        boolean result2 = compare(4.5, 4.5);
        System.out.println(result2);
    }
}

13.5 와일드 카드 타입 파라미터

!!!!중요!!!!
제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터로 ?(와일드카드)를 사용할 수 있다.
?는 범위에 있는 모든 타입으로 대체할 수 있다는 표시이다.

타입파라미터은 대체타입으로
1.자식클래스만 가능하도록 매개변수를 다음과 같이 선언할 수 있다.
리턴타입 메소드명(제네릭타입<? extends 부모클래스> 변수) {...}

2.부모 클래스만 가능하도록
리턴타입 메소드명(제네릭타입<? super 자식클래스> 변수) {...

3.어떤 타입이든 가능하도록
리턴타입 메소드명(제네릭타입<?> 변수) {...}