Generic
데이터 형식에 의존하지 않고 하나의 값이 여러 다른 데이터 타입들을 가질 수 있도록 하는 방법으로,
클래스 내부에서 지정하는 것이 아닌 외부에서 사용자에 의해 지정된다.
ex)
객체<타입> 객체이름 = new 객체<타입>();
<주로 사용되는 타입>
타입 | 설명 |
<T> | Type (데이터 타입) |
<E> | Element (요소) |
<K> | Key (키) |
<V> | Value (값) |
<N> | Number (숫자) |
※ 타입이 설명과 반드시 일치해야 할 필요는 없지만 일반적으로 사용되는 선언이 보기에 편하다.
Generic의 장점
- 제네릭을 사용하면 잘못된 타입이 들어올 수 있는 것을 컴파일 단계에서 방지할 수 있다.
=안정성이 높아진다. - 클래스 외부에서 타입을 지정 해주기 때문에 따로 타입을 체크하고 변환해줄 필요가 없다.
=관리하기가 편하다. - 비슷한 기능을 지원하는 경우 코드의 재사용성이 높아진다.
Generic 사용방법
1. 클래스 및 인터페이스 선언
타입 파라미터로 명시할 수 있는 것은 참조타입(R타입)만 사용할 수 있다.
(int, double, char... 등의 기본타입(P타입)은 사용할 수 없다.)
때문에 기본타입의 경우 Integer, Double, Character... 등의 랩퍼클래스로 사용된다.
public class ClassName <T, K> { ... }
//기본적으로 제네릭타입의 클래스나 인터페이스의 경우 위와같이 선언한다.
//선언된 <T, K>의 범위는 { ... } 까지이다.
public class Main {
public static void main(String[] args) {
ClassName<String, Integer> a = new ClassName<String, Integer>();
//a객체의 ClassName의 T는 String, K는 Integer가 된다.
}
}
2. 제네릭 클래스
외부 클래스에서 제네릭 클래스를 생성할 때 괄호< > 안의 타입을 파라미터로 보내 제네릭 타입을 지정해 준다.
class ClassName<E> { //제네릭 클래스
private E element; //제네릭 타입 변수 element
void set(E element) { //제네릭 파라미터 메소드
this.element = element;
}
E get() { //제네릭 타입 반환 메소드
return element;
}
}
public class Generic {
public static void main(String[] args) {
ClassName<String> a = new ClassName<String>();
//a객체의 ClassName의 E제네릭타입은 String으로 모두 변환된다
ClassName<Integer> b = new ClassName<Integer>();
//b객체의 ClassName의 E제네릭타입은 Integer로 모두 변환된다
a.set("10");
b.set(10);
System.out.println("a data : " + a.get());
System.out.println("a E Type : " + a.get().getClass().getName());
//반환된 변수의 타입 출력
System.out.println();
System.out.println("b data : " + b.get());
System.out.println("b E Type : " + b.get().getClass().getName());
//반환된 변수의 타입 출력
}
}
- getClass( ).getName( ) : 현재 실행중인 클래스의 클래스명 출력
[실행결과]
a data : 10
a E Type : java.lang.String
b data : 10
b E Type : java.lang.Integer
3. 제네릭 메소드
매개변수 타입과 리턴 타입으로 타입 파라미터를 갖는 메소드
정적 메소드로 선언할 때 사용된다.
클래스에서와는 다르게 반환타입 이전에 제네릭 타입< >을 선언한다.
타입 파라미터를 리턴 타입과 매개변수에 사용한다.
※ 클래스는 인스턴스 앞에 있는 데이터형을 보고 제네릭 타입을 결정하고,
메소드는 뒤에 있는 매개값을 보고 제네릭 타입을 결정한다.
ex)
제네릭 메소드 선언
public <T> T genericMethod(T t) {
...
}
- 접근제어자 <제네릭타입> 리턴타입 메소드명( 제네릭타입 파라미터 ) {
}
제네릭 메소드 호출
T<Integer> T = genericMethod(100);
//매개값이 100이기 때문에 타입 파라미터를 Integer로 추정
T<Integer> T = <Integer> genericMethod(100);
//제네릭타입을 Integer로 지정했기 때문에 타입 파라미터를 Integer로 지정
- 리턴타입 변수 = 메소드명(매개값);
- 리턴타입 변수 = <구체적타입> 메소드명(매개값);
제네릭 클래스에서 사용한 예제에 제네릭 메소드 추가
[실행결과]
a data : 10
a E Type : java.lang.String
b data : 10
b E Type : java.lang.Integer
<T> returnType : java.lang.Integer
<T> returnType : java.lang.String
<T> returnType : ClassName
와일드 카드 <?>
- <? extends a> : 상한 제한 (Upper bound)
a와 a의 자식 타입만 사용 가능
특정 타입만 제한하고 싶을 경우 사용한다. - <? super a> : 하한 제한 (Lower bound)
a와 a의 부모 타입만 사용 가능
해당 객체가 업캐스팅(Up Casting)이 될 필요가 있을 때 사용한다. - <?> : 제한 없음 (Un bound)
모든 타입의 객체 사용 가능
데이터가 아닌 기능의 사용에만 관심이 있는 경우 (어떤 타입으로 리턴받아도 상관 없을 경우) 사용한다.
※ <b extends a>와 <? extends a>는 비슷한 구조지만 차이점이 있다.
- <b extends a>
a와 이를 상속하는 Integer, Short, Double... 등의 타입이 지정될 수 있으며, 객체 혹은 매소드를 호출할 경우
b는 지정된 타입으로 변환이 된다. - <? extends a>
a와 이를 상속하는 Integer, Short, Double... 등의 타입이 지정될 수 있지만, 객체 혹은 메소드를 호출할 경우
지정되는 타입이 없어 타입 참조를 할 수 없다.
다음 그림과 같이 서로 다른 클래스들이 상속관계를 갖고 있다고 가정했을 경우
extends
<T extends B> // B와 C타입만 올 수 있음
<T extends E> // E타입만 올 수 있음
<T extends A> // A, B, C, D, E 타입이 올 수 있음
<? extends B> // B와 C타입만 올 수 있음
<? extends E> // E타입만 올 수 있음
<? extends A> // A, B, C, D, E 타입이 올 수 있음
- 뒤에 오는 타입이 최상위 타입으로 한계가 정해진다.
super
<K super B> // B와 A타입만 올 수 있음
<K super E> // E, D, A타입만 올 수 있음
<K super A> // A타입만 올 수 있음
<? super B> // B와 A타입만 올 수 있음
<? super E> // E, D, A타입만 올 수 있음
<? super A> // A타입만 올 수 있음
- 뒤에 오는 타입이 최하위 타입으로 한계가 정해진다
<?>
<?> //A, B, C, D, E 타입이 올 수 있음
- <? extends Object>와 동일하다
'JAVA' 카테고리의 다른 글
ArrayList (0) | 2021.07.22 |
---|---|
컬렉션 (Collection) (0) | 2021.07.20 |
랩퍼 (Wrapper) (0) | 2021.07.19 |
메모리 (Memory) (0) | 2021.07.19 |
Enum (0) | 2021.07.19 |