Post

[JAVA] 제네릭

제네릭

  • Java 5부터 도입된 기능으로, 타입 파라미터를 전달받아 객체나 메소드에서 제네릭 타입을 사용하는 기법
  • 컴파일 시점에 타입을 체크하여 타입 안전성 보장
  • 다양한 타입에서 작동하는 객체나 메소드 정의 가능
  • 캐스팅(형변환) 사용이 불필요함
  • 타입 파라미터에는 참조형만 전달 가능
    • Java에서 제공하는 각 기본형에 대응하는 래퍼 클래스(Wrapper Class) 사용
  • 제네릭 타입은 배열을 선언할 수 없음
    • Data<Integer>[] arrayOfData; //오류
기본형래퍼 클래스
booleanBoolean
charCharacter
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble

제네릭 클래스

1
2
3
4
5
6
7
8
9
10
11
class Data<T> {
  private T t;

  public void set(T t) {
    this.t = t;
  }

  public T get() {
    return t;
  }
}
  • 클래스를 정의할 때 클래스명 뒤에 다이아몬드 연산자(<>)를 사용하여 타입 파라미터 선언
  • 여러 개의 타입 파라미터 지정 가능
  • 클래스 내부의 데이터 필드나 메소드에 타입 파라미터 사용 가능
    • static 데이터 필드에는 사용할 수 없음
  • 생성자 호출 시, 변수 선언부에 제네릭 타입이 명시되어 있다면 생성자의 타입 파라미터 생략 가능 (Java 7부터 컴파일러가 타입 추론 가능)
    • ex) List<Integer> list = new ArrayList<>();

제네릭 메소드

1
2
3
4
5
public <T> void swap(T[] array, int i, int j) {
  T temp = array[i];
  array[i] = array[j];
  array[j] = temp;
}
  • 타입 파라미터를 메소드의 반환형, 매개변수의 자료형, 지역 변수의 자료형으로 사용 가능
  • 메소드를 정의할 때 접근 제어자와 반환형 사이에 타입 파라미터 선언 (반환형을 지정하지 않으면 타입 파라미터를 반환형으로 사용)
  • 하나의 메소드로 여러 타입의 데이터를 처리할 때 유용함
  • static, non-static 모두 제네릭 메소드로 정의 가능
  • 메소드에 전달하는 매개변수로 메소드의 타입 추론이 가능한 경우, 타입 파라미터 생략 가능
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
class Pair<K, V> {
  private K key;
  private V value;

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

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());
  }
}

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 same = Util.<Integer, String>compare(p1, p2); //<Integer, String> 생략 가능
    System.out.println(same);
  }
}

제네릭 인터페이스

제네릭 클래스에서 제네릭 인터페이스 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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<> ("Even", 8);
    Pair<String, String> p2 = new OrderedPair<> ("hello", "java");
  }
}

일반 클래스에서 제네릭 인터페이스 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
interface Pair<K, V> {
  public K getKey();
  public V getValue();
}

// 클래스를 정의할 때 제네릭 인터페이스에 타입 파라미터 선언
class MyPair implements Pair<String, Integer> {
  private String key;
  private Integer value;

  public MyPair(String key, Integer value) {
    this.key = key;
    this.value = value;
  }
  public String getKey() { return key; }
  public Integer getValue() { return value; }
}

public class Main {
  public static void main(String args[]) {
    MyPair mp = new MyPair("test", 1);
  }
}

Raw 타입

  • 타입 파라미터 없이 사용되는 제네릭 타입
  • 타입 파라미터를 Object 타입으로 처리함
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class ArrayList<E> implements List<E> {
  public boolean add(E, e) {...}
  public E get(int index) {...}
  public E remove(int index) {...}
}

public class Main {
  public static void main(String args[]) {
    List list1 = new ArrayList(); // Raw 타입: 타입 파라미터를 Object 타입으로 취급
    list1.add("hello"); // Object 타입으로 저장됨
    String s1 = (String) list1.get(0); // Object->String 형변환 필요

    List<String> list2 = new ArrayList<String>(); // 제네릭 타입 사용
    list2.add("hello"); // String 타입으로 저장됨
    String s2 = list2.get(0); // 형변환 필요 없음
  }
}

