[Java] Chap13 제네릭

Seunghee Lee·2023년 3월 20일
0

Java

목록 보기
9/12

1. 제네릭이란 ?

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

✅ 코드 형태

public class 클래스명<T> {	//<T>는 단지 타입 파라미터를 쓸 것을 의미한다.
	public T 필드명;
}

예를 들어, Box 클래스에서 결정되지 않은 content(필드)의 타입을 T라는 타입 파라미터로 정의하자.

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

Box 클래스는 T가 무엇인지 모르지만, Box 객체가 생성될 시점에 다른 타입으로 대체된다는 것을 알고 있다. 만약 Box의 내용물로 String을 저장하고 싶다면 타입 파라미터를 String으로 지정하면 된다.

Box<String> box = new Box<String>();	//타입 파라미터 String으로 지정
box.content = "안녕하세요";
String content = box.content;	//강제 타입 변환 없이 바로 "안녕하세요"를 얻을 수 있다.

※ 주의할 점은 타입 파라미터를 대체하는 타입은 클래스와 인터페이스라는 것이다.

Box<String> 라고 하지 않은 이유는 기본 타입은 타입 파라미터의 대체 타입이 될 수 없기 때문이다.

변수를 선언할 때와 동일한 타입으로 호출하고 싶다면 생성자 호출 시 생성자에는 타입을 명시하지 않고 <>만 붙일 수 있다.

Box<String> box = new Box<String>();	//굳이 이렇게 안 써도 됨

Box<String> box = new Box<>();	//<>만 써도 동일한 타입으로 호출 가능

ex) 제네릭 타입 파라미터 실습

  • [ Box.java ]
public class Box<T> {
    public T content;   //타입 파라미터로 T 사용... T는 아무런 의미없이 "타입을 갖는다."의 의미만 갖고 있다.
}
  • [ GenericExample.java ]
public class GenericExample {
    public static void main(String... args) {
        //String 타입으로 생성
        Box<String> box1 = new Box<>();
        box1.content = "안녕하세요";
        String str = box1.content;
        System.out.println(str);

        //String 타입으로 생성
        Box<Integer> box2 = new Box<>();
        box2.content = 100;
        Integer value = box2.content;
        System.out.println(value);
    }
}


2. 제네릭 타입

제네릭 타입은 결정되지 않은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.

제네릭 타입은 선언부에 <>부호가 붙고 그 사이에 파라미터에 타입 파라미터들이 위치한다.

✅ 코드 형태

public class 클래스명<A, B, ...> { ... }
public interface 인터페이스명<A, B, ...> { ... }

타입 파라미터는 일반적으로 대문자 알파벳 한글자로 표현한다.

외부에서 제네릭 타입을 사용하려면 타입 파라미터에 구체적인 타입을 지정해야 한다. 만약 지정하지 않을 경우, Object 타입이 암묵적으로 사용된다.

ex) 다양한 종류와 모델 제품을 저장하는 Product 클래스를 제네릭 타입으로 선언해보자.

  • [ Product.java ]
/* 제네릭 타입 */
public class Product<K, M> {
    /* 필드 */
    private K kind;
    private M model;

    /* 메소드; Getter/Setter */
    public K getKind() { return this.kind; }
    public M getModel() { return this.model; }
    public void setKind(K kind) { this.kind=kind; }
    public void setModel(M model) { this.model=model; }
}
  • [ Tv.java ]
public class Tv {
}
  • [ Car.java ]
public class Car {
}
  • [ GenericExample.java ]
public class GenericExample {
	public static void main(String... args) {
        /* K는 Tv로, M은 String으로*/
        Product<Tv, String> product1 = new Product<>();

        product1.setKind(new Tv());
        product1.setModel("스마트 Tv");

        Tv tv = product1.getKind();
        String tvModel = product1.getModel();

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

        product2.setKind(new Car());
        product2.setModel("SUV");

        Car car  = product2.getKind();
        String carModel = product2.getModel();
    }
}

ex) Rentable 인터페이스를 제네릭 타입으로 선언해보자.

  • [ Rentable.java ]
public interface Rentable<P> {
    P rent();
}
  • [ Home.java ] - 렌트 대상 클래스
public class Home {
    public void turnOnLight() {
        System.out.println("전등을 켭니다.");
    }
}
  • [ Car.java ] - 렌트 대상 클래스
