Search
Duplicate
🌔

제네릭, 지네릭, 제네릭스

태그
개념
정의
상위 항목
하위 항목
난 제네릭이라고 부른다.

제네릭

데이터 타입을 일반화하는 것을 의미한다.
제네릭은 클래스나 메소드에서 사용하는 매개변수나 필드들의 데이터 타입을 컴파일 시에 미리 정의해두는 방법이다.
이를 통해 컴파일 시 타입 체크를 진행할 수 있다.
클래스나 메소드 내부에서 사용되는 객체 타입의 안정성을 확보할 수 있다.
반환값에 대한 타입 변환 및 타입 검사를 진행하지 않아 구현단계가 줄어든다.
제네릭은 자바 5 버전에서 처음 도입되었는데, 이전의 경우 여러 타입을 반환하는 대부분의 클래스나 메소드에서 인수나 반환값으로 (Object)와 같은 키워드를 사용해야 했었다.
이 경우, 해당 결과를 전달받은 메소드에서 원하는 타입으로 다시 변환해야했으며 이를 구현하는 과정에서 복잡성이 증가하므로 오류를 발생시킬 가능성도 있었다.
제네릭을 사용하면 컴파일 시에 미리 타입이 지정되므로 타입 검사나 타입 변환과 같은 번거로운 작업을 생략할 수 있게 되었다.
추상화 수준의 향상으로 인한 이득이라고 할 수 있지 않을까?
⇒ 아무튼 TypeSafe를 보장하기 위한 방법!

제네릭을 사용하는 이유

제네릭 타입을 사용하므로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있다.
자바 컴파일러는 코드에서 잘못 사용된 타입 때문에 발생하는 문제점을 제거하기 위해 제네릭이 사용된 코드에 대해 강한 타입 체크를 한다.
뭐든지, 런타임 단계에서 거르는 에러보단 컴파일 단계에서 거를 수 있는 에러가 좋다.
메소드를 사용할 때, 제네릭을 사용하면 요청되는 타입으로 범위를 제한시켜 반환하기 때문에 타입 변환에 대한 구체적인 구현이 필요 없어진다.

제네릭 사용법

제네릭의 선언 및 생성
class Sample<T> { T age; public void setAge(T age) { this.age = age; } public T getAge() { return this.age; } } class Sample<T> { T age; K key; public void setAge(T age, K key) { this.age = age; this.key = key; } public T getAge() { return this.age; } public K getKey() { return this.key; } }
Java
복사
타입 변수
임의의 참조형 타입을 의미한다. 여기선 T로 표현되었다.
아래의 네이밍을 참조해서 지켜주는 것이 좋다.
E: 요소, K: 키, V: 값, T: 타입, N: 숫자, S,U,V: 두 번째, 세 번째, 네 번째에 선언된 타입
타입 변수 클래스뿐만 아니라 메소드의 매개변수나 반환값으로도 사용 가능하다.
바이트코드 단계에서 Object로 변환되서 사용된다. 즉, 제네릭은 컴파일 시점까지만 유효하다.

제네릭 주요 개념

바운드 타입과 와일드카드에 대해 설명한다. 두 개념 모두 타입 형식을 제한하는 기능이다. 그럼에도 불구하고 두 개념이 존재하는 이유는 뭘까?
먼저 변성에 대해서 알아보자.
변성이란 제네릭을 사용할 때, 상속에 관련한 이슈가 발생할 때 접할 수 있다.
먼저 간단한 예시를 통해서 설명하자면 다음과 같다.
IntegerNumber를 상속받아 만들어진 클래스다.
따라서 IntegerNumber의 하위 타입이다.
다음과 같이 표현할 수 있다.
public void test() { List<Number> list; list.add(Integer.valueOf(1)); }
Java
복사
하지만 List<Integer>List<Number>의 하위 타입이 될 순 없다.
이런 상황에서 Java는 제네릭을 사용할 때, 타입 경계를 명시하여 SubType, SuperType을 가능하게 해주는데 이를 변성이라고 한다.
제네릭은 기본적으로 불공변성을 가지고 있다.
바운드 타입
바운드 타입은 특정 타입의 서브 타입으로 제한하는 기능이다.
클래스나 인터페이스를 설계할 때, 가장 흔하게 사용되는 개념이다.
class Sample<T extends Number> { T age; public void setAge(T age) { this.age = age; } public T getAge() { return this.age; } } // 오류 Sample test2 = new Sample(); test2.setAge("1"); // Number의 하위항목으로 제한시켰으나 하위항목이 아닌 String이 들어옴 System.out.println(test2.getAge());
Java
복사
와일드카드
제네릭을 이용해 구현된 메소드의 경우, 선언된 타입의 매개변수만 입력 가능하다.
자식 클래스나 부모 클래스를 사용할 수 없으며 여러 타입이 사용되어지는 경우 대응하기 쉽지 않다.
이를 제공하기 위해 와일드카드를 사용한다.
Unbounded WildCard
List<?>와 같은 형태로 물음표만 가지고 정의되는 형태다.
내부적으로 Object로 정의되어 사용되며 모든 타입의 인자를 받을 수 있다. 다음과 같은 경우에서 사용하는 것이 일반적이다.
Object 클래스에서 제공되는 기능을 사용하여 구현할 수 있는 메서드를 작성하는 경우
타입 파라미터에 의존적이지 않는 일반 클래스의 메소드를 사용하는 경우
List.clear(), List.size()
Upper Bounded WildCard
List<? extends Foo>와 같은 형태로 정의되는 형태다.
특정 클래스의 자식 클래스만을 인자로 받는다는 것으로 임의의 Foo 클래스를 상속받는 어느 클래스가 와도 되지만 사용할 수 있는 기능은 Foo 클래스에 정의된 기능만 사용 가능하다.
Lower Bounded WildCard
List<? super Foo>와 같은 형태로 정의되는 형태다.
Upper Bounded Wildcard와 다르게 특정 클래스의 부모 클래스만을 인자로 받는다.
PECS(Producer-Extends, Consumer-Super) 공식
그렇다면 도대체 언제 super를 사용해야 하고, 언제 extends를 사용해야 하는지 헷갈릴 수 있다.
이펙티브 자바에서는 PECS라는 공식을 만들었는데, 이는 Producer-Extends, Consumer-Super의 줄임말이다.
즉, 컬렉션으로부터 와일드카드 타입의 객체를 생성 및 만들면 extends를, 갖고 있는 객체를 컬렉션에 사용 또는 소비하면 super를 사용하라는 것이다.