제네릭

2022. 12. 9. 23:13자바

728x90

1. 제네릭: 하나의 코드를 다양한 타입의 객체에 재사용하는 객체 지향 기법

장점: 컴파일 할 때 타입을 점검-> 실행 도중 발생할 오류 사전 방지

        불필요한 타입 변환이 없음-> 프로그램 성능 향상

class Gen<T> {
    T ob; //Integer ob;

    Gen(T o) {
        ob = o; //Integer ob=88; String ob="Generics Test";
    }

    T getOb() {
        return ob;
    }

    void showType() {
        System.out.println("Type of T is " +
                ob.getClass().getName()); //오브젝트의 클래스 타입의 이름을 출력
    }
}

class GenDemo {
    public static void main(String[] args) {
        Gen<Integer> iOb;
        iOb = new Gen<Integer>(88);
        iOb.showType(); //Type of T is java.lang.Integer출력
        int v = iOb.getOb(); //오토언박싱 일어남(Integer->int), 88저장
        System.out.println("value: " + v);
        Gen<String> strOb = new Gen<String>("Generics Test");
        strOb.showType();
        String str = strOb.getOb(); 
        System.out.println("value: " + str);
    }
}

- 자주 사용되는 타입 매개변수 기호

E: 원소(Element)

K: 키 (key)

N: 숫자(Number)

T: 타입(Type)

V: 값(Value)

 

*제네릭 클래스는 각 타입 인자 별 개별 버전으로 생성x

-> 자바 컴파일러가 타입 매개변수를 타입 인자로 대체

단, 타입 인자가 같아야 호환됨

*제네릭 타입인자로 클래스 타입만 가능(기초타입 x)

*제네릭 클래스 대신 Object를 사용하면 type safety가 보장되지 않음

-> Object 참조 변수가 가리키는 것이 어떤 타입일지 컴파일 시 알 수 없음-> 런타임 에러 발생 가능

class NonGen {
    Object ob;

    NonGen(Object o) {
        ob = o;
    }

    Object getOb() {
        return ob;
    }

    void showType() {
        System.out.println("Type of ob is " +
                ob.getClass().getName());
    }
}

class NonGenDemo {
    public static void main(String[] args) {
        NonGen iOb;
        int v;
        NonGen dOb = new NonGen(1.25);
        dOb.showType();
        double dob = (Double) dOb.getOb();
        //오브젝트 타입을 Double로 타입캐스팅 해줘야 double에 대입 가능
        System.out.println("value: " + dob);
        iOb = dOb; //여기까진 별문제 없이 잘 됨
        v = (Integer) iOb.getOb(); 
        // runtime 에러 발생->Double을 Integer로 타입캐스팅 할 수 없음
    }
}

*두개 이상의 타입 매개변수 -> 콤마를 이용해 여러 타입 나열 

ex) Gen(T,V)

 

2. 타입 한정

제네릭 클래스에서 특정 변수의 타입을 한정할 때 사용

예) 리턴 값을 int,float,double등의 숫자로 한정하고 싶을때

class Stats<T> {
    T[] nums;

    Stats(T[] o) {
        nums = o;
    }

    double average() {
        double sum = 0.0;
        for (int i = 0; i < nums.length; i++)
            sum += nums[i].doubleValue(); 
            // 컴파일 오류남-> 타입이 아직 정해지지 않은 상태라 타입안정성에 위배되기 때문
        return sum / nums.length;
    }
}
class Stats<T extends Number> 
//제네릭 타입을 숫자로 한정지음(Number의 서브클래스들만 제네릭 타입으로 올수 있음)
{
    T[] nums; //숫자 타입의 배열 생성

    Stats(T[] o) {
        nums = o;
    }

    double average() // 배열들의 평균을 내고 싶은 메서드
    {
        double sum = 0.0;
        for (int i = 0; i < nums.length; i++)
            sum += nums[i].doubleValue();//이제 오류 안남
        return sum / nums.length;
    }
}
class BoundsDemo {
    public static void main(String[] args) {
        Integer[] inums = { 1, 2, 3, 4, 5 };
        Stats<Integer> iob = new Stats<Integer>(inums);
        double v = iob.average(); //Integer타입의 배열도 호환 가능
        System.out.println("iob average is " + v);
        Double[] dnums = { 1.1, 2.2, 3.3, 4.4, 5.5 };
        Stats<Double> dob = new Stats<Double>(dnums);
        double w = dob.average();
        System.out.println("dob average is " + w);
        
        //String 타입은 숫자가 아니므로 컴파일 오류 남
        // String[] strs = { "1", "2", "3", "4", "5" };
        // Stats<String> strob = new Stats<String>(strs);
        // double x = strob.average();
        // System.out.println("strob average is " + v);
    }
}

