-
[live-study] 14주차 :: 제네릭JAVA/라이브 스터디 | whiteship 2021. 2. 27. 02:08
참고도서 : Java의 정석(3rd Edition), 이것이 자바다
0. 제네릭이란제네릭(Generic)이란
- Java5부터 제네릭(Generic) 타입이 새로 추가되었다.
- 제네릭은 다양한 타입의 객체들을 다루는 메소드나 컬렉션 클래스에 컴파일 시의 타입체크(compile-time type check)를 해주는 기능이다. 즉, 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 걸러낼 수 있게 되었다.
- 제네릭은 컬렉션, 람다식, 스트림, NIO에서 사용되며, 클래스와 인터페이스, 그리고 메소드를 정의할 때 타입을 파라미터로 사용할 수 있도록 한다.
제네릭(Generic)의 장점
- 컴파일 시 강한 타입 체크를 할 수 있다. 즉, 컴파일 시 미리 타입을 체크하기 때문에, 객체의 타입 안정성을 제공한다.
- 타입 변환(type casting)을 제거한다. 예를 들어 ArrayList와 같은 컬렉션 클래스를 사용했을 때, 비제네릭 코드는 불필요한 타입 변환을 진행하여 프로그램 성능에 악영향을 미친다. 하지만, 제네릭 코드를 사용하면 이러한 타입변환을 할 필요가 없어서 프로그램 성능이 향상되고 코드가 간결해진다.
1. 제네릭 사용법제네릭 용어
- 아래와 같이 제네릭 클래스 Box가 선언되어 있다면, 각 부분을 부르는 용어는 아래와 같다.
- 또한 아래와 같이 타입 매개변수에 타입을 지정할 수 있다.
제네릭 타입 (class<T>, interface<T>)
- 제네릭 타입은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.
- 그래서 제네릭 타입은 클래스 또는 인터페이스 이름 뒤에 < > 부호를 붙이고, < > 사이에 타입 파라미터(ex. T)를 넣는다.
- 예를 들어, 비제네릭으로 Box 클래스를 만들어보면 아래와 같을 것이다. 이렇게 되면 모든 종류의 객체(Object 타입으로 선언하였으므로)를 저장할 수는 있겠지만, 타입 변환이 발생하게 된다.
- 앞서 말했듯이 이러한 타입 변환이 발생하지 않도록 하기 위해, 제네릭을 이용한다. 위 클래스에서 Object 타입을 모두 T로 대체를 하였다.
- 즉, 클래스를 설계할 때는 구체적인 타입 대신 타입 파라미터를 사용했다. 그렇게 되면 실제 클래스를 사용될 때 구체적인 타입을 지정하게 되고, 타입 변환을 최소화시킬 수 있다.
- 그리고 위와 같이 제네릭 타입 변수 선언과 객체 생성을 동시에 하는 경우, 타입 파라미터를 지정하는 부분이 중복되서 나온다. 타입이 동일한 경우(유추가 가능한 경우), 자바7부터는 다이아몬드 연산자 < >를 제공하여 타입 파라미터를 생략할 수 있다.
- 즉, 자바 컴파일러가 타입 파라미터 부분에 < > 연산자를 사용하면 알아서 유추해서 자동으로 생성해준다.
타입 파라미터 네이밍 관습
- E : Element
- K : Key
- N : Number
- T : Type
- V : Value
- S, U, V, .. : 2번째, 3번째, 4번째 타입들
멀티 타입 파라미터 (class<K,V, ...>, interface<K,V, ...>)
- 제네릭 타입은 앞서 본 것 처럼 하나의 타입 파라미터 뿐만 아니라, 두 개 이상의 멀티 타입 파라미터를 사용할 수 있다. 이 경우 각각의 타입 파라미터는 콤마( , )로 구분한다.
- 멀티 타입 파라미터는 아래와 같이 사용할 수 있다.
2. 제네릭 주요 개념 (바운디드 타입, 와일드 카드)
바운디드 타입 (<T extends 최상위타입>)
- 타입 파라미터(T)에 지정할 수 있는 구체적인 타입의 종류를 제한할 때 사용된다.
- 제한된 타입 파라미터를 선언하려면 타입 파라미터 뒤에 extends 키워드를 붙이고 상위타입을 적으면 된다.
- 상위 타입으로는 클래스와 인터페이스를 구현할 수 있는데, 인터페이스의 경우에도 (implements가 아닌) extends 키워드를 사용한다.
- 예를 들어, Util클래스에서 Number만 구체적인 타입으로 갖는 제네릭 메소드 compare()를 만들었다. 두 개의 숫자 타입을 매가값으로 받아 compare 한 결과 값을 return한다.
- int와 double은 문제없이 값이 출력되는 것을 볼 수 있지만, String은 Number 타입이 아니므로 에러가 나는 것을 볼 수 있다.
와일드 카드(<?>, <? extends ...>, <? super ...?>)
- 코드에서 ?를 일반적으로 와일드카드(wildcard)라고 부른다.
- 제네릭 타입을 매개값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 와일드카드를 다음과 같이 3가지 형태로 사용할 수 있다.
- <?> : Unbounded Wildcards (제한 없음) - 모든 타입이 가능. <? extends Object>와 동일
- <? extends T> : Upper Bounded Wildcards (상위 클래스 제한). T와 그 자손들만 가능
- <? super T> : Lower Bounded Wildcards (하위 클래스 제한). T와 그 조상들만 가능
- 예를들어, 아래와 같은 관계를 갖는 클래스들이 있고, 제너릭 타입의 Feed라는 클래스가 있다고 가정한다.
- Feed<?>가 되면 모든 타입(Animal, Cat, Dog, Poodle)이 될 수 있다.
- Feed<? extends Dog>가 되면 Dog와 그 자손들만 가능하므로, Dog, Poodle 타입이 될 수 있다.
- Feed<? super Cat>이 되면 Cat과 그 조상들만 가능하므로, Cat, Animal 타입이 될 수 있다.
3. 제네릭 메소드 만들기제네릭 메소드(<T, R> R method (T t))
- 제네릭 메소드는 매개 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드를 말한다.
- 선언하는 방법은 리턴 타입 앞에 < > 기호를 추가하고 타입 파라미터를 넣은다음, 리턴 타입과 매개 타입으로 타입 파라미터를 사용하면 된다.
제네릭 메소드 호출
- 제네릭 메소드는 두 가지 방법으로 호출 할 수 있다.
- 타입 파라미터의 구체적인 타입 명시
- 컴파일러가 타입 추정
제네릭 메소드 정의와 호출
- 위에서 설명한대로 제네릭 메소드를 정의하고 호출한다.
- Util 클래스에서 boxing이라는 제네릭 메소드를 정의하고, BoxMethodTest 클래스에서 호출하였다.
4. ErasureErasure
- 컴파일러는 제네릭 타입을 이용해서 소스파일을 체크하고, 필요한 곳에 형변환을 넣어준 다음에 제네릭 타입을 제거한다.
- 그래서 컴파일 된 파일(.class) 에는 제네릭 타입 정보가 없다.
- 이렇게 하는 이유는 JDK 1.5부터 제네릭이 도입되었지만, 아직도 원시 타입을 사용해서 코드 작성하는 것을 허용하기 때문에 이전의 소스 코드와의 호환성을 유지하기 위해서이다.
- 가장 기본적인 제거 과정 2단계를 보면 다음과 같다.
제네릭 타입의 경계(bound) 제거
- 예를 들어, 제네릭 타입이 <T extends Animal> 이라면 T는 Animal로 치환된다.
- <T>인 경우라면 T는 Object로 치환된다.
- 그리고 클래스 옆의 선언은 제거된다.
제네릭 타입을 제거한 후에 타입이 일치하지 않으면, 형변환 추가
- List의 get()은 Object 타입을 반환하므로 형변환이 필요하다.
- 와일드 카드가 포함되어 있는 경우 또한 적절한 타입으로의 형변환이 추가된다.
참고자료
'JAVA > 라이브 스터디 | whiteship' 카테고리의 다른 글
[live-study] 15주차 :: 람다식 (0) 2021.03.06 [live-study] 13주차 :: I/O (0) 2021.02.21 [live-study] 12주차 :: 애노테이션 (0) 2021.02.15 [live-study] 11주차 :: Enum (0) 2021.02.15 [live-study] 10주차 :: 멀티쓰레드 프로그래밍 (0) 2021.02.15