public class Car {
    public void run() {
        System.out.println("자동차가 달립니다.");
    }
}
  • [ HomeAgency.java ]- 집을 렌트해주는 대리점 클래스
public class HomeAgency implements Rentable<Home> {
    @Override
    public Home rent() {
        return new Home();
    }
}
  • [ CarAgency.java ]- 집을 렌트해주는 대리점 클래스
public class CarAgency implements Rentable<Car>{
    @Override
    public Car rent() {
        return new Car();
    }
}
  • [ GenericExample.java ] -HomAgency와 CarAgency에서 대여한 Home과 Car를 이용하는 방법을 보여준다.
public class GenericExample {
    public static void main(String... args) {
        HomeAgency homeAgency = new HomeAgency();
        Home home = homeAgency.rent();
        home.turnOnLight();

        CarAgency carAgency = new CarAgency();
        Car car = carAgency.rent();
        car.run();
    }
}


3. 제네릭 메소드

제네릭 메소드는 타입 파라미터를 가지고 있는 메소드를 말한다.

제네릭 메소드는 리턴 타입 앞에 <> 기호를 추가하고 타입 파라미터를 정의한 뒤, 리턴 타입과 매개변수 타입에서 사용한다.

✅ 코드 형태

public <타입 파라미터, ...> 리턴타입 메소드명(매개변수, ...) { ... }

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

/* T는 Integer로 대체, Box<Intgeer> 가 리턴된다. */
1) Box<Integer> box1 = boxing(100);

/* T는 String으로 대체, Box<String> 가 리턴된다. */
2) Box<String> box2 = boxing("안녕하세요")

ex) 제네릭 메소드 실습

  • [ Box.java ] - 제네릭 타입 클래스 생성
public class Box<T> {
    //필드
    private T t;

    //Getter 메소드
    public T get() {
        return t;
    }

    //Setter 메소드
    public void set(T t) {
        this.t = t;
    }
}
  • [ GenericExample.java ] - 제네릭 메소드를 선언하고 호출한다.
public class GenericExample {

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

    public static void main(String... args) {
        Box<Integer> box1 = boxing(100);			//T를 Integer로 대체
        int intValue = box1.get();
        System.out.println(intValue);

        Box<String> box2 = boxing("고길동 아저씨");	//T를 String으로 대체
        String strValue = box2.get();
        System.out.println(strValue);
    }
}


4. 제한된 타입 파라미터

⚠️ 경우에 따라 타입 파라미터를 대체하는 구체적인 타입을 제한할 필요가 있다 !

모든 타입으로 대체할 수 없고, 특정 타입과 자식 or 구현 관계에 있는 타입만 대체할 수 있는 타입 파라미터를 제한된 타입 파라미터(bounded type parameter)라고 한다.

✅ 코드 형태

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

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

ex) 제한된 타입 파라미터 실습

  • [ GenericExample.java ]
public class GenericExample {
	//제한된 타입 파라미터를 갖는 제네릭 메서드
    public static <T extends Number> boolean compare(T t1, T t2) {	//타입 파라미터 T를 대체할 타입을 Number로 제한
        System.out.println("compare(" + t1.getClass().getSimpleName() + "," +
                t2.getClass().getSimpleName() + ")");
        
        //Number 메소드 사용
        double v1 = t1.doubleValue();	//Number타입의 doubleValue() 메소드 호출
        double v2 = t2.doubleValue();	//Number타입의 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);
    }
}


5. 와일드카드 타입 파라미터

제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 타입 파라미터로 ?(와일드 카드)를 사용할 수 있다.

?는 범위에 있는 모든 타입으로 대체할 수 있다는 표시이다.

  • 타입 파라미터의 대체 타입으로 부모 클래스와 자식 클래스만 가능하도록 매개변수를 선언할 수 있다.
    ✅ 코드 형태
리턴타입 메소드명(제네릭타입<? extends 부모> 변수) { ... }
  • 반대로 부모 클래스와 특정 자식 클래스만 가능하도록 매개변수를 선언할 수 있다.
    ✅ 코드 형태
리턴타입 메소드명(제네릭타입<? super 특정자식> 변수) { ... }
  • 어떤 타입이든 가능하도록 매개변수를 선언할 수 있다.
리턴타입 메소드명(제네릭타입<?> 변수) { ... }
profile
자라나라 개발개발 ~..₩

0개의 댓글