제네릭과 가변 인수를 함께 쓸 때 문제점 가변 인수 메서드(varargs)는 제네릭과 함께 Java5에 함께 추가되었다. 함께 추가되었으니 서로 잘 어우러질 거라 기대하지만 사실을 그렇지 않다. 가변 인수 메서드를 호출하면 자동으로 배열이 만들어진다. 내부로 감췄어야 할 배열을 클라이언트로 노출하게 되면서 문제가 발생한다. 제네릭이나 매개변수화 타입을 포함한 가변 인수 메서드를 호출하면 컴파일러가 경고를 보낸다. 아래의 예시처럼 힙 오염이 일어날 수 있기 때문이다. // 제네릭과 가변인수를 혼용하면 타입 안전성이 깨진다. static void dangerous(List...stringLists){ List intList = List.of(42); Object[] objects = stringLists; ..
함수형 인터페이스(Functional Interface) 함수형 인터페이스는 추상 메서드를 하나만 가진 인터페이스를 말한다. // 함수형 인터페이스! public interface DoSomething { void doAnything(); } Java 8에 새롭게 추가된 default 메서드나 static 메서드가 함께 존재한다고 하더라도 추상 메서드가 하나라면 함수형 인터페이스이다. // 함수형 인터페이스! public interface DoSomething { void doAnything(); default void printDefault() { System.out.println("default"); } static void printStatic() { System.out.println("static..
Item28- 배열보다는 리스트를 사용하라에서도 다뤘듯 매개변수화 타입은 불공변이다. List에는 어떤 객체든 넣을 수 있지만 Lisit은 String만 넣을 수 있다. 즉, List은 List가 하는 일을 제대로 수행하지 못하니 리스코프 치환 원칙에 어긋나고, 따라서 하위 타입이라 볼 수 없다. 이러한 불공변성 때문에 매개변수화 타입은 유연함이 부족하다. 따라서 불공변의 유연함을 극복하려면 한정적 와일드카드 타입을 사용해야 한다. Stack 예제 E 생산자 매개변수에 와일드카드 타입 :
Item29에서 설명한 것과 마찬가지로, 클라이언트에서 입력 매개변수와 반환 값을 명시적으로 형 변환하는 메서드보다 제네릭 메서드가 더 안전하고 사용하기도 쉽다. 이왕이면 제네릭 타입으로 만든 것처럼 메서드도 제네릭 메서드로 만들자. 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다. 예를들면 Collections의 알고리즘 메서드(sort, binarySearch)는 모두 제네릭이다. 제네릭 메서드 작성법 단순한 제네릭 메서드 public static Set union(Set s1, Set s2) { Set result = new HashSet(); result.addAll(s2); return result; } 타입 매개변수 목록은 메서드의 제한자와 반환 타입 사이에 온다. 위와 같은 제..
클라이언트가 직접 형 변환해야 하는 타입보다 제네릭 타입이 더 안전하고 쓰기 편하다. 따라서 새로운 타입을 설계할 때는 형 변환 없이도 사용할 수 있도록 제네릭 타입으로 만들자. 예시 Object 기반 스택을 제네릭 타입으로 변환 public class Stack { private E[] elements; private int size; public Stack() { this.elements = new E[16]; } public void push(E e) { elements[size++] = obj; } public E pop() { if (size == 0) { throw new EmptyStackException(); } E result = elements[--size]; elements[size]..
배열과 제네릭 타입의 가장 큰 차이는 공변과 실체화다. 배열은 공변이며 실체화가 가능하고 제네릭은 불공변이며 실체화가 불가능하다. 배열보다 리스트를 사용해야 하는 첫 번째 이유 개발자라면 누구나 컴파일 타임에 오류를 발견하는 것을 선호한다. 배열은 런타임에야 오류를 발견할 수 있지만, 제네릭은 컴파일 타임에 오류를 발견할 수 있다. 아래의 설명을 통해 왜 그런지 알아보자. 배열은 공변? 공변은 함께 변한다는 뜻이다. Item28에서 말하는 배열의 공변이란 Sub가 Super의 하위 타입이라면 배열 Sub[]는 Super[]의 하위 타입이라는 것을 의미한다. 더 쉽게 이해하기 위해 다음 코드를 살펴보자. // 컴파일에 이상이 없다. Object[] objectArr = new Long[1]; // 공변 L..
제네릭을 사용하기 시작하면 수많은 컴파일러 경고들을 마주치게 된다. 이러한 경고들을 가능한 많이 제거하는 것이 좋다. 경고들을 모두 제거한다면, 그 코드는 타입 안정성이 보장되기 때문이다. 다행히 대부분의 비검사 경고는 쉽게 제거할 수 있다. 대부분의 개발자들이 IDE를 통해 코드를 작성하고 컴파일하는데, IDE는 코드상에 컴파일 경고가 날 수 있는 부분을 컴파일 전에 미리 알려준다. // 비검사 경고 발생 Set some = new HashSet(); // 타입 매개변수 추론이 어렵다. 위의 코드는 가장 간단한 예시로, 자바 7부터 지원하는 다이아몬드 연산자를 추가하면 간단히 경고가 제거된다. Set some = new HashSet(); // 컴파일러가 타입 매개변수를 추론해준다. 경고를 제거할 수 ..
로 타입(Raw Type)은 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다. 예를 들면 List list = new ArrayList(); 와 같은 형태를 가진다. 타입 매개변수? List 에서 에 해당하는 매개변수를 타입 매개변수라 한다. ex) List에서는 String이 타입 매개변수에 해당한다. 로 타입의 문제점 타입 안전성과 표현력을 확보할 수 없다. // 컬렉션의 로 타입 private final Collection stamps = ..... ; // Stamp 인스턴스만 취급한다. // 실수로 Stamp가 아닌 Coin을 넣는다. stamp.add(new Coin(..)) 위의 코드는 아무 오류없이 컴파일되고 실행된다. 컬렉션이 동전을 다시 꺼내기 전까지는 오류를 알아챌 방..
문제점 소스 파일 하나에 여러 개의 톱레벨 클래스를 선언하더라도 컴파일에는 문제가 없다. 하지만 이는 심각한 위험을 감수해야 하는 행위이다. 소스 파일 하나에 여러개의 톱레벨 클래스를 선언함으로 컴파일 에러는 발생하지 않지만 예상치 못한 결과가 나타날 수 있다. 컴파일러에 어느 소스 파일을 먼저 건네느냐에 따라 동작이 달라지기 때문이다. 예시 아래와 같이 Utensil.java 파일에 Utensil과 Dessert 클래스가 함께 존재한다고 가정해보자. // Utensil.java class Utensil { static final String NAME = "pan"; } class Dessert { static final String NAME = "cake"; 그러던 중 우연히 위와 똑같은 클래스를 담은..