Item31. 한정적 와일드카드를 사용해 API 유연성을 높이라

Generic 타입 불공변 특성으로 인한 제약

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
32
33
34
35
public class Stack<E> {

private E[] elements;
private int size = 0;
private static final int DEFAULT_INITIAL_CAPACITY =16;

public Stack(){
elements = (E[]) new Object[DEFAULT_INITIAL_CAPACITY];
}

public void push(E e){
ensureCapacity();
elements[size++] =e;
}

public E pop(){
if(size ==0 ){
throw new EmptyStackException();
}
E result = elements[--size];
elements[size] =null;
return result;
}

public boolean isEmpty(){
return size == 0;
}

private void ensureCapacity(){
if(elements.length == size){
elements = Arrays.copyOf(elements,2*size+1);
}
}
}

Generic type Stack에서 pushAll method를 추가한다고 가정하자

Read more

Item30. raw type 보다는 Generic method로 만들어라

Generic Method

아래와 같이 raw type을 받는 method는 타입 안전하지 않다.

1
2
3
4
5
public static Set union(Set s1, Set s2){
Set result = new HashSet<>(s1);
result.addAll(s2);
return result;
}

아래와 같이 다른 타입의 Generic이 와도 어떠한 compile error도 발생하지 않고 runtime exception이 터진다.

1
2
3
Set<String> strings = Set.of("a", "b", "c");
Set<Integer> integers = Set.of(1, 2, 3);
Set<String> unionResult = union(strings, integers);

method parameter와 반환타입을 다음과 같이 Generic method로 변경함으로 타입 안전하게 만들 수 있다.

1
2
3
4
5
public static <E> Set<E> union(Set<E> s1,Set<E> s2){
Set<E> result = new HashSet<>(s1);
result.addAll(s2);
return result;
}
Read more

Item29. 이왕이면 Generic Type으로 만들어라

Generic Type 변환

Stack Generic Type으로 변경할 경우 , Client에서 Stack에서 꺼낸 객체를 형변환할 필요가 없으며 동시에 ClassCastException 이 나지 않는 장점을 갖는다.

Item 28 에서는 Generic 사용시 배열보다 리스트를 고려하라고 권고는 하였으나 Generic type 안에서 List를 사용하는게 항상 가능하지도 않고, Java가 기본 타입으로 List를 제공하지 않으므로 성능 향상 목적으로 기본 타입인 배열을 사용하기도 한다.

Read more

Item28. 배열보다는 리스트를 사용하라

배열과 Generic type의 차이점

  1. 배열은 공변(covariant) 인 반면 Generic은 불공변(invariant) 이다.

Sub class가 Super class의 하위 class라고 가정하면 배열 Sub[]는 Super[]의 하위 타입이 된다.
반면에 서로 다른 타입인 Type1과 Type2가 있을떄 Generic인 List<Type1> List<Type2> 은 아무 계층관계도 가지지 않는다.

1
2
3
4
5
6
7
// Array 
Object[] longs = new Long[1];
longs[0] = "타입이 다름에도 컴파일에러가 나지 않습니다."; //ArrayStoreException

// Generic
List<Object> ol = new ArrayList<Long>(); // compile Error

배열은 type으로 인해 runtime Exception이 발생할 수 있는 반면, generic인 compile 경고를 먼저 띄워준다.

  1. 배열은 실체화 된다.

배열은 runtime 에도 원소의 타입을 인지하고 확인한다, 예를 들면 Long 배열에 String을 넣으려고 하면 runtime에 이를 확인하고 예외를 발생시킨다.
반면 generic은 runtime에 타입을 소거한다 (type erasure). 원소의 타입을 compile time에만 검사하며 , runtime에는 모른다.

Read more

Item27. 비검사 경고를 제거하라

@SuppressWarnings

compiler 경고를 제거할수는 없지만 타입 안전하다고 확신할 수 있다면 @SuppressWarning(“unchecked”) annotation을 달아 경고를 숨길 수 있다, 단 rumtime에 ClassCastException이 발생하지 않도록 타입 안전함을 검증해야 한다.

