이종 컨테이너 패턴 (type safe heterogeneous container pattern) 제네릭은 Set, Map 등의 컬렉션과 ThreadLocal, AtomicReference 등의 단일원소 컨테이너도 흔히 쓰인다. 이런 모든 쓰임에서 매개변수화되는 대상은 원소가 아닌 컨테이너 자신이다. 따라서 하나의 컨테이너에서 매개변수화할 수 있는 타입의 수가 제한된다. 예를들어, Set에는 원소의 타입을 뜻하는 단 하나의 타입 매개변수만 있으면 되며, Map 에는 key, value의 타입을 뜻하는 2개만 필요한 식이다. 더 유연한 방식이 필요할때가 있다. 데이터베이스희 행(row)은 임의 개수의 열(column)을 가질 수 있는데, 모두 열을 타입 안전하게 이용할 수 있다면 더 편할 것이다. 여기에 ..
가변인수 메서드 가변인수(varargs) 메서드와 제네릭은 함께 사용이 어렵다. 가변인수는 메서드에 넘기는 인수의 개수를 클라이언트가 조절할 수 있게 해준다. 가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다. 그런데 내부로 감춰야 했을 이 배열을 클라이언트에 노출시키는 문제가 생겼다. 그 결과 varargs 매개변수에 제네릭이나 매개변수화 타입이 포함되면 알기 어려운 컴파일 경고가 발생한다. 실체화 불가 타입은 런타임에는 컴파일 타임보다 타입 관련 정보를 적게 담고 있다. 그리고 거의 모든 제네릭과 매개변수화 타입은 실체화되지 않는다. 메서드를 선언할때 실체화 불가 타입으로 varargs 매개변수를 선언하면 컴파일러가 경고를 보낸다. 가변인수 메서드를 호출할 때도 vara..
불공변 타입 매개변수화 타입은 불공변(invariant)이다. 즉, 서로 다른 타입 Type1, Type2 가 있을때 List은 List의 하위 타입도 상위 타입도 아니다. 예를들어, List 은 문자열만 넣을 수 있고, List는 어떤 객체도 넣을 수 있기 때문에 List은 List의 하위타입이 아니다. 이는 List이 List가 하는 일을 제대로 수행하지 못하므로 하위 타입이 될 수 없다는 결론이다. 와일드 카드 타입으로의 변경 Stack 의 public API 추린 코드 public class Stack { public Stack(); public void push (E e); public E pop(); public boolean isEmpty(); } 여기에 일련의 원소를 스택에 넣는 메서드를 ..
제네릭 메서드 생성 클래스와 마찬가지로, 메서드도 제네릭으로 만들 수 있다. 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다. 경고 발생 코드 public static Set union(Set s1, Set s2) { Set result = new HashSet(s1); result.addAll(s2); return result; } 타입 불안정한 코드를 제네릭 메서드로 변경해보자. 제네릭 메서드 public static Set unionNext(Set s1, Set s2) { Set result = new HashSet(s1); result.addAll(s2); return result; } 타입 매개변수 목록은 이고, 반환 타입은 Set 이다. 이 메서드는 경고 없이 컴파일되며, 타입 ..
배열 코드를 제네릭 코드로 변경 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이지만, 제네릭 타입을 새로 만드는 일은 조금 더 어렵다. 제네릭을 사용하지 않은 기본 코드 package com.java.effective.item29; import java.util.Arrays; import java.util.EmptyStackException; public class Stack { private Object[] elements; private int size = 0; private static final int DEFAULT_INITIAL_CAPACITY = 16; public Stack() { elements = new Object[DEFAULT_INITIAL_CAPACITY]; } publi..
배열과 제네릭 배열과 제네릭 타입에는 중요한 차이가 두가지 있다. 배열은 공변이다. 공변이란, 함께 변한다는 뜻이다. Sub 가 Super 의 하위타입일 경우, 배열 Sub[] 는 배열 Super[] 의 하위 타입이 된다. 제네릭은 불공변이다. 즉, 서로 다른 타입 Type1, Type2 가 있을때 List,은 List 의 하위 타입도 아니고 상위 타입도 아니다. 배열 vs 리스트 1) 배열의 경우 Object[] objectArray = new Long[1]; objectArray[0] = "sss"; // 런타임시에 에러가 발생 배열은 런타임 시점에 오류를 알 수 있다. 2) 리스트의 경우 List ol = new ArrayList(); ol.add("sss"); 리스트는 컴파일할 때 바로 알 수 있..
비검사 경고 제네릭을 사용하기 시작하면 수많은 컴파일러 경고를 보게 될 것이다. 비검사 형변환 경고 비검사 메서드 호출 경고 비검사 매개변수화 가변인수 타입 경고 비검사 변환 경고 오류 코드 (경고 발생) Set test = new HashSet(); /** [unckecked] unchecked conversion required : Set found : HashSet */ 수정 코드 Set test = new HashSet(); Java7 부터 지원하는 다이아몬드 연산자()로 해결하였다. 컴파일러가 올바른 실제 타입 매개변수를 추론한다. (Test) 비검사 경고 제거 및 무시 할수있는 한, 모든 비검사 경고를 제거하라. 모두 제거한다면 그 코드는 타입 안정성이 보장된다. 런타임에 ClassCastEx..
제네릭 타입 클래스와 인터페이스 선언에 타입 매개변수가 쓰이면 이를 제네릭 클래스 혹인 제네릭 인터페이스라고 한다. 이를 통틀어 제네릭 타입(generic type)이라고 한다. List 예를들어, List 인터페이스는 원소의 타입을 나타내는 타입 매개변수 E 를 받는다. 각각의 제네릭 타입은 일련의 매개변수화 타입을 정의한다. List 은 원소의 타입이 String 인 리스트를 뜻하는 매개변수화 타입이다. 여기서 String 이 정규(formal) 타입 매개변수 E 에 해당하는 실제(actual) 타입 매개변수다. 로 타입 (Raw Type) 제네릭 타입을 하나 정의하면 그에 딸린 로 타입(raw type)도 함께 정의된다. 로 타입이란, 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다..
톱레벨 클래스 소스 파일 하나에 톱레벨 클래스를 여러개 선언하더라도 자바 컴파일러는 불평하지 않는다. 하지만 아무런 득이 없고 심각한 위험을 감수해야한다. 어느 소스파일을 먼저 컴파일하느냐에 따라 결과가 달라질 수 있는 위험이 있는데, 이를 예제를 통해 알아보자. Utensil.java package com.java.effective.item25; class Utensil { static final String NAME = "pan"; } class Dessert { static final String NAME = "cake"; } Main.java package com.java.effective.item25; public class Main { public static void main(String[]..
중첩 클래스 중첩 클래스(nested class)란 다른 클래스 안에 정의된 클래스를 말한다. 중첩된 클래스는 자신을 감싼 바깥 클래스에서만 쓰여야하며, 그 외의 쓰임새가 있다면 톱레벨 클래스로 만들어야한다. 중첩 클래스의 종류 1) 정적 멤버 클래스 바깥 클래스와 함께 쓰일때만 유용한 public 도우미 클래스 2) (비정적) 멤버 클래스 바깥 클래스의 인스턴스와 암묵적으로 연결된다. 어댑터를 정의할때 자주 쓰인다. 멤버 클래스에서 바깥 인스턴스를 참조할 필요가 없다면 무조건 정적 멤버 클래스로 만들자. 3) 익명 클래스 바깥 클래스의 멤버가 아니며, 쓰이는 시점과 동시에 인스턴스가 만들어진다. 비정적인 문맥에서 사용될 때만 바깥 클래스의 인스턴스를 참조할 수 있다. 자바에서 람다를 지원하기 전에 즉석..
태그 달린 클래스 태그 클래스란, 두가지 이상의 의미를 표현할 때 그 중 현재 표현하는 의미를 태그값으로 알려주는 클래스다. package com.java.effective.item23; class Figure { enum Shape { RECTANGLE, CIRCLE }; // 태그 필드 - 현재 모양을 나타낸다. final Shape shape; // 다음 필드들은 모양이 사각형(RECTANGLE)일 때만 쓰인다. double length; double width; // 다음 필드는 모양이 원(CIRCLE)일 때만 쓰인다. double radius; // 원용 생성자 Figure(double radius) { shape = Shape.CIRCLE; this.radius = radius; } // 사각..
상수 인터페이스 인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 한다. 클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할수 있는지를 클라이언트에 얘기해주는 것이다. 인터페이스는 이 용도로만 사용해야한다. 예외적으로 상수 인터페이스라는 것은 static final 필드로만 가득찬 인터페이스인데, 메서드가 존재하지 않는다. public interface PhysicalConstantsInterface { static final String MESSAGE = "Hello"; static final String ALERT = "World"; } 상수 인터페이스는 클래스에서 정규화된 이름을 사용하는 것을 피하고자 인터페이스에 구현한 것이다. 상수 인터페이스는 잘못 ..