티스토리 뷰
728x90
반응형
로 타입은 사용하지 말라
- 로 타입(raw type)이란 제네릭에서 타입 매개변수를 전혀 사용하지 않은 경우를 말합니다.
- 이러한 로 타입은 타입 매개변수가 없기 때문에 컴파일러에서 형변환 코드를 알아서 넣어주지 못하기 때문에 실수로 의도와 다른 타입의 객체를 넣어도 오류가 발생하지 않고 컴파일되고 실행이 됩니다.
private final List users = ...;
🧨 문제가 발생하는 코드
- Foods 클래스의 print 메서드에서 iterator로 순회시 Food 클래스로 형변환시 예외가 발생하게 됩니다.
public class Foods {
private final List foods = new ArrayList();
public void add(Object o) {
foods.add(o);
}
public void print() {
Iterator iterator = foods.iterator();
while (iterator.hasNext()) {
Food food = (Food) iterator.next(); // ClassCastException 발생
System.out.println("food : " + food);
}
}
}
public class Food {
private final String name;
public Food(String name) {
this.name = name;
}
}
public class Weapon {
private final String name;
public Weapon(String name) {
this.name = name;
}
}
public class EffectiveJavaApplication {
public static void main(String[] args) throws Exception {
Foods foods = new Foods();
foods.add(new Food("햄버거"));
foods.add(new Weapon("도끼"));
foods.print();
}
}
💡 제네릭을 사용하여 예외 예방
- 제네릭을 사용하여 Food 객체만 받을 수 있고 기존의 Weapon 객체를 add하게 되면 컴파일에서 인지해서 에러라는 것을 표시해주게 됩니다. 즉, 로 타입을 쓰게 되면 제네릭의 안전성과 표현력을 모두 포기한다는 의미입니다.
public class Foods {
private final List<Food> foods = new ArrayList();
public void add(Food o) {
foods.add(o);
}
public void print() {
Iterator iterator = foods.iterator();
while (iterator.hasNext()) {
Food food = (Food) iterator.next();
System.out.println("food : " + food);
}
}
}
💡 로 타입이 남아 있는 이유
- 자바가 제네릭을 받아들이기까지 거의 10년이 걸린 탓에 제네릭 없이 짠 코드가 이미 세상을 뒤덮어 버렸습니다. 그래서 기존 코드를 모두 수용하면서 제네릭을 사용하는 새로운 코드와도 맞물려 돌아가게 해야만 했습니다. 그래서 마이그레이션 호환성을 위해서 로 타입을 지원하고 제네릭 구현에는 소거 방식을 사용하기로 했습니다.
💡 List와 List<Object>
- 전자와 같은 로 타입은 위에서도 계속 안된다고 했으나 List<Object>와 같이 임의 객체를 허용하는 매개변수화 타입은 괜찮다고 합니다. 왜 일까요? 로 타입과는 다르게 List<Object>는 컴파일러에게 모든 타입을 허용한다는 것을 명시적으로 전달하는 코드이기 때문입니다. 그리고 로 타입인 List를 매개변수로 받는 메서드는 List<String>과 같은 List도 전달하는데 문제가 없지만 List<Object>를 매개변수로 받는 메서드에서는 넘길 수 없습니다.
- 그 이유는 제네릭의 하위 타입 규칙 때문인데, List<String>은 List의 하위 타입이지만 List<Object>의 하위 타입은 아니기 때문입니다. 그 결과 List와 같은 로 타입을 매개변수로 사용하면 타입 안정성을 잃게 됩니다.
🧨 문제가 발생하는 경우(List)
- 아래 코드를 실행하면 ClassCastException이 발생하게 됩니다. 그 이유는 static add 메서드에서는 전달받은 매개변수 list에 add할 때 타입 구분없이 추가하게 되지만 list.get(1)하는 경우 컴파일러는 자동으로 타입 매개변수로 선언된 String으로 형변환을 시도하게 되고 123은 Integer 타입이기 때문에 형변환에 예외가 발생하는 것입니다.
public class EffectiveJavaApplication {
public static void main(String[] args) throws Exception {
List<String> list = new ArrayList<>();
add(list, "hello");
add(list, 123);
String s = list.get(1);
}
public static void add(List list, Object o) {
list.add(o);
}
}
🧨 문제가 발생하는 경우(List<Object>)
- 아래처럼 변경을 하면 컴파일에러가 발생하게 됩니다.
- java: incompatible types: java.util.List<java.lang.String> cannot be converted to java.util.List<java.lang.Object>
public class EffectiveJavaApplication {
public static void main(String[] args) throws Exception {
List<String> list = new ArrayList<>();
add(list, "hello"); // 컴파일 에러 발생
add(list, 123); // 컴파일 에러 발생
String s = list.get(1);
}
public static void add(List<Object> list, Object o) {
list.add(o);
}
}
💡문제 해결 방법(와일드 카드)
- 제네릭 타입을 쓰고 싶지만 실제 타입 매개변수가 무엇인지 신경쓰고 싶지 않은 경우에 로 타입이 아닌 ?를 사용하면 어떤 타입도 담을 수 있는 범용적인 매개변수화 타입이 됩니다.
public class EffectiveJavaApplication {
public static void main(String[] args) throws Exception {
HashSet<Integer> s1 = new HashSet<>(){{
add(1);
add(2);
add(3);
}};
HashSet<Integer> s2 = new HashSet<>(){{
add(1);
add(4);
add(5);
add(6);
}};
long count = numElementInCommon(s1, s2);
System.out.println(count); // 1
}
public static long numElementInCommon(Set<?> s1, Set<?> s2) {
return s1.stream()
.filter(obj -> s2.contains(obj))
.count();
}
}
💡비한정적 와일드카드 타입은 로 타입에 비해 안전합니다.
- 아무 원소나 넣을 수 있어 타입 불변식을 훼손할 수 있는 로 타입에 비교해서 비한정적 와일드카드 타입에는 null외의 어떤 원소도 넣을 수 없습니다. 그래서 컬렉션의 타입 불변식을 훼손하지 못하게 막았습니다.
💡로 타입을 사용해야 하는 경우
- 로 타입을 사용해야 하는 경우는 바로 class 리터럴에는 로 타입을 사용해야 하는데, 자바 명세에는 class 리터럴에 매개변수화 타입을 사용하지 못하게 했습니다. (배열과 기본타입은 허용)
- 허용되는 경우
- List.class
- String[].class
- int.class
- 허용이 안되는 경우
- List<String>.class
- List<?>.class
- 또 다른 경우는 instanceof 연산자를 사용한 경우 런타임시 제네릭 타입 정보는 지워지기 때문에 instanceof 연산자는 비한정적 와일드카드 타입 이외의 매개변수 타입에는 적용이 불가능합니다. 또한 로 타입이나 비한정적 와일드카드 타입이나 instanceof는 동일하게 동작합니다. 그렇기 때문에 불필요한 코드 작성을 <?>로 하지 않고 로 타입으로 하는게 낫습니다.
- instanceof에서는 로 타입을 사용해서 Set인지 확인을 했다면 내부 코드에서는 Set<?>으로 형변환을 해주는게 좋습니다.
if(o instanceof Set) {
Set<?> s = (Set<?>) o;
}
참고 자료)
https://catsbi.oopy.io/83635cfe-1cab-43f2-a943-56a9efd83fb2
728x90
반응형
'스터디 > 이펙티브 자바' 카테고리의 다른 글
이펙티브 자바 - Item28. 배열보다는 리스트를 사용하라. (0) | 2022.07.18 |
---|---|
이펙티브 자바 - Item27. 비검사 경고를 제거하라. (0) | 2022.07.18 |
이펙티브 자바 - Item24. 멤버 클래스는 되도록 static으로 만들라. (0) | 2022.07.16 |
이펙티브 자바 - Item23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라. (0) | 2022.07.16 |
이펙티브 자바 - Item22. 인터페이스는 타입을 정의하는 용도로만 사용하라. (0) | 2022.07.16 |
공지사항
최근에 올라온 글
최근에 달린 댓글
- Total
- Today
- Yesterday
TAG
- spring boot 엑셀 다운로드
- 자바 백엔드 개발자 추천 도서
- microkernel architecture
- spring boot excel download paging
- @ControllerAdvice
- spring boot redisson 분산락 구현
- JDK Dynamic Proxy와 CGLIB의 차이
- 트랜잭셔널 아웃박스 패턴 스프링 부트 예제
- pipe and filter architecture
- 람다 표현식
- 레이어드 아키텍처란
- 서비스 기반 아키텍처
- redis 대기열 구현
- java ThreadLocal
- transactional outbox pattern
- transactional outbox pattern spring boot
- space based architecture
- polling publisher spring boot
- spring boot excel download oom
- redis sorted set으로 대기열 구현
- redis sorted set
- java userThread와 DaemonThread
- 공간 기반 아키텍처
- spring boot redisson destributed lock
- 트랜잭셔널 아웃박스 패턴 스프링부트
- spring boot redisson sorted set
- spring boot redis 대기열 구현
- pipeline architecture
- spring boot poi excel download
- service based architecture
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | 16 | 17 | 18 | 19 | 20 | 21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 30 | 31 |
글 보관함