Read more

Item26. Generic Raw Type은 사용을 자제하라

Generic Type

클래스와 인터페이스 선언에 타입 매개변수(type parameter)가 쓰이면 이를 generic class 또는 generic interface라 한다. 이 두 개를 통칭해서 Generic type이라고 부른다.

1
public class GenericExample<T>{} // T : 타입 매개변수 

각각의 generic type은 매개변수화 타입 (parameterized type)을 정의한다. 매개변수화타입은 <> 괄호 안에 실제 타입을 명시함으로서 정의가 가능하다.

1
GenericExample<String> stringGenericExample = new GenericExample<>();

raw tye은 generic type을 하나 정의하면 함께 정의되는데, generic type에서 타입 매개변수를 전혀 사용하지 않았을떄를 말한다. , raw type이 generic type을 하나 정의할떄마다 같이 생성되는 이유는 기존 java code와 호환성 문제떄문이다.

1
GenericExample rawTypeGeneric = new GenericExample();
Read more

Item25. top level class는 한 파일에 하나만 담으라

소스 파일 하나당 top-level class를 여러개 선언해도 compile error는 나지 않는다,

1
2
3
4
5
6
7
8
9
// Utensil.java 
class Utensil {

static final String NAME = "pan";
}

class Dessert{
static final String NAME = "cake";
}
1
2
3
4
5
6
7
8
class Dessert {

static final String NAME = "pie";
}

class Utensil {
static final String NAME = "pot";
}

위와 같이 하나의 java file에 여러개의 class가 들어있는 경우 문제가 생길수도 있다.

Read more

Item23. 태그 달린 class보다는 class 계층 구조를 활용하자

태그 달린 class

헌재 표현하는 의미를 태그 값으로 알려주는 class 는 아래와 같이 class 계층구조를 사용하지 않고, 한 class내에 태그 필드를 가지는 class를 말한다.

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
32
33
34
35
36
37
public class Figure {

enum Shape {RECTANGLE,CIRCLE};
// 현재 모양
final Shape shape;

// RECTANGLE일떄만 쓰이는 필드
double length;
double width;

// CIRCLE일떄만 쓰이는 필드
double radius;

// CIRCLE용 생성자
public Figure(double radius) {
this.shape = Shape.CIRCLE;
this.radius = radius;
}

// RECTANGLE용 생성자
public Figure(double length, double width) {
this.shape = Shape.RECTANGLE;
this.length = length;
this.width = width;
}

double area(){
switch (shape){
case RECTANGLE:
return length * width;
case CIRCLE:
return Math.PI * (radius * radius);
default:
throw new AssertionError(shape);
}
}
}
  • 태그 달린 class의 단점

  1. 태그 필드를 가지고, 분기처리 (swith,if-else)를 하기 때문에 코드 가독성이 떨어지고, 계층형 구조로 가져갔을때 불필요한 코드들이 생긴다.

  2. 태그 값마다 필요한 필드가 다른데, 이를 모두 하나의 class에 보관함으로, 객체를 생성하였을떄 사용되지 않는 필드 (메모리 낭비) 도 같이 초기화 된다.

  3. 변경에 열려있다, 태그 값 추가시마다 분기로직을 수정해야 한다.

  4. 인스턴스 타입을 보고 현재 타입이 나타내는 의미를 알 수가 없다.

Read more

Item22. 인터페이스는 타입을 정의하는 용도로만 사용하라

인터페이스의 사용 용도

인터페이스는 자신을 구현한 class의 instance를 참조할 수 있는 타입 역할을 한다.

즉 class가 어떤 인터페이스를 구현한다는 것은 자신의 인터페이스로 무엇을 할수 있을 지 client에게 얘기해주는 것이며, 인터페이스는 오직 이 용도로만 사용해야 한다.

Read more