<태그 달린 클래스는 장황하고 오류를 내기 쉽고 비효율적이다.>
// 1번
abstract class Figure {
abstract double area();
}
// 3번
class Circle extends Figure {
final double radius;
Circle(double radius) {
this.radius = radius;
}
@Override
double area() {
return Math.PI * (radius * radius);
}
}
// 3번
class Rectangle extends Figure {
final double length;
final double width;
Rectangle(double length, double width) {
this.length = length;
this.width = width;
}
@Override
double area() {
return length * width;
}
}
클래스명.this
형태로 바깥 클래스의 이름을 명시하는 용법을 말한다.<멤버 클래스에서 바깥 인스턴스에 접근할 일이 없다면 무조건 static을 붙여서 정적 멤버 클래스로 만들자.>
<컴파일러에 어느 소스 파일을 먼저 건네느냐에 따라 동작이 달라진다.>
톱레벨 클래스들을 서로 다른 소스 파일로 분리한다.
굳이 여러 톱레벨 클래스를 한 파일에 담고 싶다면 정적 멤버 클래스를 사용하는 방법을 고민해볼 수 있다.
public class Main {
public static void main(String[] args) {
System.out.println(Utensi.NAME + Dessert.NAME);
}
private static class Utensil {
static final String NAME = "pan";
}
private static class Dessert {
static final String NAME = "cake";
}
}
List<E>
List<String>
List<E>
의 로 타입은 List
이다.// Stamp 인스턴스만 취급한다.
private final Collection stamps = ...;
로 타입은 제너릭이 도입되기 이전 코드와의 호환성을 위해 제공될 뿐이다.
제너릭을 활용하면 위 예제의 'Stamp 인스턴스만 취급한다.'는 정보가 주석이 아닌 타입 선언 자체에 녹아든다.
private final Collection<Stamp> stamps = ...;
List<Object>
는 사용해도 괜찮다.List<Object>
는 모든 타입을 허용한다는 매개변수화 타입이다.
List
를 받는 메서드에 List<String>
을 넘길 수 있다.List<Object>
를 받는 메서드에는 넘길 수 없다.List<String>
은 로 타입인 List의 하위 타입이지만, List<Object>
의 하위 타입은 아니다.
비한정적 와일드카드 타입을 사용하자!
Set<E>
의 비한정적 와일드카드 타입은 Set<?>
이다.제네릭을 사용하기 시작하면 수많은 컴파일러 경고들을 마주치게 된다.
대부분의 비검사 경고는 쉽게 제거할 수 있다.
Set<Car> cars = new HashSet();
Venery.java:4: warning: [unchecked] unchecked conversion
Set<Car> cars = new HashSet();
^
required: Set<Car>
found: HashSet
-> Set<Car> cars = new HashSet<>();
컴파일러가 알려준 대로 수정하면 경고가 사라진다.
public <T> T[] toArray(T[] a) {
if (a.length < size) {
// 수정 전
return (T[]) Arrays.copyOf(elements, size, a.getClass());
// 수정 후
// 생성한 배열과 매개변수로 받은 배열의 타입이 모두 T[]로 같으므로 올바른 형변환입니다
@SuppressWarnings("unchecked") T[] result =
(T[]) Arrays.copyOf(elements, size, a.getClass());
return result;
}
System.arraycopy(elements, 0, a, 0, size);
if (a.length > size)
a[size] = null;
return a;
}
애너테이션은 선언에만 달 수 있기 때문에 return 문에는 @SuppressWarning을 다는 게 불가능하다.
@SuppressWarnings(“unchecked”) 애너테이션을 사용할 때면 그 경고를 무시해도 안전한 이유를 항상 주석으로 남겨야 한다.
배열은 공변이다.
Sub[]
는 배열 Super[]
의 하위 타입이 된다.반면 제네릭은 불공변이다.
List<Object>
는 List<String>
의 하위 타입도 아니고 상위 타입도 되지 않는다.문제는 배열에 있다!
아래의 코드처럼 배열에서는 오류를 런타임에야 알게 되지만, 리스트를 사용하면 컴파일 시점에 오류를 바로 확인할 수 있다.
Object[] objectArray = new Long[1];
objectArray[0] = "타입이 달라 넣을 수 없다."; // ArrayStoreException 발생
List<Object> ol = new ArrayList<Long>(); // 호환되지 않는 타입이므로 컴파일부터 막힌다.
ol.add("타입이 달라 넣을 수 없다.");
배열은 실체화된다.
제너릭은 실체화되지 않는다.
new List<E>[], new List<String>[], new E[]
// 오류 발생~!배열을 리스트로 대체하자!
배열로 형변환할 때 제네릭 배열 생성 오류나 비검사 형변환 경고가 뜨는 경우 대부분은 배열인 E[]
대신에 컬렉션인 List<E>
를 사용하면 해결된다.
public class Chooser {
private final Object[] choiceArray;
public Chooser(Collection choices) {
this.choiceArray = choices.toArray();
}
// 이 메서드를 호출할 때마다 반환된 Object를 형변환해야 한다.
public Object choose() {
Random rnd = ThreadLocalRandom.current();
return choiceArray[rnd.nextInt(choiceArray.length)];
}
}
class Chooser<T> {
private final List<T> choiceList;
public Chooser(Collection<T> choices) {
this.choiceList = new ArrayList<>(choices);
}
public T choose() {
Random rnd = ThreadLocalRandom.current();
return choiceList.get(rnd.nextInt(choiceList.size()));
}
}
런타임에 형변환 오류를 발생시키지 않는다.