Generic
1 Generic
- 자바 5부터 Generic이 추가되었다.
- 제네릭은 컬렉션, 람다식, 스트림, NIO에서 널리 사용된다.
- 제네릭은 클래스, 인터페이스, 메소드를 정의할 때 타입을 파라미터로 사용할 수 있도록한다.
- 타입 파라미터는 코드 작성 시 구체적인 타입 으로 대체되어 다양한 코드를 생성하도록 한다.
1.1 Generic을 사용하는 이유
컴파일 시 강한 타입 체크
- 제네릭 타입을 사용하면 잘못된 타입이 사용될 수 있는 문제를 컴파일 과정에서 제거할 수 있다.
- 런타임 오류를 수정하는 것보다 컴파일 시간 오류를 수정하는 것이 더 쉽다
타입 변환을 제거
- 불필요한 타입 변환을 하지 않기 때문에 성능 향상
// 제네릭 사용 X
List list = new ArrayList();
list.add("hello");
String s = (String) list.get(0);
// 제네릭 사용
ArrayList<String> list = new ArrayList<>();
list.add("hello");
String s = list.get(0);
2 Generic Type
Generic Type은 타입을 파라미터로 가지는 클래스와 인터페이스를 말한다.- 즉
Type Parameter를 가진 클래스와 인터페이스를 말한다.
- 즉
- 선언시 클래스 또는 인터페이스 이름 뒤에
< >부호가 붙는다. < >사이에type parameter가 위치한다.
class name<T1, T2, ..., Tn> { /* ... */ }
public interface 인터페이스명<T> { ... }
2.1 Type Parameter(Type Variable)
- 실제 사용 코드에서는 Type Parameter 자리에 구체적인 타입을 인자로 주어야 한다.
- 메소드 정의시 파라미터를 선언하고 실제 메소드를 호출 할 때 인자를 넘겨주는 것과 같다.
- 메소드와 제네릭을 비교하면 메소드의 파라미터가 제네릭의
Type Parameter와 상응하고 메소드의 인자는 제네릭의type argument와 상응된다. - 컴파일러가
type argument로 받은 타입으로 클래스를 재구성해준다.
Type Parameter의 구체적인 타입은 기본 타입을 제외한 모든 종류가 가능하다- class type, interface type, array type, type variable 가능
예시
public class Box<T> {
private T t;
public T get() { return t; }
public void set(T t) { this.t = t }
}
- 위와 같은 Generic Type Box가 있다고 가정해보자.
Box<String> box = new Box<>();
type parameterT에type argument로 String 사용했다.
public class Box<String> {
private String t;
public String get() { return t; }
public void set(String t) { this.t = t }
}
- 재구성된 Box 클래스는 위와 같다.
Box<Integer> box = new Box<>();
- 이번에는
type parameterT에type argument로 Integer 사용를 사용해보자.
public class Box<Integer> {
private Integer t;
public Integer get() { return t; }
public void set(Integer t) { this.t = t }
}
- 재구성된 Box 클래스는 위와 같다.
2.2 Type Parameter Naming Convention
- 일반적으로
Type Parameter의 이름은 대문자 알파벳 한 문자로 표현한다.
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
2.3 Generic Type Invocation과 초기화
- 코드에서 제네릭 클래스를 참조하기 위해 generic type invocation이 필요하다.
- generic type invocation이란 클래스에
type argument를 전달하는 것이다. - 마치 메소드에 argument를 전달하는 것과 같다.
Box.java
public class Box<T> {
private T t;
public T get() { return t; }
public void set(T t) { this.t = t }
}
- 위와 같은 Generic Type이 있다고 가정하자.
generic type invocation 예시
// Generic Type invocation
// 여기서 type parameter는 T이고 type argument는 Integer이다.
Box<Integer> integerBox;
// Generic Type invocation과 초기화 동시에 진행
Box<Integer> integerBox = new Box<Integer>();
// 자바 7이후 아래와 같이 type argument 생략해서 사용 가능
Box<Integer> integerBox = new Box<>();
4 Generic 메소드
- 제네릭 메소드는
Type Parameter를 갖는 메소드를 말한다. - 클래스 혹은 인테페이스 전체 레벨에
Type Parameter를 설정하는 것이 아니라 하나의 메소드에만Type Parameter를 지정하고 싶을 때 Generic 메소드 사용한다.
제네릭 메소드 선언 방법
public <타입파라미터,...> 리턴타입 메소드명(매개변수,...) { ... }
public <T> Box<T> boxing(T t) { ... }
- 리턴 타입 앞에 angle bracket(
< >)을 추가하고 그 안에Type Parameter를 기술한다. Type Parameter를 리턴타입과 매개변수에 사용한다.- 리턴 타입:
Box<T> - 매개 변수:
T
- 리턴 타입:
제네릭 메소드 호출
Box<Integer> box = <Integer>boxing(100); //type argument를 명시적으로 지정
Box<Integer> box = boxing(100); //type argument 생략 가능 컴파일러가 추론한다.
5 제한된 타입 파라미터(Bounded Type Parameters)
Type Parameter이 받는type argument를 특정한 타입으로 제한하고 싶은 경우 Bounded Type Parameter를 사용한다.- 수와 관련된 일을 하는 메소드는 Number 클래스 또는 그 하위 클래스의 인스턴스를 받길 원한다.
- 이러한 경우
type argument로 Number 클래스 또는 그 하위 클래스로 제한할 수 있다.
5.1 Bounded Type Parameter 정의
public <T extends 상위타입> 리턴타입 메소드(매개변수, ...) { ... }
- 상위 타입은 클래스 뿐만 아니라 인터페이스도 가능하다.
- 인터페이스라고 해서 extends 대신 implements를 사용하지 않는다.
- 주의할 점
- 메소드의 중괄호 안에서 타입 파라미터 변수로 사용 가능한 것은 상위 타입의 멤버(필드, 메소드)로 제한된다.
- 하위 타입에만 있는 필드와 메소드는 사용할 수 없다.
- 상위타입으로 타입 파라미터를 제한시킨 상태에서 하위 타입의 멤버를 사용하면, 상위타입이 들어올 경우 에러가 발생한다.
예시1
- inspect 메소드에 Bounded Type Parameter 적용
- Number 클래스 또는 그 하위 클래스 로
type argument를 제한함 integerBox.inspect("some text");- String 클래스는 Number 클래스 또는 그 하위 클래스가 아니기 때문에 컴파일 에러
- 컴파일 과정에서 에러를 체크할 수 있는 제네릭의 장점
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!
}
}
예시2
- 타입 제한 기능과 더불어 제한시킨 타입의 메소드를 호출 할 수 있다.
- NaturalNumber 클래스에 Bounded Type Parameter 적용
- Integer 클래스 또는 그 하위 클래스로
type argument를 제한함 - T(Type Parameter)를 통해 Integer 클래스의 메소드 호출 가능
n.intValue()
public class NaturalNumber<T extends Integer> {
private T n;
public NaturalNumber(T n) { this.n = n; }
public boolean isEven() {
return n.intValue() % 2 == 0;
}
}
5.2 Multiple Bounds
- 아래와 같이 바운드는 하나 이상 지정 가능하다.
<T extends B1 & B2 & B3>
- 바운드 중 하나가 클래스라면 맨 처음에 기입해줘야 한다.
Class A { /* ... */ }
interface B { /* ... */ }
interface C { /* ... */ }
// ok
class D <T extends A & B & C> { /* ... */ }
// compile-time error: A는 클래스이기 때문에 맨 처음 기입해야한다.
class D <T extends B & A & C> { /* ... */ }
6 Generic과 Subtype
예시1
Object someObject = new Object();
Integer someInteger = new Integer(10);
someObject = someInteger; // OK
- 위와 같이 상위 클래스(Object) 참조 변수로 하위 클래스(Integer) 객체를 참조할 수 있다는 것을 알고 있을 것이다.
- 이러한 두 클래스간의 관계를 객체 지향 용어로
is a관계라고 한다.- Integer is a Object
예시2
// Number를 type argument로 Generic Type Invocation
Box<Number> box = new Box<Number>();
// Integer is a Number 관계이므로 OK
box.add(new Integer(10));
// Double is a Number 관계이므로 OK
box.add(new Double(10.1));
- 제네릭에서도 위와 같은 코드가 가능하다.
예시3
public void boxTest(Box<Number> n) { /* ... */ }
// 불가능 Box<Integer>는 Box<Number>의 서브 타입이 아니다
box.boxTest(new Box<Integer>());