제네릭(Generic)

김남건·2021년 7월 24일
0
post-thumbnail

제네릭(Generic)

쉽게 말하자면 데이터 타입을 일반화하는 것이다.
클래스나 메서드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정하는 것이다.

장점

  1. 클래스, 메서드 내부에서 사용되는 객체의 타입 안정성을 높일 수 있다.
  2. 반환값의 타입 변환 및 검사에 들어가는 노력을 줄일 수 있다.

JDK 1.5 이전의 경우 여러 타입을 사용하는 클래스나 메서드에서 매개변수나 반환값으로 Object를 사용했다. 이 경우 Object 타입을 다시 원하는 타입으로 변환해야 하며, 그 과정에서 오류가 날 가능성이 있었다.

JDK 1.5부터는 제네릭을 사용하면서 컴파일 시에 미리 타입이 정해지므로, 타입 변환 같은 번거로운 작업을 생략할 수 있다.

선언

class MyArray<T> {
    T element;
    void setElement(T element) { this.element = element; }
    T getElement() { return element; }
}

타입 변수

위에서 사용된 T를 말하며, 임의의 참조형 타입을 의미한다. 어떠한 문자를 사용하여도 상관없으며, 타입 변수 여러 개를 사용하는 경우 쉼표로 구분하여 명시한다. 메서드의 매개변수나 반환값으로도 사용 가능하다.

변수 선언, 인스턴스 생성

변수를 선언하거나 인스턴스 생성 시 타입 변수 자리에는 실제 타입을 명시해야 한다. 아래와 같이 실제 타입을 명시하면, 내부적으로 타입 변수가 실제 타입으로 처리된다.

MyArray<Integer> myArr = new MyArray<Integer>();

Java SE 7부터는 인스턴스 생성 시 타입 추정이 가능한 경우 생략 가능하다.

MyArray<Integer> myArr = new MyArray<>();

타입 변수와 배열

바로 T의 배열을 생성하지 않고 Object 배열을 생성 후 T[]로 형변환을 해야 한다. 선언부의 경우 T가 결정되기 전이기 때문에 배열을 생성할 수가 없는 것이다.

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

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

타입 변수의 다형성

타입 변수도 다형성을 가져서, 부모 클래스가 타입 변수로 사용된 경우 자식 클래스의 인스턴스가 참조될 수 있다.

import java.util.*;

class LandAnimal {
    public void crying() { System.out.println("육지동물");}
}
class Cat extends LandAnimal {
    public void crying() { System.out.println("냐옹냐옹"); }
}
class Dog extends LandAnimal {
    public void crying() { System.out.println("멍멍"); }
}
class Sparrow { public void crying() { System.out.println("짹짹"); } }

class AnimalList<T> {
    ArrayList<T> al = new ArrayList<T>();

    void add(T animal) { al.add(animal); }
    T get(int index) { return al.get(index); }
    boolean remove(T animal) { return al.remove(animal); }
    int size() { return al.size(); }
} 

public class Generic01 {
    public static void main(String[] args) {
        AnimalList<LandAnimal> landAnimal = new AnimalList<>();

        landAnimal.add(new LandAnimal());
        landAnimal.add(new Cat());
        landAnimal.add(new Dog());
        // landAnimal.add(new Sparrow()); // 오류가 발생함.
 
        for (int i = 0; i < landAnimal.size() ; i++) {
            landAnimal.get(i).crying();
        }
    }
}

제거 시기

선언되고 사용된 제네릭 타입은 컴파일 시 컴파일러에 의해 자동으로 검사되어 타입 변환된다. 그 이후에는 모든 제네릭 타입이 제거되어 class 파일에는 제네릭 타입들은 포함되지 않게 된다. 이렇게 동작하는 이유는 제네릭을 사용하지 않는 코드와의 호환성을 위해서다.

타입 변수 제한

extends 키워드를 사용하여 특정 타입만을 사용하도록 제한할 수 있다. 타입 변수에서 특정 클래스의 기능을 사용해야 할 때 유용하다.

