Java Generic 자바 제네릭

choi jeong heon
슬기로운 개발생활
13 min readApr 24, 2020

Generic이 무엇인가요?

Generic : 포괄적인, 총칭의, 회사 이름이 붙지 않은, 일반 명칭으로 판매되는사전적 의미로, 딱 하나를 정하지 않고 범용적이고 포괄적이라는 의미입니다.

Java에서 Generic은 ,

Data type을 특정한 type하나로 정하지 않고 사용할때마다 바뀔 수 있게 범용적이고 포괄적으로 지정한다 라는 의미입니다.

Oracle에서 Generic을 설명할 때 사용하는 간단한 예제를 보도록 합시다.

/**
* Generic version of the Box class.
* @param <T> the type of the value being boxed
*/
public class Box<T> {
private T t;

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

Oracle에서 설명하기를,

class name<T1,T2, .. , Tn >

클래스 이름 옆의 괄호 (<>)의 parameter는 type parameters이며 type variable 이라고 부르기도 합니다.

<> 는 Java 7 이후부터 Diamond 로부른다고 합니다.

type parameter ‘T’ 는 클래스 내부 어디에서든 사용할 수 있습니다.

위의 Box 클래스에서 모든 Object의 생성은 T로 replace 됩니다.

type variable은 class type, interface type, array type … 등등의 non-primitive type은 어떠한 것이든 가능합니다.

https://docs.oracle.com/javase/tutorial/java/generics/types.html

Generic 왜 써야하죠?

Object 클래스의 한계

List list = new ArrayList();list.add(“hello”);String str = list.get(0);System.out.println(list);

위의 코드는 정상적으로 동작할까요?

list의 data type은 Object입니다. Object는 모든 클래스의 조상이기 때문에 어떤 종류의 클래스로도 포인팅할 수 있습니다. 따라서 2번째 라인까지는 문제 없어 보입니다.

하지만 3번째 라인에서 컴파일 에러가 발생합니다.

왜냐하면 list.get(0)를 하게되면 String type을 반환하는 것이 아니라 Object 타입을 반환하기 때문입니다.
위의 코드를 정상적으로 작동하게 하려면 다음과 같이 수정해야합니다.

String str = (String)list.get(0);

(String) 으로 type casting 했습니다. Object가 String으로 casting되면서 코드는 정상적으로 동작합니다.

그러면 casting 하면 되지 뭐가 문제입니까?

만약 list에 수만개의 String이 들어있고 모든 데이터를 꺼내야 한다면, 수만번의 type casting이 필요하게 됩니다. 이것은 시스템 성능의 큰 저하를 가져오게 되죠.

또한 , 다음과 같이 잘못된 casting이 발생할 수 있습니다.

int iValue = (int)list.get(0);

그러면 ClassCastException 발생하고 프로그램이 죽어버릴 수도 있습니다.

그래서 Java 5 부터 Generic이 추가되었습니다.

기본적으로 Collections 클래스들에 Generic이 적용되었습니다.
다음과 같이 사용할 수 있습니다.

ArrayList<String> stringList = new ArrayList<>();

위의 Oracle 예제를 Generic을 사용해서 다시 구현해보았습니다.

List<String> list = new ArrayList<>();list.add(“hello”);String str = list.get(0);System.out.println(list);

그리고 이 코드는 casting 없이 정상적으로 동작합니다!

또한 Generic을 사용하면 ClassCastException으로부터 자유로워집니다.

컴파일러가 에러라고 알려주네요. 정말 좋죠?

여담으로, Generic을 사용하기 위해 Java 5에서 collection framework를 모두 재개발했다고 합니다.

Generic에 대해 더 자세히 알아보겠습니다.

Multiple Type Parameters

두 개 이상의 멀티 타입 파라미터를 사용할 수 있습니다.
각 type parameter는 콤마( , ) 로 구분합니다.

아래의 코드는 두개의 OrderedPair 클래스 인스턴스를 생성합니다.

Pair<String, Integer> p1 = new OrderedPair<String, Integer>("Even", 8);
Pair<String, String> p2 = new OrderedPair<String, String>("hello", "world");

p1의 value는 Integer이고 p2의 value는 String이 되겠네요.

참고로 Oracle에서는 Type Parameter Naming Conventions (파라미터 네이밍 규칙)을 소개하고 있습니다.

The most commonly used type parameter names are:

  • E — Element (used extensively by the Java Collections Framework)
  • K — Key
  • N — Number
  • T — Type
  • V — Value
  • S,U,V etc. — 2nd, 3rd, 4th types

Generic Method

method 역시 Generic하게 구현할 수 있습니다.
형식은 다음과 같습니다.

public <type parameter> 'return type' 'method name'( ...)
물론 항상 public일 필요는 없습니다.
ex)
public static <K,V>boolean compare(Pair(K,V) p1, Pair(K,V) p2){
//생략
return true;
}
**static method의 경우 type parameter 가 return type앞에 반드시 있어야 합니다.

더 자세한 내용은 여기를 참고하세요.

Bounded Type Parameter

만약 type parameter에 제한을 두고 싶을 경우 다음과 같이 사용하면 됩니다.

