Generic

2023. 12. 4. 00:49java/java

1] 왜 제네릭을 사용해야 할까?

 

Java 5부터 제네릭 타입이 새로 추가되었다. 제네릭 타입을 이용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있게 되었다. 제네릭은 컬렉션, 람다식, 스트림, NIO에서 널리 사용되므로 확실히 애해해 두어야 한다.

 

제네릭을 사용하는 코드는 비제네릭 코드에 비해 다음과 같은 이점을 가지고 있다.

 

컴파일 시 강한 타입 체크를 할 수 있다.

 자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해 제네릭 코드에 대해 강한 타입 체크를 해 컴파일 타임에 에러를 체크한다.

 

타입 변환을 제거한다.

비제네릭 코드는 불필요한 타입 변환을 하기 때문에 프로그램 성능에 악영향을 미친다.

    public static void main(String[] args) {
        List list = new ArrayList();
        list.add("hello");
        
        String str = (String) list.get(0); //타입 변환
    }

 

 제네릭 코드를 사용하면 타입 변환을 할 필요가 없어 프로그램 성능이 향상된다.

public static void main(String[] args) {
    List<String> list = new ArrayList<String>();
    list.add("hello");

    String str = list.get(0);
}

 

 

2] 멀티 타입 파라미터(class<K, V, ...>, interface<K, V, ...>)

 

제네릭 타입은 두 개 이상의 멀티 타입 파라미터를 사용할 수 있는데, 이 경우 각 타입 파라미터를 콤마로 구분한다.

ex)

public class Product <T, M> {
    private T kind;
    private M model;

    public T getKind() {
        return kind;
    }

    public void setKind(T kind) {
        this.kind = kind;
    }

    public M getModel() {
        return model;
    }

    public void setModel(M model) {
        this.model = model;
    }
}

===========================================================

public class ProductExample {

    public static void main(String[] args) {
        Product<Radio, String> product1 = new Product<>();
        product1.setKind(new Radio());
        product1.setModel("오성 라디오");

        Radio radio = product1.getKind();
        String model = product1.getModel();
    }
}

 

 자바 7부터  제네릭 타입 파마리터의 중복 기술을 줄이기 위해 다이아몬드 연산자를 제공한다. 자바  컴파일러는 타입을 유추해 자동으로 설정해준다.

 

 

3] 제네릭 메소드(<T, R> R method(T t))

 

제네릭 메소드는 매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드를 말한다. 제네릭 메소드를 선언하는 방법은 리턴 타입 앞에 <> 기호를 추가하고 타입 파라미터를 기술한 다음, 리턴 타입과 매개 타입으로 타입 파라미터를 사용하면 된다.

 

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

 

제네릭 메소드는 두 가지 방식으로 호출할 수 있다. 코드에서 타입 파라미터의 구체적인 타입을 명시적으로 지정해도 되고, 컴파일러가 매개값의 타입을 보고 구체적인 타입을 추정하도록 할 수 있다.

public <T> Box<T> boxing(T t) {...}

=================================================

Box<Integer> box = <Integer>boxing(100);
Box<Integer> box = boxing(100);

 

 

 

4] 제한된 타입 파라미터(<T extends 최상위 타입>)

타입 파라미터에 지정되는 구체적인 타입을 제한할 필요가 종종 있다. 예를 들어 숫자를 연산하는 제네릭 메소드는 매개값으로 Number 타입 또는 하위 클래스타입의 인스턴스만 가져야 한다. 이것이 제한된 타입 파라미터가 필요한 이유이다.

 

제한된 타입 파라미터를 선언하려면 타입 파라미터 뒤에 extends 키워드를 붙이고 상위 타입을 명시하면 된다. 상위 타입은 클래스뿐만 아니라 인터페이스도 가능하다.

 

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

 

타입 파라미터에 지정되는 구체적인 타입은 상위 타입이거나 상위 타입의 하위 도는 구현 클래스만 가능하다. 주의할 점은 메소드의 중괄호 {} 안에서 타입 파라미터 변수로 사용 가능한 것은 상위 타입의 멤버(필드, 메소드)로 제한된다.

 

public class Util {

    public static <T extends Number> int compare(T t1, T t2) {
        double v1 = t1.doubleValue();
        double v2 = t2.doubleValue();
        
        return Double.compare(v1, v2);
    }
}

==================================================================

public class BoundedTypeParameterExample {

    public static void main(String[] args) {
        //String str = Util.compare("a", "b"); => 컴파일 에러

        int result = Util.compare(10, 20);
        System.out.println(result);
    }
}

 

 

 

5] 와일드카드 타입(<?>, <? extends ...>, <? super ...>)

코드에서 ?를 일반적으로 와일드카드라고 부른다. 제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드 카드를 다음과 같이 세 가지 형태로 사용한다.

 

 - 제네릭타입<?> : 제한없음

=> 타입 파라미터를 대치하는 구체적인 타입으로 모든 클래스나 인터페이스 타입이 올 수 있다.

 

 - 제네릭타입<? extends 상위타입> : 상위 클래스 제한

=> 타입 파라미터를 대치하는 구체적인 타입으로 상위 타입이나 하위 타입만 올 수 있다.

 

 - 제네릭타입<? super 하위타입> : 하위 클래스 제한

=> 타입 파라미터를 대치하는 구체적인 타입으로 하위 타입이나 상위 타입이 올 수 있다.

 

public class Course <T>{
    private String name;
    private T[] students;

    public Course(String name, int capacity) {
        this.name = name;
        students = (T[]) (new Object[capacity]);
    }

    public String getName() {
        return name;
    }

    public T[] getStudents() {
        return students;
    }

    public void add(T t) {
        for (int i = 0; i < students.length; i++) {
            if (students[i] == null) {
                students[i] = t;
                break;
            }
        }
    }
}

 

수강생이 될 수 있는 타입은 다음 4가지 클래스라고 가정하자.

 Person의 하위 클래스로

  -Worker

  -Student

 

 Student의 하위 클래스로

  - HighStudent가 있다.

 

1. Course<?> 수강생은 모든 타입이 될 수 있다.

2. Course<? extends Student> 수강생은 Student와 HighStudent만 될 수 있다.

3. Course<? super Worker> 수강생은 Worker와 Person만 될 수 있다.

 

 

6] 제네릭 타입의 상속과 구현

제네릭 타입도 다른 타입과 마찬가지로 부모 클래스가 될 수 있다. 다음은 Product<T, M> 제네릭 타입을 상속해서 ChildProduct<T, M> 타입을 정의한다.

public class ChildProduct<T, M> extends Product<T, M> { ... }

 

자식 제네릭 타입은 추가적으로 타입 파라미터를 가질 수 있다. 

public class ChildProduct<T, M, C> extends Product<T, M> { }

 

public class Product <T, M> {
    private T kind;
    private M model;

    public T getKind() {
        return kind;
    }

    public void setKind(T kind) {
        this.kind = kind;
    }

    public M getModel() {
        return model;
    }

    public void setModel(M model) {
        this.model = model;
    }
}

===============================================

public class ChildProduct<T, M, C> extends Product<T, M>{
    private C company;

    public C getCompany() {
        return company;
    }

    public void setCompany(C company) {
        this.company = company;
    }
}