학습할 것 (필수)
- 제네릭 사용법
- 제네릭 주요 개념 (바운디 드 타입, 와일드카드)
- 제네릭 메서드 만들기
- Erasure
제네릭 사용법
제네릭 사용법은 간단하다.
다이아몬드 연산자(<>)에 타입을 정의해주면 끝이다.
하지만 코드를 보다보면 ... 등 여러가지가 존재하는 것을 보았다.
해당 E,T,K ... 등은 무슨 기능이 있는건 아니다.
따라서 원하는 값을 넣어줘도 되긴 하지만
하지만 자바에는 제네릭 명명 규칙이 있는데 다음과 같다.
- E : 요소(element)
- K : 키(key)
- N : 숫자(Number)
- T : 타입(type)
- V : 값(value)
- S, U, V : 2,3,4번째 선언된 타입
해당 규칙은 코드를 이해하는데에 도움을 준다. 따라서 무조건은 아니지만 규칙이 있으니 따라주자.
제네릭 장점
제네릭으로 사용함으로써 개발자가 얻는 이점은 매우 많다.
- 컴파일타임때 타입추론을 통한 타입에러를 사전에 잡을 수 있다.
- 컴파일러가 타입추론을 통한 타입 캐스팅을 자동으로 해준다.
- 코드의 재사용성이 매우 높아진다.
위 3가지의 내용을 하나씩 살펴보자.
컴파일타임때 타입추론을 통한 타입에러를 사전에 잡을 수 있다.
해당 장점은 우리가 자주쓰는 List<E>를 통해 알아 볼 수 있다.
public interface List<E> extends Collection<E> {...}
List를 사용할때 우리는 다이아몬드 연산자(<>) 안에 어떠한 타입을 지정하고 사용한다.
List<String> stringList = new ArrayList<>();// 다이아몬드 연산자에 타입을 지정.
타입을 지정함으로써 컴파일러는 타입을 추론할수 있게된다.
따라서 List 요소는 String 인 객체만 들어올 수 있다.
해당 예제 처럼 String 타입 외 다른 타입이 들어온다면 개발자는 사전에 오류를 잡아낼 수 있다.
만약 타입을 지정하지 않고 로 타입(raw type)으로 사용시 컴파일러는 타입추론을 하지 못한다
따라서 리스트 내부에 어떠한 객체를 넣어도 개발자는 오류가 발생한걸 인지하지 못한다.
public static void main(String[] args) {
List rawTypeList = new ArrayList(); //로 타입 리스트
rawTypeList.add("String");
rawTypeList.add(10);
}
이처럼 컴파일러는 제네릭을 통한 타입추론을 할 수 없기 때문에 해당 코드는 컴파일오류가 나지않는다.
그럼 뭐가 문제야?? 일단은 컴파일은된다는 거자나?? 라고 생각이 들 수 있다.
제네릭을 통한 타입추론이 가능해짐으로써 우리는 리스트를 믿고 사용할 수 있게된다.
즉 안정성을 보장할 수 있게된다는 뜻인데
만약 로 타입 리스트를 사용하게된다면 리스트 내부에 어떠한 객체가 있는지 알 수 없게된다.
이러한 문제점은 안정성이 없고 얼마든지 개발자의 실수로 인해 리스트내부에 다른 객체가 혼합하여 들어갈 수 있다.
컴파일러가 타입추론을 통한 타입 캐스팅을 자동으로 해준다.
해당 장점도 위와 연결되는 내용인데 바로 예제부터 살펴보자.
public static void main(String[] args) {
List rawTypeList = new ArrayList();
rawTypeList.add("String");
rawTypeList.add(10);
for (Object o : rawTypeList) {
System.out.println(o);
}
}
해당 예제는 매우 간단하다. 앞에서 말한것처럼
컴파일러는 제네릭을 보고 타입추론을 한다고 말하였다.
하지만 현재 예제의 리스트는 로 타입 리스트이기 때문에 컴파일러는 타입추론을하지 못한다.
따라서 for-each 문을 사용할때 Object 의 객체를 내보내게 된다.(어떠한 객체가 있는지 모르기 때문에)
이러한 문제점은 만약 리스트 내부에 서로다른 타입의 객체가 들어가 있으면
for (Object o : rawTypeList) {
if(o instanceof String){
System.out.println((String) o);
}
if(o instanceof Integer){
System.out.println(o);
}
}
일일이 비교를통한 타입캐스팅을 해줘야한다.
하지만 제네릭을 사용하게 된다면?
List<String> stringList = new ArrayList<>();
for (String s : stringList) {
System.out.println(s);
}
다음과 같이 타입추론을 통한 자동 타입캐스팅을 컴파일러가 해준다.
앞서 살펴본 장점은 모두 제네릭이 제공해주는 안정성이 얼마나 편한지 알 수 있다.
코드의 재사용성이 매우 높아진다.
해당 내용은 먼저 예제부터 살펴보자.
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>(List.of(1,2,3,4,5));
List<String> stringList = new ArrayList<>(List.of("a","b","c","d","f"));
for (String s : stringList) {
System.out.println(s);
}
for (Integer integer : intList) {
System.out.println(integer);
}
}
해당코드는 단순히 리스트를 순회하면서 콘솔에 찍는 행위만한다.
하지만 타입이 서로 다르기 때문에 어쩔 수 없이 반복하는 코드가 보인다.
해당 문제점은 제네릭 메소드를 만들어서 사용하면 해결 할 수 있다.
public class MainRunner {
static <E> void ListForEach(List<E> list){
for (E e : list) {
System.out.println(e);
}
}
public static void main(String[] args) {
List<Integer> intList = new ArrayList<>(List.of(1,2,3,4,5));
List<String> stringList = new ArrayList<>(List.of("a","b","c","d","f"));
ListForEach(intList);
ListForEach(stringList);
}
}
다음과 같이 타입에 의존하지 않는 메서드를 만들어서 코드를 재활용 할 수 있다.
제네릭 주요 개념 (바운디드 타입, 와일드 카드)
바운디드 타입(Bounded Generics)
간단하게 타입을 제한할 수 있는 제네릭이다.
만약 리스트 안에 Number 의 하위클래스만(자기포함)을 받고 싶다면 다음과 같이 해주면된다.
class List<E extends Number>{
...생략
}
이와 반대로 super 는 하위클래스가 아닌 상위클래스만(자기포함) 받고 싶을때 사용한다
와일드 카드(WildCard)
타입을 신경쓰지 않겠다. 라고 이해하면 될꺼 같다.
void Foo(List<?>list list){} // List 내부의 요소들을 Object 타입으로 취급하게됨
즉 타입을 신경쓰지 않으니 타입캐스팅을 해주지 않는다 (Object 타입으로 취급)
하지만 바운디드 타입과 같이 사용하게된다면?
void Foo(List<? extends Number > list){} // 내부의 요소를 Number 타입으로 취급하게됨
Number 로 취급하게 된다.
와일드카드는 구체적인 데이터 타입이 필요하지 않을때 사용한다.
즉 구체적인 타입보단 각각의 데이터가 가지고 공통적인 부분 즉 추상적인 데이터를 필요할때 사용하는거 같다.
Erasure(소거)
Erasure 란 원소 타입을 컴파일 타임에만 검사하고 런타임에는 해당 정보를 알 수 없는 것이다.
즉 컴파일 타임에만 타입에 대한 제약조건을 적용하고 런타임에는 타입에 대한정보를 소거하는 행위다.
소거는 타입 파라미터가 unbounded 이면 타입파라미터를 object 타입으로 변경한다.
- Example<T> ->unbounded 임으로
- Example<Object>으로 변경
- Example<? extends Number> -> bounded임으로
- Example<Number> 으로 변경
소거는 제네릭을 적용할 수 있는 일반클래스, 인터페이스, 메서드에서만 발생하며.
'Java > live-study' 카테고리의 다른 글
[JAVA] 백기선라이브스터디 15주차 람다식 (1) | 2021.03.05 |
---|---|
백기선라이브스터디 13주차 : I/O (0) | 2021.02.17 |
[Java] 백기선 라이브스터디 12주차: 애노테이션 (0) | 2021.02.03 |
[Java] 백기선 라이브 스터디 11주차 과제: Enum (0) | 2021.01.28 |
[Java]백기선 라이브 스터디 10주차 과제: 멀티쓰레드 프로그래밍 (0) | 2021.01.20 |