제네릭 Generic
- 데이터 타입을 일반화하는 것
- 클래스나 메소드에서 사용할 내부 데이터 타입을 컴파일 시에 미리 지정한다.
→ 컴파일 시에 type check를 한다.
→ 객체의 타입을 컴파일 시에 체크하기 때문에 객체의 타입 안정성을 높이고 형변환의 번거로움이 줄어든다.
Java 5 이전
- 여러 타입을 사용하는 대부분의 클래스나 메소드에서 인수나 반환값으로 Object 타입을 사용했었다.
- Object로 반환된 타입에 대해 다시 원하는 타입으로 타입을 변환해야 한다. → 오류 발생 가능성 ↑
- (컬렉션에서 객체를 검색하거나 제거할 때마다 개발자들이 명시적으로 형변환을 해줘야 했다.)
import java.util.ArrayList;
public class Main {
static class Temp{
Object field;
public Temp(Object field) {
this.field = field;
}
public Object getField() {
return field;
}
}
public static void main(String[] args) {
Temp temp = new Temp("String");
// 형변환 필요
String str = (String) temp.getField();
}
}
제네릭 사용
public class Main {
public static class Temp<T>{
T field;
public Temp(T field) {
this.field = field;
}
public T getField() {
return field;
}
}
public static void main(String[] args) {
Temp<String> temp = new Temp<>("String");
// 형변환 필요 없음
String str = temp.getField();
}
}
Generic 특징
타입 안정성을 높인다.
- 의도하지 않은 타입의 객체를 저장하는 것을 막는다.
- 저장된 객체를 꺼낼 때에도 원래의 타입과 다른 타입으로 형변환되는 오류를 줄인다.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
ArrayList<Integer> number = new ArrayList<>();
number.add(1);
number.add(2);
// number.add("1"); // Integer만 들어갈 수 있음
// 형변환 필요 없음
int num = number.get(0);
ArrayList<String> str = new ArrayList<>();
str.add("a");
str.add("b");
// 형변환 필요 없음
String s = str.get(0);
}
}
- 의도하지 않은 타입( = String)의 객체를 저장하는 것을 막는다.
- number에서 꺼낸 타입은 Integer, str에서 꺼낸 타입은 String
제네릭을 사용하는 이유
타입 안정성
- 제네릭을 사용하면 컴파일 시간에 타입 체크를 수행하여, 잘못된 타입의 객체가 사용되는 것을 방지할 수 있다.
- 런타임에 ClassCastException 발생하는 것을 줄일 수 있다.
코드 재사용
- 하나의 클래스나 메서드를 다양한 타입에 대해 동작하도록 만들 수 있다.
코드 간결성
- 형변환 코드를 작성하지 않아도 된다.
제네릭은 불공변
- List<String>은 List<Object>의 하위 타입이 아니다.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// ArrayList<Object> number = new ArrayList<String>(); 안됨
}
}
* 배열은 공변
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
Object[] obj = new String[5];
obj[0] = 1; // 런타임에 ArrayStoreException 발생
}
}
타입추론
- Java 7부터 제네릭의 타입추론이 가능해졌다.
- 컴파일러가 알맞은 타입을 자동으로 추론할 수 있다.
import java.util.ArrayList;
public class Main {
public static void main(String[] args) {
// Java 7 이전에는 둘 다 Integer를 써줘야 했음
ArrayList<Integer> number = new ArrayList<Integer>();
// 컴파일러가 코드 문맥을 통해
// 선언할 때, String으로 했으니까 생성할 타입 인자를 String으로 추론 가능
ArrayList<String> str = new ArrayList<>();
}
}
- 제네릭 타입 : Temp<T>
- 정규 타입 매개변수 : T
- raw 타입 : Temp
- 매개변수화 타입 : Temp<String>
- 실제 타입 매개변수 : String
정규 타입 매개변수 알파벳은 아무거나 사용해도 되지만 관용적으로 사용하는 알파벳들이 있다.
T : Type의 T
E : Element의 E, List 같은 컬렉션의 요소에 사용
K : Key의 K, Map의 key
V : Value의 V, Map의 value
여러 개의 타입 파라미터가 필요한 경우에는 <> 안에 쉼표로 필요한 만큼 선언하면 된다.
public class Main {
public static class Temp<T, E>{
T field;
E element;
public Temp(T field, E element) {
this.field = field;
this.element = element;
}
public T getField() {
return field;
}
public E getElement(){
return element;
}
}
public static void main(String[] args) {
Temp<String, Integer> temp = new Temp<>("String", 1);
// 형변환 필요 없음
String str = temp.getField();
Integer n = temp.getElement();
}
}
제한된 타입 파라미터
- 타입 파라미터를 사용할 때, ~중에 아무거나를 구현하는 방법
public class Main {
public static class Temp<T extends Number>{
T field;
public Temp(T field) {
this.field = field;
}
public T getField() {
return field;
}
}
}
→ Number 를 구현하고 있는 객체만 T가 될 수 있다.
→ 즉, Number를 포함한 객체 중 아무거나
와일드카드 <?>
- 아무 타입이나 받고 싶을 때 사용하는 방법
- 항상 <> 연산자 안에 있어야 한다.
import java.util.List;
public class Main {
static void printList(List<?> list) {
list.forEach(System.out::println);
}
}
<T>는 반환값, 매개변수 타입, 메서드 코드 중에서 사용 가능하지만
<?> 는 사용 불가능 하다.
public <T> void go(T param){} // 됨
// public void go(? param){} // 안됨
한정적 와일드카드 타입
- 한정적 타입 매개변수와 비슷하게 ~ 중 아무거나를 구현할 수 있는 방법
<? extends T> // T 타입을 포함한 T의 하위 타입 아무거나
<? super E> // E의 상위 타입 아무거나
Type Erasure 타입 소거
- 컴파일러가 제네릭을 확인하여 필요한 곳에 형변환을 넣어준 다음 제네릭 타입을 제거한다.
- 컴파일된 파일(*.class)에는 제네릭 타입이 없다.
→ 제네릭이 도입되기 이전의 소스코드와의 호환성을 유지하기 위해서
→ 타입 파라미터가 바인드 되지 않은 경우에는 Object로 대체된다.
<> 연산자 안에 Primitive Type을 사용하지 못하는 이유
- Type Erasure 때문
- 제네릭 타입이 특정 타입으로 제한되어 있을 경우에는 해당 타입에 맞춰 컴파일 시 타입을 변경하고
- 제한되어 있지 않을 경우에는 Object 타입으로 변경한다.
- Primitive Type은 Object를 상속받고 있지 않기 때문에 사용하지 못한다.
- 기본 타입 자료형을 사용하기 위해서는 Wrapper 클래스를 사용해야 한다.
Wrapper 클래스를 사용할 경우 Boxing, Unboxing으로 구현 자체에 크게 신경쓰지 않아도 된다.
Wrapper 클래스
- Primitive Type 데이터를 객체로 다루기 위해 제공되는 클래스
- Primitive Type 값을 내부에 래핑하여 객체로 만들어준다.
- 각각 Primitive Type에 대응하는 Wrapper 클래스가 있다.
Boxing
- Primitive Type 값을 해당하는 Wrapper 클래스의 인스턴스로 변환하는 것
- ex) int 값을 Integer 객체로 변환하는 것
Unboxing
- Wrapper 클래스의 인스턴스를 다시 해당하는 Primitive Type 값으로 변환하는 것
- ex) Integer 객체의 값을 int 값으로 변환하는 것
Java 5부터 Autoboxing, Auto-unboxing을 지원하여 컴파일러가 자동으로 boxing, unboxing 코드를 생성해준다.
728x90
'Team' 카테고리의 다른 글
[CS 스터디] String vs StringBuffer vs StringBuilder (0) | 2023.09.21 |
---|---|
[CS 스터디] MySQL의 procedure와 scheduler (0) | 2023.09.07 |
[CS 스터디] Spring 의존성 주입 (0) | 2023.08.30 |
[CS 스터디] Annotation (0) | 2023.08.24 |
[CS 스터디] S3에 파일을 업로드 하는 방법 (0) | 2023.08.17 |
댓글