목표
자바의 제네릭에 대해 학습하세요.
학습할 것
- 제네릭 사용법
- 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
- 제네릭 메소드 만들기
- Erasure
제네릭이란
- 데이터 타입을 일반화하는 것을 의미한다.
제네릭을 사용하는 이유
- 컴파일 시 강한 타입 체크를 할 수 있다.
- 타입 변환을 제거한다.
- 자바 5부터 제네릭 타입이 새롭게 추가되었다. 제네릭 타입을 이용함으로써 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있다. 제네릭은 클래스와 인터페이스, 그리고 메소드를 정의할 때 타입과 파라미터로 사용할 수 있도록 한다.
제네릭 사용법
제네릭 클래스 선언 및 생성
타입 변수
- 임의의 참조형 타입을 의미한다.
- 아무런 이름을 사용해도 컴파일에 문제가 없다.
- 여러 개의 타입 변수는 쉼표(,)로 구분하여 사용한다.
제네릭 타입의 권장 네이밍
- E : 요소
- K : 키
- N : 숫자
- T : 타입
- V : 값
- S, U, V : 두 번째, 세 번째, 네 번째에 선언된 타입
제네릭 클래스 사용법
GenericSample<String> // 꺽세안에 사용할 타입을 넣어준다.
GenericSample class는 String 타입으로 정의된 것과 같으므로 다른 타입을 넣으면 컴파일 에러가 발생한다.
genericSample 객체에 int 타입을 넣으면 컴파일 에러가 발생한다.
추가적으로 genericSample2 객체와 같이 제네릭 클래스도 기존의 객체 생성 방식으로 꺽세 없이 사용 가능하다.
참조변수와 생성자에 대입된 타입은 일치해야 한다.
JDK 1.7부터 추정이 가능한 경우 생성자 타입 지정 생략 가능하다.
제네릭의 제한
- 제네릭 클래스의 객체를 생성할 때, 객체별로 다른 타입을 지정하는 것은 적절하다. 제네릭은 인스턴스별로 다르게 동작하려고 만들었기 때문이다.
- 모든 객체에 대해 동일하게 동작해야하는 static맴버에(class 변수) 타입 변수 T를 사용할 수 없다.
- 제네릭 타입의 배열을 생성하는 것도 허용되지 않는다.(new 연산자 때문)
제한된 제네릭 클래스 생성법
아래와 같이 Box를 상속받는 FruitBox 제네릭 클래스를 생성하였다.
그런데 여기서 문제는 현재 과일 박스에 아무런 물건을 받을 수 있다. 장난감이라든지 과일이라든지
이러한 문제를 상속을 이용해서 해결할 수 있다.
더 이상 과일 박스에 과일 외에 상품을 담을 수 없다.
제네릭 주요 개념 (바운디드 타입, 와일드 카드)
바운디드 타입
- 바운디드 타입은 특정 타입의 서브 타입으로 제한한다.
와일드 카드
제네릭으로 구현된 메소드의 경우 선언된 타입으로만 매개변수를 입력해야 한다.
이를 상속받은 클래스 혹은 부모클래스를 사용하고 싶어도 불가능하고 어떤 타입이 와도 상관없는 경우에 대응하기 좋지 않다.
이러한 문제를 해결하기 위해서 와일드 카드를 사용한다.
와일드 카드 종류
Unbounded WildCard
- Unbounded WildCard는 List<?> 와 같은 형태로 물음표만 가지고 정의되어지게 된다.
- Object 클래스에서 제공되는 기능을 사용하여 구현할 수 있는 메소드를 작성하는 경우
- 타입 파라미터에 의존적이지 않는 일반 클래스의 메소드를 사용하는 경우
Upper Bounded WildCard
- Upper Bounded WildCard는 List<? extends Foo>의 형태로 사용한다.
- 특정 클래스의 자식 클래스만 인자로 받는다는 의미이다.
- 임의 Foo 클래스를 상속받는 어느 클래스가 와도 되지만 사용할 수 있는 기능은 Foo 클래스에 정의된 기능만 사용할 수 있다.
Lower Bounded WildCard
- Lower Bounded WildCard는 List<? super Foo>의 형태로 사용한다.
- 특정 클래스의 부모 클래스만 인자로 받는다는 의미이다.
제네릭 메서드
메서드의 선언부에 제네릭 타입이 선언된 메서드를 제네릭 메서드라 한다.
타입 변수의 선언은 메서드 선언부에서 반환 타입 바로 앞에 위치한다.
클래스에서 지정한 제네릭유형과 별도로 메소드에서 독립적으로 제네릭 유형을 선언하여 쓸 수 있다.
public static <T> T getObject(Class<T> classType) {
}
위와 같은 방식은 정적 메소드로 선언할 때 필요하기 때문이다.
정적 메소드는 객체를 인스턴스로 생성해서 접근할 필요없이 메모리에 올라가 있기 때문에 인스턴스 생성없이 바로 사용할 수 있다.
이런 정적 메소드가 타입을 얻어 오기 위해서는 제네릭 클래스와 별도로 독립적인 제네릭이 사용되어야 하는 이유이다.
Erasure
제네릭의 타입 소거(Generics Type Erasure)
- Erasure란 원소 타입을 컴파일 타임에서만 검사를하고 런타임에는 해당 타입 정보를 알 수가 없다. 즉 컴파일 상태에만 제약 조건을 적용하고, 런타임에는 타입에 대한 정보를 소거하는 프로세스이다.
즉, 컴파일된 파일(*.class)에는 지네릭 타입에 대한 정보가 없는 것이다.
- 이렇게 처리되는 이유는 지네릭이 도입되기 이전의 소스코드와의 호환성을 유지하기 위해서다.
- JDK1.5 부터 지네릭스가 도입되었지만, 아직도 원시 타입을 사용해서 코드를 작성하는 것을 허용한다.
- 언젠가 새로운 기능을 위해 하위 호환성을 포기하기 될 때가 올 것이다.
이렇게 하위호환성을 유지해야 함으로 인해 raw type 지원 + 지네릭을 구현할 때 소거(erasure) 방식을 이용하였다.
제네릭 primitive 타입 사용못하는 이유
제네릭에 대해 알아보면 다양한 코드를 접하고 작성해봤겠지만 특이한? 점이 있다.
바로 타입 파라미터에 primitive 타입을 사용하지 않는다는 것이다.
- 왜일까?- 결론은 타입 소거(type Erasure) 때문이다.
- 이해를 위한 List<Integer>를 정의해본다.List<Integer> list = new ArrayList<>();
- 위 코드의 바이트코드를 보면 아래와 같다.... INVOKESPECIAL java/util/ArrayList. <init> ()V ...
- 주목해야 할 점은 ArrayList가 생성될 때 타입정보가 없다는 것이다.
- 제네릭을 사용하지 않고 Raw Type으로 ArrayList를 생성해도 똑같은 바이트코드를 볼 수 있다.
- 그리고 내부에서 타입 파라미터를 사용할 경우 Object 타입으로 취급되어 처리한다.
- 이것이 타입 소거 (type Erasure) 라고 한다.
- 타입 소거는 제네릭 타입이 특정 타입으로 제한 되어 있을 경우 해당 타입에 맞춰 컴파일시 타입 변경이 발생하고 타입 제한이 없을 경우 Object 타입으로 변경된다.
→ 바로 하위 호환성을 지키기 위해 위와 같은 개념이 등장한 것이다.
→ 제네릭을 사용하더라도 하위 버전에서도 동일하게 동작해야 하기 때문이다.
자 그렇다면 알 수 있다 primitive 타입을 사용하지 못하는 것도 바로 이 기본 타입은 Object 클래스를 상속받고 있지 않기 떄문이다.
- 그래서 기본 타입 자료형을 사용하기 위해서는 Wrapper 클래스를 사용해야 한다.
- Wrapper 클래스를 사용할 경우 Boxing, Unboxing을 명시적으로 사용할 수도 있지만 암묵적으로 사용할 수 있으니 구현 자체에는 크게 신경쓸 부분이 없다.
참조
www.notion.so/4735e9a564e64bceb26a1e5d1c261a3d
제네릭
WhiteShip Java Study 시즌 1
www.notion.so
'JAVA > Study' 카테고리의 다른 글
백기선님 자바 스터디 13주차 (0) | 2021.03.04 |
---|---|
백기선님 자바 스터디 2주차 (0) | 2021.02.14 |
백기선님 자바 스터디 12주차 (0) | 2021.02.06 |
백기선님 자바 스터디 11주차 - Enum (0) | 2021.01.31 |
백기선님 자바 스터디 9주차 (0) | 2021.01.18 |