전공/객체지향프로그래밍

[객체지향프로그래밍][Java] Generics 심화 내용

Campus Coder 2023. 6. 3. 17:34
728x90
반응형

Generics

제네릭의 사용 이유

컴파일 시간 단축

캐스트의 제거

 

 

Generics class, interface

// raw type Box class
public class Box {
    private Object object;
    public void set(Object object) { this.object = object; }
    public Object get() { return object; }
}

// generic type Box class
public class Box<T> {
    private T t;
    public void set(T t) { this.t = t; }
    public T get() { return t; }
}

클래스 또는 인터페이스의 이름 뒤에 <>를 통해 타입 매개변수를 지정할 수 있음

 

타입 매개변수 예시

E - Element

K - Key

N - Number

T - Type

V - Value

 

예시 1

class Box<T> {
    private T t;
    public T get() { return t; }
    public void set(T t) { this.t = t; }
}

public class Main {
    public static void main(String[] args) {
        Box<String> box1 = new Box<String>();
        box1.set("hello");
        String str = box1.get();
        Box<Integer> box2 = new Box<>(); // type inference 
        box2.set(6);                     // 객체 생성시 타입 매개변수 생략 가능
        int value = box2.get();
    }
}

 

예시 2

class Box<T> { private T t;
    public T get() { return t; }
    public void set(T t) { this.t = t; }
}

interface Pair<K, V> {
    public K getKey();
    public V getValue();
}
class OrderedPair<K, V> implements Pair<K, V> {
    private K key; private V value;
    public OrderedPair(K key, V value) { 
        this.key = key;
        this.value = value;
    }
    public K getKey(){ return key; }
    public V getValue() { return value; }
}
public class Main {
    public static void main(String[] args) {
        Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
        Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world"); 
        OrderedPair<String, Integer> p3 = new OrderedPair<>("Even", 8);
        OrderedPair<String, String> p4 = new OrderedPair<>("hello", "world");
        OrderedPair<String, Box<Integer>> p = new OrderedPair<>("primes", new Box<Integer>());
                                           // new OrderedPair<>("primes", new Box<>()); 가능
    }
}

 

 

Generics method

<T, R> R method(T t)
ststic <T> void method(T t)
<R> R method(int arg)

위와 같은 형태로 표현

 

예시 1

class Box<T> {
    private T t;

    public <T> T method1(T t) { return this.t = t; } 
    // 제네릭 메소드(O), 메소드의 타입 매개변수 T와 클래스의 T(this.t의 타입)는 다름, 컴파일 에러 
    
    public T method2(T t) { return this.t = t; } 
    // 제네릭 메소드(X)
}

class Box {
    static <T> void method(T t) {...}
    // 제네릭 메소드 (O)
}

 

예시 2

class Util {
    public static <K, V> boolean compare(Pair<K, V> p1, Pair<K, V> p2) {
        return p1.getKey().equals(p2.getKey()) &&
                p1.getValue().equals(p2.getValue());
    }

    /* 주석처리된 메소드에 비해 위쪽 제네릭을 사용한 메소드는 다양한 형태의 Pair를 사용할 수 있음
    public static boolean compare(Pair<Integer, String> p1, Pair<Integer, String> p2) {
        return p1.getKey().equals(p2.getKey()) &&  
                p1.getValue().equals(p2.getValue());
    }
    */
}

class Pair<K, V> {
    private K key;
    private V value;

    public Pair(K key, V value) {
        this.key = key;
        this.value = value;
    }
    
    public void setKey(K key) { this.key = key; }
    public void setValue(V value) { this.value = value; }
    public K getKey() { return key; }
    public V getValue() { return value; }
}

public class Main {
    public static void main(String[] args) {
        Pair<Integer, String> p1 = new Pair<>(1, "apple");
        Pair<Integer, String> p2 = new Pair<>(2, "pear");
        
        boolean same1 = Util.<Integer, String>compare(p1, p2);
        boolean same2 = Util.compare(p1, p2); // type inference
        // 컴파일러가 메소드의 타입 매개변수 형을 추론 가능한 경우
        // 타입 매개변수 형을 명시하지 않아도 됨
        // 대부분의 경우 이렇게 사용
    }
}

 

 

Bounded Type parameters

타입 매개변수의 형태를 한정할 수 있음

 

<U extends Number>

Number를 포함해서 Number를 상속받는 클래스로 U의 범위를 제한

또한 U는 Number를 상속받는 클래스이므로 U.intValue()와 같은 Number의 메소드 사용 가능

(<T>의 경우, 기본적으로 Object를 상속받아 Object의 메소드 사용 가능)

 

인터페이스와 클래스로 타입 매개변수 형태 한정

class A { /* ... */ } 
interface B { /* ... */ } 
interface C { /* ... */ }

// 클래스는 상속이 하나만 가능하므로, 클래스 인터페이스의 순서를 맞춰주어야 함
// class Box extends A implements B, C {}
class D <T extends A & B & C> { /* ... */ }
class D <T extends B & A & C> { /* ... */ } // compile-time error

 

상속과 매개변수 형태 한정

public void boxTest(Box<Number> n) { /* ... */ }

boxTest(new Box<Integer>) // 오류
// Number과 Integer는 상속관계지만
// Box<Number>와 Box<Integer>는 상속 관계가 아님
// 이를 해결하기 위해 와일드카드<?> 개념 도입

 

Wildcards <?>

Box<Number>와 Box<Integer>는 상속 관계 x
Wildcards

List<?> ln = new LinkedList<Number>();
List<?> li = new LinkedList<Integer>();
// 지역변수 선언, 필드 선언, 매개변수 선언에 활용 가능

 

와일드 카드와 서브타입

 

Upper Bounded Wildcards

public class Main {
    public static double sumOfList(List<? extends Number> list) {
        double s = 0.0;
        for (Number n : list)
            s += n.doubleValue();
        return s;
    }

    public static void main(String[] args) {
        List<Integer> li = Arrays.asList(1, 2, 3);
        System.out.println("sum = " + sumOfList(li));
        List<Double> ld = Arrays.asList(1.2, 2.3, 3.5);
        System.out.println("sum = " + sumOfList(ld));
    }
}

 

Lower Bounded Wildcards

public static void addNumbers (List < ? super Integer > list)
{
    for (int i = 1; i <= 10; i++) {
        list.add(i);
    }
}

 

Unbounded Wildcards

public class Main {
    public static void printList(List<?> list) {
        for (Object elem: list)
            System.out.print(elem + " "); System.out.println();
    }
    public static void main(String[] args) {
        List<Integer> li = Arrays.asList(1, 2, 3);
        List<String>  ls = Arrays.asList("one", "two", "three");
        printList(li);
        printList(ls);
    }
}
728x90
반응형