제네릭 타입 제한

  • 타입 파라미터에 적용 가능한 자료형의 상한을 제한하는 것
  • <T extends 제한타입>
    • 타입 파라미터는 제한타입이거나 제한타입의 하위 클래스여야 함
1
2
3
4
5
6
7
8
class Data<T extends Number> {...}

public class Main {
  public static void main(String args[]) {
    Data<Integer> data1 = new Data<>(20);
    Data<String> data2 = new Data<>("Hello"); //오류: The type String is not a valid substitute for the bounded parameter <T extends Number> of the type Data<T>
  }
}

인터페이스 타입 제한

  • 인터페이스를 제네릭 제한 타입으로 선언하여, 해당 인터페이스를 구현하는 클래스만 타입 파라미터로 제한할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
interface Readable {...}

// Readable 인터페이스를 구현한 클래스
class Student implements Readable {...}

// 타입 파라미터에 Readable 인터페이스를 구현한 클래스만이 올 수 있게 됨
class School <T extends Readable> {...}

public class Main {
  public static void main(String args[]) {
    School<Student> a = new School<Student>();
  }
}

다중 인터페이스 타입 제한

  • & 연산자를 이용하면 해당 인터페이스들을 모두 구현한 클래스만 타입 파라미터로 제한할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
interface Readable {}
interface Closeable {}

// Readable 인터페이스만 구현한 클래스
class Student implements Readable {}

// 인터페이스 Readable과 Closeable를 구현한 클래스만 제네릭 타입에 올 수 있음
class School <T extends Readable & Closeable> {}

public class Main {
  public static void main(String args[]) {
    School<Student> a = new School<Student>(); //오류: Readable과 Closeable를 모두 구현해야 함
  }
}

제네릭 클래스의 형변환

  • 일반 클래스는 상속 관계에 있는 클래스 간에 자동 형변환이 가능하지만, 제네릭 클래스는 상속 관계여도 형변환이 되지 않는다.
  • 만약 형변환이 필요하다면, 와일드카드를 사용하여 우회할 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
// 일반적인 상속 관계에서의 형변환
Object parent = new Object();
Integer child = new Integer(1);
parent = child; //업캐스팅 가능
Object parent2 = new Integer(1);
Integer child2;
child2 = (Integer) parent2; //다운캐스팅 가능

// 제네릭에서는 불가능
ArrayList<Object> parent = new ArrayList<>();
ArrayList<Integer> child = new ArrayList<>();
parent = child; //오류: 업캐스팅 불가능
child = parent; //오류: 다운캐스팅 불가능

상한 경계 와일드카드(upper bounded)

  • <? extends T>
  • T 또는 T의 하위 클래스만 타입 파라미터로 사용 가능
1
2
3
4
5
6
7
8
9
ArrayList<? extends Object> parent = new ArrayList<>();
ArrayList<? extends Number> child = new ArrayList<>();
ArrayList<? extends Integer> child2 = new ArrayList<>();
ArrayList<Integer> child3 = new ArrayList<>();

//제네릭 타입 업캐스팅
parent = child;
parent = child2;
parent = child3;

하한 경계 와일드카드(lower bounded)

  • <? super T>
  • T 또는 T의 상위 클래스만 타입 파라미터로 사용 가능
1
2
3
4
5
6
7
8
9
ArrayList<Object> parent = new ArrayList<>();
ArrayList<? super Object> parent2 = new ArrayList<>();
ArrayList<? super Number> parent3 = new ArrayList<>();
ArrayList<? super Integer> child = new ArrayList<>();

//제네릭 타입 다운캐스트
child = parent;
child = parent2;
child = parent3;

비한정적 와일드카드(unbounded)

  • <?>
  • 모든 타입 허용

참고사이트

자바 제네릭의 공변성 & 와일드카드 완벽 이해

This post is licensed under CC BY 4.0 by the author.