class AnimalList<T extends LandAnimal> { ... }
public static <T extneds Number> Student<T> changing(T t){...}

타입 변수가 인터페이스를 구현해야 하는 경우에도 implements 대신 extends를 사용한다.

interface WarmBlood { ... }

class AnimalList<T extends WarmBlood> { ... }

클래스를 상속받는 동시에 인터페이스를 구현해야 한다면, '&' 기호를 사용한다.

class AnimalList<T extends LandAnimal & WarmBlood> { ... }

제네릭 메서드

메서드의 선언부에 타입 변수를 사용한 메서드를 말한다.
타입 변수의 선언이 메서드 선언의 타입 변환 앞에 위치한다. 리턴 타입이나 매개변수의 타입을 제한하기 위해 사용한다.

public class Util {
    // 매개변수의 타입이 Person의 타입 변수에 들어가서 return 타입이 결정됨
    public static<T> Person<T> changing(T t){
        Person<T> person = new Person<>();
        person.setT(t);

        return person;
    }
}

public class PersonEx {
    public static void main(String[] args) {
        Person<Integer> p1 = Util.<Integer>changing(100);
    }
}

우변의 타입은 생략 가능하다.

Person<Integer> p1 = Util.changing(100);

제네릭 메서드의 타입 변수 제한

리턴 타입 앞에 타입 변수를 명시하는 부분에서 타입 변수를 제한할 수가 있다.

public class Compare {
    public static <T extends Number> int compare(T t1, T t2){
        double value1 = t1.doubleValue();
        double value2 = t2.doubleValue();

        return Double.compare(value1, value2);
    }
}

public class CompareEx {
    public static void main(String[] args) {
        int result = Compare.compare(100, 200);
        System.out.println(result);

        Compare.compare("홍길동", "김길동");
        // String은 Number를 상속받지 않으므로 error
    }
}

와일드카드(wild card)

이름에 제한을 두지 않는다는 것을 표현하기 위해 사용하는 기호다. 자바의 경우 물음표를 사용한다. 주로 제네릭 매개변수의 타입 변수를 제한하기 위해 사용한다.

<?>           // 타입 변수에 모든 타입을 사용할 수 있음.
<? extends T> // T 타입과 T 타입을 상속받는 자손 클래스 타입만을 사용할 수 있음.
<? super T>   // T 타입과 T 타입이 상속받은 조상 클래스 타입만을 사용할 수 있음.
import java.util.*;

class LandAnimal {
    public void crying() { System.out.println("육지동물");}
}
class Cat extends LandAnimal {
    public void crying() { System.out.println("냐옹냐옹"); }
}
class Dog extends LandAnimal {
    public void crying() { System.out.println("멍멍"); }
}
class Sparrow { public void crying() { System.out.println("짹짹"); } }

class AnimalList<T> {
    ArrayList<T> al = new ArrayList<T>();

    public static void cryingAnimal(AnimalList<? extends LandAnimal> al)
    {
        LandAnimal la = al.get(0);
        la.crying();
    }

    void add(T animal) { al.add(animal); }
    T get(int index) { return al.get(index); }
    boolean remove(T animal) { return al.remove(animal); }
    int size() { return al.size(); }
}

제네릭 타입 상속

제네릭 타입을 부모 클래스로 사용할 경우, 부모 클래스에서 사용한 타입 변수를 자식 클래스에도 반드시 사용해야 한다.

class Student<T, M> extends Person<T, M>{...}

타입 변수를 추가적으로 사용하는 것도 가능하다.

class Student<T, M, C> extends Person<T, M>{...}

제네릭 타입의 인터페이스를 구현하는 경우에도 인터페이스에 사용한 타입 변수를 클래스에서도 사용해야 한다.

class Student<T> implements Comparable<T>

Reference

http://tcpschool.com/java/java_generic_concept
http://tcpschool.com/java/java_generic_various

0개의 댓글

관련 채용 정보