들어가며
우테코에서 Generic 관련 미니 미션을 수행하게 되었다. 수행을 하면서 느낀 것은 제네릭에 대해서 대충은 알고 있었지만 자세히 모른다고 생각하게 되었고 한번 정리해볼 필요가 있다고 느꼈다.
제네릭
제네릭이란 클래스를 정의 할 때 구체적인 타입을 적지 않고 변수로 적는 것이다.
제네릭은 모든 종류의 타입을 다룰 수 있도록, 클래스나 메소드를 타입 매개변수를 이용하여 선언하는 기법이다.
타입 매개변수란 타입을 변수로 표시하는 것을 의미한다. 즉 타입을 어떤 클래스의 매개변수로 보는 것이다.
내부 데이터 타입을 외부에서 지정하는 것.
제네릭이 없었다면 ?
public class Test{
private Object data;
public void set(Object data){
this.data = data;
}
public Object get(){
return data;
}
}
위와 같은 방식으로 반환받을 시 캐스팅 해야한다.
제네릭이 있다면 ?
public class Test<T>{
private T data;
public void set(T data){
this.data = data;
}
public T get(){
return data;
}
}
Test<String> test = new Test<String>();
위와 같은 방식으로 사용할 수 있다.
제네릭의 사용 이유
- 동적으로 타입이 결정되지 않고 컴파일 시 타입이 결정되므로 보다 안전하다.
- 런타임시 타입 충돌 문제 방지할 수 있다.
- 타입 체크와 형변환을 생략할 수 있어서 코드가 간결해진다.
제네릭 타입 매개변수
타입 매개변수로 사용하는 문자는 다른 변수와 혼동을 피하기 위해 하나의 대문자를 사용한다.
관례적으로
- E : Element를 의미하며 컬렉션에서 요소를 의미
- T : Type을 의미
- V : Value를 의미
- K : Key를 의미
제네릭 클래스
기존 클래스 작성 방법과 같은데 클래스 이름 뒤에 제네릭 타입의 매개변수를 추가하는 것이 차이가 있다.
public class Test<T>{
public <T> void test(T test) {
// ...
}
}
레퍼런스 변수 선언
Test<String> test;
위와 같은 방식으로 선언한다.
제네릭 클래스에 구체적인 타입을 대입하여 객체를 생성하는 과정을 구체화라고 한다. 이런 구체화 과정에서 **기본 타입(ex int)**은 사용할 수 없다.
제네릭 메소드
클래스의 일부 메소드만 제네릭으로 구현 가능하다.
제네릭 클래스의 타입과 관련이 없다!
class Test {
static <T> void test(T[] a){
for(int i=0; i<a.length(); i++{
System.out.println(a[i])
}
}
}
타입 매개변수는 메소드의 리턴 타입 앞에 선언된다.
제네릭 메소드를 호출 시 컴파일러가 메소드 인자를 타입을 통해 유추 가능하므로 타입을 명시하지 않아도 된다.
제한된 타입 매개변수
타입 매개변수에 구체적인 타입을 제한할 필요가 있다.
예를 들면 숫자 연산 제네릭 메소드는 매개변수 값으로 Number또는 하위 클래스 타입만 가져야한다. 그러면 상한 경계로 Number를 주면 되는 것이다.
상한 경계
T extends Number
- 타입 매개변수의 클래스는 crew 클래스이거나 하위 클래스 이어야한다.
static <T extends Number> Double sum(SimpleList<T> value){
~~~
}
T의 상한 경계클래스는 Number가 된다.
하한 경계
T super Number
- 타입 매개 변수의 클래스는 Number클래스이거나 Number의 상위클래스 이어야한다.
와일드 카드
와일드카드는 제네릭 타입을 매개 값이나 리턴 타입으로 사용할 때 구체적인 타입 대신에 사용하는 것으로 코드에서는 ? 로 표현하는 것이다.
이것을 보고 이해가 안갔다 ..
와일드카드와 제네릭의 차이
- 제네릭 : 타입을 모르지만, 타입이 정해진다면 그 타입의 특성을 활용한다.
- 와일드카드 : 타입을 모르지만, 무슨 타입인지 생각안하고 타입의 특성도 사용하지 않는다.
List<T> list 1 - list에 특정 타입이 오고, 그 특정 타입과 연관된 기능도 사용한다. ex) add, addAll
List<?> list 2 - list에 어떤 타입이 오던 상관없다. list의 기능만 사용한다. ex) size, 특정 타입과 연관된 기능은 사용하지 않는다. ex)add addAll
제네릭은 타입을 딱 지정해서 사용한다. 하지만 와일드카드는 매개변수에서 받더라도 타입의 가능성을 열어, 확정되지 않는다.
자바의 공변성 / 반공변성
자바에는 변성이라는 공변성과 반공변성 개념을 합친 것이 있다.
변성이란 타입의 서로 다른 타입간에 어떤 관계가 있는지 나타내는 개념이다.
공변성이란 서로 다른 타입간에 함께 변할 수 있는 특징이 있다.
- S가 T의 하위 타입이면 , S[]는 T[]의 하위 타입이다.
반공변성이란 공변성의 반대이다.
- S가 T의 하위 타입이면, T[]는 S[]의 하위타입이다.
하지만 배열과 다르게 제네릭 타입은 공변성/반공변성을 지원하지 않기에 무공변의 성질이다. 그래서 제네릭 타입은 전달받은 그 타입으로만 사용가능하고, 캐스팅이 불가하다.
이렇게 캐스팅이 불가하므로 같은 메소드를 쓰고 싶을 경우 메소드를 엄청 오버로딩하여 사용해야한다.
이를 해결하기 위해 나온 기능이 제네릭 와일드 카드이다.
자바의 제네릭은 기본적으로 공변, 반공변을 지원하지 않지만, 와일드카드를 이용하면 컴파일러 트릭을 통해 공변, 반공변이 적용되도록 설정할 수 있다.
상위 제한 와일드카드
class Printer { }
class LaserPrinter extends Printer { }
static <T> void copy(SimpleList<? extends T> laserPrinters, SimpleList<T> printers){
~~
}
위의 코드를 보면 현재 LaserPrinter가 Printer를 상속하고 있다. 이 둘을 copy하고자 하는데 타입이 다르고 공변성,반공변성이 적용되지 않기 때문에 와일드 카드를 사용하여 처리한 것을 볼 수 있다.
나가며
제네릭 생각보다 어렵다.. 딱 미션을 할 수 있을 정도만 공부해보았는데 다음에 더 자세히 공부해야할 것 같다. PECS도 공부해보자 !
'코코코딩공부 > JAVA' 카테고리의 다른 글
[JAVA] 상속 과 조합 (0) | 2023.03.13 |
---|---|
[JAVA] VO(Value Object) 란 무엇인가 ? (0) | 2023.03.12 |
[JAVA] 예외 처리 (0) | 2023.02.26 |
[JAVA] 일급 컬렉션 (0) | 2023.02.25 |
[JAVA] 원시값 포장 (0) | 2023.02.25 |