이야기박스
Effective java 3/E - 제네릭 (item 26~33) 본문
제네릭은 자바5부터 사용되었습니다.
제네릭 이전에는 컬렉션에서 꺼낼 때마다 형변환 해줬어야 했지만 제네릭은 컴파일러에게 컬렉션이 담을 수 있는 타입을 알려주고 형변환을 컴파일러에게 맡기게 됩니다. 많이 편해졌죠..
Item 26 로 타입은 사용하지 말라
제네릭 클래스, 제네릭 인터페이스
클래스와 인터페이스 선언에 타입 매개변수가 쓰인 것
==> 이를 통틀어 제네릭 타입이라고 부름
제네릭 타입은 일련의 매개변수화 타입(Parameterized type)을 정의함
List<String> ==> String이 매개변수화 타입
raw type이란?
매개변수화 타입이 지정이 안된 것
--> List
이렇게만 쓰인것
로 타입은 제네릭이 지원되기 이전에 사용되던 것, 호환성 때문에 현재도 서비스 중이지만 좋은 방법은 아니다.
이유
- 로 타입은 컴파일은 정상적으로 됨
- 런타임에서 에러가 발생함
단 List<Object>와 같은 것은 괜찮음
이건 제네릭임
Set<?> --> 이건 와일드카드 타입
와일드 카드 타입은 안전함
==> Collection<?>에는 null 이외에는 어떤 원소도 넣을 수 없음
Raw type을 쓰는 예외 상황 2가지
- class 리터럴에는 로 타입 쓸 것
- List.class , String[].class, int.class ==> o
- List<String>.class, List<?>.class ==> x
- instanceof 연산자 관련
- 와일드카드 타입 이외의 매개변수화 타입에는 사용 불가능함
- 로 타입이랑 비한정적 와일드카드 타입 연산자는 완전 동일 동작함 ==> 코드상으로 깔끔한 로타입 쓰는게 나을 것 같음
Item 27 비검사 경고를 제거하라
Item 28 배열보다는리스트를 사용하라
배열과 리스트의 차이
배열 - 런타임에도 자신이 담기로한 원소 타입을 인지하고 검사 (공변, 실체화)
리스트 - 컴파일에만 검사 후, 런타임에는 검사하지 않음 (불공변, 타입 정보 소거)
==> 배열과 리스트를 함께 사용하기 어려움
==> 제네릭 배열을 사용하지 못하게 한 이유 (?)
Item 29 이왕이면 제네릭 타입으로 만들라
이왕이면 클래스를 제네릭 클래스로 하자는 것
Generic --> primitive 타입은 못쓰고 --> 한정적 와일드카드 타입으로 사용할 수 있음
Item 30 이왕이면 제네릭 메서드로 만들라
package no5.generic.item30;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.junit.Test;
/**
* Created by Jin Won Park on 2018-12-05
* E-mail : oringnam@gmail.com
* Blog : http://box0830.tistory.com
* Github : http://github.com/oringnam
*/
public class GenericMethodCase {
/**
* 로 타입을 사용
* 컴파일은 되지만 런타임에서 경고 발생
* @param s1 집합 A
* @param s2 집합 B
* @return 합 집합
*/
public static Set union(Set s1, Set s2) {
Set result = new HashSet(s1);
result.addAll(s2);
return result;
}
/**
* 제네릭 사용
* 컴파일되고 타입도 안전, 사용 쉬움
* s1, s2, return value의 타입이 모두 같아야 함
* @param s1 집합 A
* @param s2 집합 B
* @param <E> 제네릭 타입
* @return 합 집합
*/
public static <E> Set<E> union2(Set<E> s1, Set<E> s2) {
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
/**
* 타입 추론이 일어나서 자동으로 매개변수의 값을 찾아 줌
*/
@Test
public void typeChecker() {
Map<String, List<String>> test = newHashMap();
}
public static <K, V> HashMap<K,V> newHashMap() {
return new HashMap<>();
}
public static <E extends Comparable<E>> E max(Collection<E> c) {
E result = null;
// do something
return result;
}
}
package no5.generic.item30;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;
import org.junit.Test;
/**
* Created by Jin Won Park on 2018-12-05
* E-mail : oringnam@gmail.com
* Blog : http://box0830.tistory.com
* Github : http://github.com/oringnam
*/
public class Identity {
/**
* 자기 자신을 반환하고 싶을 때 사용 함.
*/
private static Function<Object, Object> IDENTITY_FN = (t) -> t;
@SuppressWarnings("unchcekd")
public static <T, R> Function<T, R> identityFunction() {
return (Function<T, R>) IDENTITY_FN;
}
/**
* 사용 예제, 함수를 정의해주면 그걸 쓰고 아니면 있는 그대로 반환해주도록
*/
@Test
public void identityExample() {
final List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
System.err.println("test { 2 times }");
System.out.println(map(numbers, i -> i * 2));
System.err.println("test { null }");
System.out.println(map(numbers, null));
}
private <T, R> List<R> map(final List<T> list, final Function<T, R> mapper) {
final Function<T, R> function;
if (mapper != null) {
function = mapper;
} else {
function = identityFunction();
}
final List<R> result = new ArrayList<>();
for (T t : list) {
result.add(function.apply(t));
}
return result;
}
}
Item 31 한정적 와일드카드를 사용해 API 유연성을 높이라
한정적 와일드카드
<? extends E> ==> 누군가를 상속받았기 때문에 불공변을 어느정도 해소
예시
List<Number> List<Integer> ==> 불공변이기때문에 그냥 쓰면 안되지만 한정적 와일드카드 쓰면 됨
유연성을 극대화하려면 원소의 생산자나 소비자용 입력 매개변수에 와일드카드 타입을 사용할 것
반환 타입에는 한정적 와일드카드 타입을 사용하면 안됨 --> 클라이언트 코드에서도 와일드카드 사용해야 하기 때문
* 와일드카드 타입이 복잡하더라도 잘 쓰면 API가 훨씬 유연해질 수 있음
널리 쓰일 라이브러리를 작성하려면 와일드카드 타입을 적절히 사용해줘야 함
PECS ==> Producer - extends & Consumer - super
* Comparable & Comparator --> 모두 소비자
Item 32 제네릭과 가변인수를 함께 쓸 때는 신중하라
가변인자를 넘기면 --> 배열 생성 --> 클라이언트 노출 --> 제네릭 or 매개변수화 타입 포함되면 컴파일 경고
void dangerous(List<String>... stringLists) {
List<Integer> intList = new ArrayList<>();
intList.add(10);
Object[] objects = stringLists;
// 힙 오염 발생
objects[0] = intList;
// ClassCastException 발생
// --> 컴파일러가 생성하는 형변환이 있음
String s = ((List<String>) stringLists[0]).get(0);
}
@Test
public void dangerousTest() {
dangerous(new ArrayList<>(), new ArrayList<>(), new ArrayList<>());
}
--> 제네릭 varargs 배열 매개변수에 값을 저장하는 것은 안전하지 못함
@SafeVarargs --> 메서드 작성자가 그 메서드가 타입 안전하다고 보장하는 것 --> 클라이언트 측에서 경고 발생 x
매서드의 안전은 어떻게 파악하는가?
- 메서드가 이 배열에 아무것도 저장하지 않음 (매개변수들을 덮어쓰지 않는다)
- 배열의 참조가 밖으로 노출되지 않는다 (신뢰할 수 없는 코드가 배열에 접근할 수 없다)
==> 안전하다
//@SafeVarargs
final <T> T[] toArray(T... args) {
// 가변인자 작동 원리 --> Object[]로 업캐스팅하여 진행
// Object[] objects = args;
return args;
}
<T> T[] pickTwo(T a, T b, T c) {
int num = ThreadLocalRandom.current().nextInt(3);
switch (num) {
case 0: return toArray(a, b);
case 1: return toArray(a, c);
case 2: return toArray(b, c);
}
throw new AssertionError();
}
@Test
public void genericVarargsTest() {
// 컴파일러는 Object[] --> String[] 으로 형변환하는 과정을 자동으로 함
// 그러나, Object[]는 String[]의 하위타입이 아니기 때문에 ClassCastException이 발생생
String[] attributes = pickTwo("aaa", "bbb", "ccc");
}
@Test
public void genericVarargsTest2() {
String[] strings = toArray("aaa", "bbb");
}
테스트 1의 경우 --> pickTwo에서 Generic으로 받기 때문에, Object[] 타입으로 형변환되어 나옴
테스트 2의 경우 --> strings의 타입이 String 배열이기 때문에 자동으로 String[]으로 형변환되어 나옴
Why??
test() --> toArray() ==> String[]
test() --> pickTwo() --> (이 과정에서 차이점?) toArray ==> Object[]
** @SafeVarargs 어노테이션은 제네릭이나 매개변수화 타입의 varargs 매개변수를 받는 모든 메서드에 사용할 것
===> 즉 안전하지 않은 varargs 메서드는 사용하지 말라는 것 !!
복습 !!
- varargs 매개변수 배열에 아무것도 저장하지 않는다
- 그 배열(혹은 복제본)을 신뢰할 수 없는 코드에 노출하지 않는다.
===> @SafeVarargs는 재정의할 수 없는 메서드에만 달아야 함 --> final 붙입시다
Item 33 타입 안전 이종 컨테이너를 고려하라
타입 안전 이종 컨테이너 패턴 (type safe heterogeneous container pattern)
/**
* @author Jin Won Park
* @since 2018-12-07
* E-mail : oringnam@gmail.com
* Blog : http://box0830.tistory.com
* Github : http://github.com/oringnam
*/
@Slf4j
public class Favorites {
private Map<Class<?>, Object> favorites = new HashMap<>();
public <T> void putFavorite(Class<T> type, T instance) {
favorites.put(Objects.requireNonNull(type), type.cast(instance));
}
public <T> T getFavorite(Class<T> type) {
// Object type --> T type으로 변환이 필요함
// Class의 cast 메서드를 사용해 동적 형변환을 이루어 줌
return type.cast(favorites.get(type));
}
@Test
public void typeSafeHeterogeneousContainerPattern() {
Favorites favorites = new Favorites();
favorites.putFavorite(String.class, "Java");
favorites.putFavorite(Integer.class, 0xcafebabe);
favorites.putFavorite(Class.class, Favorites.class);
String favoriteString = favorites.getFavorite(String.class);
int favoriteInteger = favorites.getFavorite(Integer.class);
Class<?> favoriteClass = favorites.getFavorite(Class.class);
log.info("string : {}", favoriteString);
log.info("int : {}", favoriteInteger);
log.info("class : {}", favoriteClass);
}
}
Class.cast(instance) --> 동적 형변환 방법
* 문제점
실체화 불가 타입에는 사용할 수 없음
--> List<String>, List<Integer> 모드 List.class라는 같은 Class 공유
==> 깔끔하게 하기 힘들다
==> 방법 중 하나로 Super Type Token 이라는 것이 있다고 함
요약 !!
제네릭으로 컨테이너를 만드는데는 제약이 있지만
컨테이너 자체가 아닌 키를 타입 매개변수로 바꾸면 제약 없이 타입 안전 이종 컨테이너를 만들 수 있음
참고
'Programming Language > JAVA' 카테고리의 다른 글
Effective java 3/E - 람다 & 스트림 (item 42~48) (0) | 2018.12.20 |
---|---|
Effective java 3/E - 열거타입 & 어노테이션 (item 34~41) (0) | 2018.12.13 |
Effective java 3/E - item 15~25 (0) | 2018.11.29 |
Effective java 3/E - item 10~14 (0) | 2018.11.29 |
Effective java 3/E - item 1~9 (0) | 2018.11.16 |