자바 기초
제네릭
YJH3968
2021. 4. 18. 16:22
728x90
1. 제네릭의 개요와 특징
- 제네릭(Generic) : 일반화, 범용화라는 뜻으로, 클래스나 method에서 사용하는 필드나 매개 변수의 자료형(클래스형)을 호출하는 쪽에서 지정하도록 하는 기능
- 제네릭을 이용하면 코드 중복이 방지되고 프로그램 작성이 유연해진다. 또한 컴파일 중 타입 검증을 강화해 프로그램 안정성을 높이거나 불필요한 형변환을 제거하는 목적으로도 사용한다.
- 컬렉션 프레임워크는 배열과 다형성을 내부적으로 적절히 활용해 좀 더 효율적으로 다량의 객체를 관리할 수 있게 해 준다.
- 컬렉션 프레임워크에 제네릭을 사용해 컴파일 중 타입을 확실하게 확인해 줄 뿐만 아니라 형변환도 필요없게 된다.
// 컬렉션 프레임워크와 제네릭
컬렉션프레임워크클래스<타입> 객체이름 = new 컬렉션프레임워크클래스생성자<타입>();
// 예시
list<A> list = new ArrayLis<A>();
// 선언하면서 타입을 지정하면 생성자에서 타입은 생략할 수 있다.
// 단, <> 형태는 유지해야 제네릭화가 된다.
list<A> list = new ArrayList<>();
- 클래스를 선언하는 중 제네릭을 사용하는 방법과 method에 제네릭을 사용하는 방법은 다음과 같다.
접근제한자 지정예약어 class 클래스이름<키/제한/와일드카드> extends 상위클래스 implements 상위인터페이스들
{ 내용부(키/제한/와일드카드 등을 사용해 표현) }
// 예시
public class myClass<T, T2 extends String, T3> extends Object implements Serializable {
private T myField_01;
private T2 myField_02;
private T3 myField_03;
}
접근제한자 지정예약어 <키/제한/와일드카드> 결과형반환값(키/제한/와일드카드포함)
method이름(자료형(키/제한/와일드카드포함) 매개변수들) throws 예외클래스들 { 내용부 }
// 예시
public static <T, N extends Number> N myMethod(T info, final N param) {
return param;
}
2. 제네릭 타입 클래스
- 제네릭 타입 클래스는 클래스의 객체를 생성할 때 매개변수를 사용해 특정 타입을 전달한다. 이를 통해 제네릭 타입 클래스에서는 이를 기반으로 필드와 method가 다시 정의된다. 즉, 클래스형만 다르고 코드는 동일한 형태의 클래스를 여러 번 반복적으로 만들 필요가 없어지게 된다.
// 제네릭 타입 클래스
접근제한자 지정예약어 class 클래스이름<타입매개변수들> extends 상위클래스 implements 상위인터페이스들 {
타입 매개변수를 사용하는 내용부;
}
- 타입 매개 변수의 표기
- E : Element의 약자로 컬렉션 프레임워크를 사용할 때 각 객체를 지칭하는 의미로 사용한다.
- K : Key의 약자로 키와 값이라는 쌍으로 이루어진 형태에서 키의 의미로 사용한다.
- V : Value의 약자로 키와 값이라는 쌍으로 이루어진 형태에서 값의 의미로 사용한다.
- T : Type의 약자로 자료형이나 클래스형의 의미로 사용한다. 제네릭에는 기본 자료형을 타입으로 사용할 수 없어서 여기서 자료형은 wrapper 클래스를 의미한다.
- N : Number의 약자로 수치 계열의 의미로 사용한다.
- Wrapper 클래스
- 다형성을 목적으로 구현한 method의 매개변수로 기본 자료형을 전달할 방법이 필요해서 개발되었다.
- 보통은 이를 위해 기본 자료형을 Boxing해 wrapper 클래스로 만들고 사용한 뒤 Unboxing해서 다시 기본 자료형으로 바꾸는 과정을 거치나 Java SE 1.5 version부터는 자동 변환이 가능하다.
3. 제네릭 타입 method
- method를 호출할 때 method 이름 앞에 클래스형들을 명시적으로 작성해도 되지만 일반적으로는 컴파일러가 필요한 타입을 추론하기 때문에 생략한다. 다만 타입 제한이나 와일드카드 타입 등을 사용하면 명시적으로 타입을 지정해야 한다.
// method 호출 시
클래스나객체.<클래스형들>method이름(매개변수들);
- method 오버로딩을 사용할 때 컬렉션 프레임워크의 클래스를 매개변수로 사용할 때 컬렉션 프레임워크의 제네릭 표현은 컴파일 중 타입 매개 변수가 사라져 이를 주의해야 한다.
4. 제네릭 타입의 범위 지정
- 제네릭 타입을 특정 범위로 한정해서 타입을 받으려면 extends를 타입 매개 변수 선언에 사용한다. 이 때 상속하려는 게 인터페이스여도 extends를 사용한다.
- 클래스 이름은 한 번만 사용 가능하고 인터페이스는 여러 번 사용 가능하다.
// 제네릭 타입의 제한
<타입매개변수 extends 클래스이름 & 인터페이스이름 & 인터페이스이름 ... >
- 제네릭에서 각 객체의 값을 비교하는 경우 컴파일 중에 타입 매개 변수 자체가 제거되기 때문에 값을 숫자로 인식하지 못하는 경우가 발생한다. 이를 위해 <, >, == 대신 Comparable 인터페이스를 사용한다.
- 위에서 언급한 타입의 제한은 특정 클래스의 하위 클래스로 제한하는 경우였고, 반대로 특정 클래스의 상위 클래스형만 전달받도록 제한할 수도 있다.
// 제네릭 타입의 제한2
<? super 클래스나인터페이스나타입매개변수>
- 위 코드에서 ?은 와일드카드 타입이다. 와일드카드는 Any를 의미한다.
- super 뒤에는 클래스나 인터페이스, 타입 매개 변수 중 하나를 쓸 수 있다.
- 이는 보통 상속 관계가 있는 클래스를 매개 변수에서 상위 클래스도 같이 사용하고 싶을 때 중복 코드를 최소화하기 위해 사용하거나 컬렉션 프레임워크에서 상속 관계가 있는 클래스를 제네릭화할 때 사용한다.
5. 제네릭 와일드카드 타입
// Upper Bounded 와일드카드
<? extends 클래스나인터페이스나타입매개변수>
// Lower Bounded 와일드카드
<? super 클래스나인터페이스나타입매개변수>
// Unbounded 와일드카드
<?>
6. 제네릭의 한계
- 기본 자료형을 제네릭 타입 클래스의 타입 매개 변수로 전달하여 객체를 생성할 수 없다.
- 타입 매개 변수로 객체를 생성할 수 없다. 객체를 생성하고 싶은 경우 타입을 클래스형으로 넘겨받아 객체를 생성해야 한다.
- 타입 매개 변수로 static 필드를 선언할 수 없다. 타입 매개 변수는 객체를 생성하면서 타입을 결정해줘야 하는데, static 필드는 이 과정을 거치지 않기 때문이다.
- 타입 매개 변수가 다른 클래스는 타입끼리 상속 관계에 있더라도 형변환을 할 수 없다. 즉, 객체를 생성하면서 이미 타입 매개 변수가 결정된 클래스를 형변환하는 것이 불가능하다.
- 제네릭 타입 클래스는 예외 관련 클래스를 상속받을 수 없다.
- 예외 처리의 catch 구문의 클래스로 타입 매개 변수를 사용할 수 없다. 그러나 예외 전가에서는 타입 매개 변수를 사용할 수 있다.
- 컬렉션 프레임워크의 클래스들을 제네릭화하여 사용하는 경우 method 오버로딩 시 타입 매개 변수가 다른 것은 서로 다른 자료형으로 인식하지 않기 때문에 method 오버로딩이 되지 않는다.
// #2
class MyClass <T> {
private T obj = new T(); // 컴파일 오류 발생
}
// 다음과 같이 작성하면 정상적으로 작동한다.
public static <E> void myMethod(Class<E> cls) throws Exception {
E obj = cls.newInstance();
}
// #3
class MyClass<T> {
private static T obj; // 컴파일 오류 발생
}
// #4
MyClass<Integer> myObj_01 = new MyClass<>();
MyClass<Number> myObj_02 = (MyClass<Number>)myObj_01; // 컴파일 오류 발생
// #5
class MyClass<T> extends Exception { ... } // 컴파일 오류 발생
// #6
public static <T extends Exception> void execute() {
try {
// 내용부
} catch(T ex) { ... } // 컴파일 오류
}
// 예외 전가에서는 타입 매개 변수를 사용할 수 있다.
public <T extends Exception> void execute() throws T { ... }
// #7
class MyClass {
public void copy(List<String> src) { ... }
public void copy(List<StringBuilder> src) { ... } // 컴파일 오류 발생
}
출처 : 헬로 자바 프로그래밍(2016)
728x90