public class Box<T> {

private T t;

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

public T get() {
return t;
}

public <U extends Number> void inspect(U u){
System.out.println("T: " + t.getClass().getName());
System.out.println("U: " + u.getClass().getName());
}

public static void main(String[] args) {
Box<Integer> integerBox = new Box<Integer>();
integerBox.set(new Integer(10));
integerBox.inspect("some text"); // error: this is still String!
}
}

type parameter U 를 Number로 제한했습니다. 이때 extends 키워드를 사용하면 됩니다. U에는 Integer , Float, Double 등등의 클래스들만 허용됩니다.

Generic, Inheritance, Subtype

Is a 관계
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger; // OK

Object는 Integer의 Super type 이므로 Integer를 Object에 할당할 수 있습니다.
객체지향에서 이를 ‘Is a’ 관계라고 표현 합니다.

public void someMethod(Number n) { /* ... */ }

someMethod(new Integer(10)); // OK
someMethod(new Double(10.1)); // OK

Integer는 Object이면서 동시에 Number 이기 때문에 위의 코드도 허용됩니다.

Box<Number> box = new Box<Number>();
box.add(new Integer(10)); // OK
box.add(new Double(10.1)); // OK

Generic에서도 마찬가지로 허용됩니다.

그러나

public void boxTest(Box<Number> n) { /* ... */ }boxTest(Box<Integer>)
boxText(Box<Double>)
**boxTest메서드에서 위의 두 코드를 받지 못합니다.

Box<Integer>와 Box<Double>은 Box<Number>의 sub type이 아니기 때문입니다.

반면에 Collections 클래스에서는 ArrayList<E> 가 List<E>를 implements 하고 List<E> 가 Collections<E>를 implements 하는 관계이므로 ArrayList<E>는 List<E>의 sub type, List<E>는 Collection<E>의 sub type입니다.

그런데 만약, List<E>를 상속받고 Generic type P를 갖는 클래스를 만들어 보면 다음과 같습니다.

//E는 List<E>에게 상속받은 generic type.
Interface PayloadList<E,P> extends List<E> {
void setPayload(int index, P val);...}

List<E>를 상속받았으므로 아래의 녀석들은 List<E>의 sub type입니다.

•PayloadList<String,String>•PayloadList<String,Integer>•PayloadList<String,Exception>

WildCard

Generic code에서 question mark (?)는 wild card 라고 부르며 ‘unknown type’ 즉, 알 수 없는 type 을 나타냅니다.
Wild Card는 다양한 상황에서 사용될 수 있습니다.

Local Variable
List<?> b;
Field,type of parameter
public class Example {
? field1; // invalid
List<?> field2; // valid

private ? method1(? param) {return param;} // invalid
private List<?> method2(List<?> param) {return param;} // valid

private void method3() {
? var1; // invalid
List<?> var2; // valid
}
}
return type에도 사용될 수 있으나 권장하지 않는다고 합니다. 여기를 참고하세요.

Upper Bounded Wildcards

변수에 대한 제한을 완화시키고 싶은 경우 Upper Bounded Wildcards를 사용하면 됩니다. wild card를 ‘extends’ 키워드 앞에 붙인 꼴 입니다.

<? extends Type >

예를 들어 Number와 Number의 sub type ( Integer, Double ..) 을 가질 수 있는 List 를 만들어야 한다면,

List<Number> 는 오직 Number type과 매치되지만,
List<? extends Number>는 Number와 Number의 sub type 모두에 매치됩니다.


public double sumOfList(List<? extends Number> list) {
double s = 0.0;
for (Number n : list)
s += n.doubleValue();
return s;
}
public void process(List<? extends Foo> list){
for (Foo elem : list){
// ...
}
}

UnBounded Wildcards

UnBounded Wildcard는 위에서 잠깐 언급했던 wild card, 즉 (?) 를 단독으로 사용하는 경우입니다.

<?>   for example, List <?>

예를 들어 printList() 메서드를 만들어 본다면, 다음과 같이 만들 수 있습니다.

public static void printList(List<Object> list) {
for (Object elem : list)
System.out.println(elem + " ");
System.out.println();
}

하지만 printList() 메서드의 목적은 print가 가능한 모든 type에서 동작하는 메서드여야 합니다. 위의 코드는 List<Integer> , List<Double> 등등에서는 동작하지 않습니다. ‘Is a’ 관계가 아니기 때문이죠.

이때 UnBounded Wildcards를 사용하면 됩니다.

public static void printList(List<?> list) {
for (Object elem: list)
System.out.print(elem + " ");
System.out.println();
}

이제 printList() 메서드는 List<Integer> , List<Double> 등등에서 동작합니다.

Lower Bounded Wildcards

Upper Bounded Wildcards와 반대라고 생각하시면 됩니다. 특정 type에서 해당 type의 super type들까지 허용하는 Generic을 만드는 경우입니다.
wild card 뒤에 ‘super’ 키워드를 붙여서 사용합니다.

<? super Type>

예를 들어 Integer타입과 Integer의 super type들까지 허용하는 List를 만드려면, 다음과 같이 사용합니다.

List<? super Integer>
**List<Number> , List<Object> , List<Integer> 모두 허용됨.
public static void addNumbers(List<? super Integer> list) {
for (int i = 1; i <= 10; i++) {
list.add(i);
}
}

지금까지 Java의 Generic에 대해 알아보았습니다.
더 자세한 내용은 Oracle Docs에서 확인해보세요.

--

--