* 타입 한정자로 인터페이스도 사용 가능

예) class GEN<T extends MyClass &MyInterface>

단, class T extends MyClass implements MyInterface를 구현해야 저 타입한정자 쓸 수 있음.

 

3. 와일드카드 인자

class Stats 예제에서 타입이 다른 두 오브젝트의 평균이 같은지 구하는 메서드를 구한다고 가정해보자

 class Stats<T extends Number>
 {...
 boolean isSameAvg(Stats<T> ob) {
        if (average() == ob.average())
            return true;
        return false;
    }
 }
 
 class BoundsDemo{
 ...
 iob.isSameAvg(dob);//컴파일 에러남->iob의 타입 매개변수가 dob의 타입과 일치해야 하기 때문

타입매개변수 T가 비교하려는 오브젝트의 타입과 일치해야 가능함

 class Stats<T extends Number>
 {...
 boolean isSameAvg(Stats<?> ob) //와일드카드 인자를 사용하면 어느 오브젝트와도 가능
 {
        if (average() == ob.average())
            return true;
        return false;
    }
 }
 
 class BoundsDemo{
 ...
 iob.isSameAvg(dob);//이제 잘됨

-?를 와일드카드 인자라고 함

*와일드카드 인자도 특정 타입으로 한정 가능

<? extends superclass> //슈퍼클래스를 와일드카드의 상한으로 설정

<? super subclass> //서브클래스를 와일드카드의 하한으로 설정

class TwoD {
    int x, y;

    TwoD(int a, int b) {
        x = a;
        y = b;
    }
}

class ThreeD extends TwoD {
    int z;

    ThreeD(int a, int b, int c) {
        super(a, b);
        z = c;
    }
}

class FourD extends ThreeD {
    int t;

    FourD(int a, int b, int c, int d) {
        super(a, b, c);
        t = d;
    }
}
/* 계층적 구조 FouD < ThreeD < TwoD  */

class Coords<T extends TwoD> //타입을 TwoD로 한정
{
    T[] coords;

    Coords(T[] o) {
        coords = o;
    }
}

class BoundedWildcard {
    static void showXY(Coords<?> c)
    //Coords는 TwoD로 한정된 클래스라 ok->인자로 모든 서브클래스들 다 가능
    {
        System.out.println("X Y Coordinates:");
        for (int i = 0; i < c.coords.length; i++)
            System.out.println(c.coords[i].x + " " +
                    c.coords[i].y);
    }

    static void showXYZ(Coords<? extends ThreeD> c)
    //extends 키워드로 ThreeD 오브젝트를 상한으로 지정->인자로 ThreeD와 FourD 가능
    {
        System.out.println("X Y Z Coordinates:");
        for (int i = 0; i < c.coords.length; i++)
            System.out.println(c.coords[i].x + " " +
                    c.coords[i].y + " " + c.coords[i].z);
    }

    static void showAll(Coords<? extends FourD> c)
    //FourD 오브젝트를 상한으로 지정->인자로 FourD만 가능
    {
        System.out.println("X Y Z T Coordinates:");
        for (int i = 0; i < c.coords.length; i++)
            System.out.println(c.coords[i].x + " " +
                    c.coords[i].y + " " +
                    c.coords[i].z + " " +
                    c.coords[i].t);
    }

    public static void main(String[] args) {
        TwoD[] td = {
                new TwoD(0, 0),
                new TwoD(7, 9),
                new TwoD(18, 4),
                new TwoD(-1, -23)
        };
        Coords<TwoD> tdlocs = new Coords<TwoD>(td); //TwoD 생성
        System.out.println("Contents of tdlocs.");
        showXY(tdlocs); //  TwoD라 ok
        // showXYZ(tdlocs); // Error,  ThreeD랑 FourD만 가능
        // showAll(tdlocs); // Error,  FourD만 가능
        FourD[] fd = {
                new FourD(1, 2, 3, 4),
                new FourD(6, 8, 14, 8),
                new FourD(22, 9, 4, 9),
                new FourD(3, -2, -23, 17)
        };
        Coords<FourD> fdlocs = new Coords<FourD>(fd);
        System.out.println("Contents of fdlocs.");
        // 다 FourD 가능
        showXY(fdlocs);
        showXYZ(fdlocs);
        showAll(fdlocs);
    }
}

4. 제네릭 메서드

- 메서드의 매개변수나 리턴 값을 제네릭 매개변수로 정의할 수 있음 

- 제네릭 메서드는 일반 클래스 내부에서도 정의될 수 있음

- 제네릭 매개변수는 메서드의 리턴 타입 왼쪽에 선언

class GenMethDemo {
    // 해당 오브젝트가 배열 안에 있는지 체크하는 메서드
    // 메인함수가 static이라 메인함수에서 호출할 수 있도록 static으로 선언
    static <T extends Comparable<T>, V extends T> boolean isIn(T x, V[] y)
    /*comparable 인터페이스: 같은 타입의 인스턴스를 서로 비교하는 클래스들은 모두
    Comparable 인터페이스를 구현한 것이다.*/
    {
        for (int i = 0; i < y.length; i++)
            if (x.equals(y[i]))
                return true;
        return false;
    }

    public static void main(String[] args) {
        // Use isIn() on Integers.
        Integer[] nums = { 1, 2, 3, 4, 5 };
        if (isIn(2, nums))
            System.out.println("2 is in nums");
        if (!isIn(7, nums))
            System.out.println("7 is not in nums");
        System.out.println();
        // Use isIn() on Strings.
        String[] strs = { "one", "two", "three", "four", "five" };
        if (isIn("two", strs))
            System.out.println("two is in strs");
        if (!isIn("seven", strs))
            System.out.println("seven is not in strs");

        // if(isIn("two", nums))
        // System.out.println("two is in strs");
        // 타입호환이 안되서 컴파일 에러 발생
    }
}

5. 제네릭 생성자

생성자의 매개변수도 제네릭으로 선언 가능

생성자 또한 일반 메서드 내부에 선언 가능

class GenCons {
    private double val;

    <T extends Number> GenCons(T arg) {
        val = arg.doubleValue(); // double타입으로 변환
    }

    void showVal() {
        System.out.println("val: " + val);
    }
}

class GenConsDemo {
    public static void main(String[] args) {
        GenCons test = new GenCons(100);
        GenCons test2 = new GenCons(123.5F);
        test.showVal(); //100.0
        test2.showVal(); //123.5
    }
}

6. 제네릭 인터페이스

인터페이스의 메서드를 다양한 타입과 호환되도록 할 때

또는 메서드 매개 변수를 한정할 때 사용

// A Min/Max interface.
interface MinMax<T extends Comparable<T>> {
    T min();

    T max();
}

class MyClass<T extends Comparable<T>> implements MinMax<T> {
    T[] vals;

    MyClass(T[] o) {
        vals = o;
    }

    public T min() {
        T v = vals[0];
        for (int i = 1; i < vals.length; i++)
            if (vals[i].compareTo(v) < 0)
                v = vals[i];
        return v;
    }

    public T max() {
        T v = vals[0];
        for (int i = 1; i < vals.length; i++)
            if (vals[i].compareTo(v) > 0)
                v = vals[i];
        return v;
    }
}

class GenIFDemo {
    public static void main(String[] args) {
        Integer[] inums = { 3, 6, 2, 8, 6 };
        MyClass<Integer> iob = new MyClass<Integer>(inums);
        System.out.println("Max value in inums: " + iob.max());
        System.out.println("Min value in inums: " + iob.min());
    }
}

* 일반 클래스와 제네릭 클래스 간에도 상속 가능

* 자식 클래스는 부모 클래스를 상속 받고 새로운 제네릭 타입을 추가할 수 있음

class Gen2<T, V> extends Gen<T>{
...}

 

7. 제네릭의 제한사항

1) 타입 매개변수는 오브젝트화 할 수 없음

class Gen<T> {
    T ob;

    Gen() {
        ob = new T(); 
        // error->new키워드로 생성자 만들때 타입이 정해지지 않아서 type safety 벗어남
    }
}

2) 제네릭 멤버 변수를 static으로 선언할 수 없고, 

static 메서드가 제네릭 멤버 변수 사용 불가( 단, 제네릭 메서드 매개변수는 사용 가능)

(static 메서드 안에서는 원래 static 변수만 접근 가능)

 

class Wrong<T> {
    static T ob; // 에러-> 제네릭 멤버 변수 static 선언 불가

    static T getOb() { //에러-> static 메서드가 제네릭 멤버 변수 사용 불가
        return ob;
    }
}

3) 제네릭 배열 변수 선언은 가능하나, 생성은 불가

class Gen<T extends Number> {
    T ob;
    T[] vals; // 제네릭 배열 변수 선언 가능

    Gen(T o, T[] nums) {
        ob = o;
        vals = new T[10]; // 컴파일 에러-> 제네릭 배열 생성은 불가
        vals = nums; // 대입은 OK 
    }
}
728x90

'자바' 카테고리의 다른 글

람다 표현식  (2) 2022.12.10
static 키워드  (1) 2022.12.09
기타 자바 키워드  (0) 2022.12.08
try-with-resources문  (0) 2022.12.08
입출력 기본  (0) 2022.12.07