전체 글

전체 글

    32. 제네릭과 가변인수를 함께 쓸 때는 신중하라

    가변인수 메서드와 제네릭은 잘 어우러지지 않는다. 가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다. 그런데 내부로 감춰야 했을 이 배열을 그만 클라이언트에 노출하는 문제가 생겼다. 그 결과 제네릭이나 매개변수화 타입이 포홤되면 알기 어려운 컴파일 경고가 발생한다. 메서드를 선언할 때 실체화 불가 타입으로 매개변수를 선언하면 컴파일러가 경고를 보낸다. 가변인수 메서드를 호출할 때도 가변 매개변수가 실체화 불가 타입으로 추론되면 경고가 발생하고, 이 매개변수화 타입의 변수가 타입이 다른 객체를 참조하면 힙 오염이 발생한다. 이런 상황에서는 컴파일러가 자동 생성한 형변환이 실패할 수 있으니, 제네릭 타입 시스템이 약속한 타입 안정성의 근간이 흔들려버린다. 메서드에 제네릭 가변인..

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

    아이템 28에서 이야기했듯 매개변수화 타입은 불공변이다. 서로 다른 타입 A와 B가 있을 때 List는 List의 하위타입도 상위타입도 아니다. List은 List의 하위 타입이 아니라는 뜻이다. 때로는 유연성을 추구해야 할 때가 있다. public class Stack{ .... } 위와 같은 제네릭 클래스가 있다. 이 Stack에 데이터를 넣기 위해서 Iterable를 제공해야 할때, E의 타입의 하위 타입도 넣을 수 있어야 한다. public void push(Iterable list, int i, int j); 어떤 선언이 더 나을까? 더 나은 이유는 무엇일까? public API라면 간단한 두 번째가 낫다. 어떤 리스트든 이 메서드에 넘기면 명시한 인덱스의 원소들을 교환해 줄 것이다. 신경 써야..

    29,30. 이왕이면 제네릭 타입으로 만들라

    Object 배열 기반 스택이다. 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]; } public void push(Object e) { ensureCapacity(); elements[size++] = e; } public Object pop() { if (size == 0 throw new EmptyStackException(); Object result = elemtns[--size]; elements[siz..

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

    배열과 제네릭 타입에는 중요한 차이가 두가지 있다. a가 b의 하위타입이라면 배열 a[]는 배열 b[]의 하위 타입이 된다. ( 공변 ) 그러나 제네릭은 불 공변이라 List는 List와 하위 타입도 아니고 상위 타입도 아니다. 이것만 보면 제네릭에 문제가 있는 것 같지만, 사실 문제가 있는 것은 배열 쪽이다. 다음은 문법상 허용되는 코드다. Object[] objectArray = new Long[1]; objectArray[0] = "타입"; // 런타임에 실패, ArrayStoreException 발생 하지만 다음 코드는 문법에 맞지 않는다. List ol = new ArrayList(); ol.add("타입"); // 컴파일 불가, 호환되지 않는 타입 어느쪽이든 Long 저장소에 String을 넣..

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

    제네릭을 사용하기 시작하면 수많은 컴파일러 경고가 생긴다. 비검사 경고는 컴파일러가 타입 안정성에 대한 정보를 충분히 확인하지 못했을 때 발생한다. 할 수 있는 한 모든 비검사 경고를 제거해야 한다. 비검사 경고는 런타임에 ClassCastException을 일으킬 수 있는 잠재적 가능성을 뜻한다. 타입 캐스팅으로 인해 런타임에 exception이 발생하면 경우에 따라 많은 소스를 훑어봐야 할 수도 있다..( 극 고통 발생 ) 경고를 제거할 수는 없지만 타입이 안전하다고 확실할 수 있다면 @SuppressWarnings("unchecked") 애너테이션을 달아 경고를 숨길 수 있다. 하지만 너무 넓은 범위에 해당 어노테이션을 사용하면 어노테이션으로 인해 문제가 숨겨질 수 있으니 가능한한 좁은 범위에 사용..

    26. 로 타입을 사용하지 말라 ( 제네릭, 타입 안정성 )

    제네릴은 자바 5부터 사용할 수 있다. 제네릭을 지원하기 전에는 컬렉션에서 객체를 꺼낼 때 마다 캐스팅을 해야 했다. 실수로 인한 캐스팅 오류가 런타임에 자주 나타났다. 제네릭을 사용하면 켈렉션이 담을 수 있는 타입을 컴파일러에 알려주게 된다. 때문에 타입이 달라서 발생하는 캐스팅 오류를 차단할 수 있고 안정성을 높일 수 있다. 용어. 캘래스의 인ㅌ페이스 선언에 타입 매개변수가 사용되면 이를 제네릭 클래스 혹은 제네릭 인터페이스라 부른다. List 인터페이스는 원소의 타입을 나타내는 타입 매개변수 E를 받는다. 그래서 인터페이스의 완전한 이름은 List이다. 제네릭 클래스와 제네릭 인터페이스를 통들어 제네릭 타입이라 한다. 각각의 제네릭 타입은 일련의 매개변수화 타입을 정의한다. 에 들어가는 타입이 셀제..

    25. 톱레벨 클래스는 한 파일에 하나만 담으라

    소스 파일 하나에 톱레벨 클래스를 여러 개 선언하더라도 자바 컴파일러는 불평하지 않는다. 하지만 아무런 득이 없을 뿐더러 심각한 위험을 감수해야 한다. 한 클래스를 여러 가지로 정의할 수 있고, 그중 어느 것을 사용할지는 어느 소스 파일을 먼저 컴파일하냐에 따라 달라지기 때문이다. // Utensil.java class Utensil { static final String NAME = "pan"; } class Dessert { static final String NAME = "cake"; } Utensil.java 파일에 정의 된 두개의 클래스의 예제이다. 그 다음 Dessert.java 파일에 같은 두 클래스를 담은 클래스를 만들었다. // Dessert.java class Utensil { stat..

    24. 멤버 클래스는 되도록 static으로 만들라 ( 멤버 클래스,어댑터 패턴 )

    중첩 클래스란 다른 클래스 안에 정의된 클래스를 말한다. 중첩 클래스는 자신을 감싼 바깥 클래스에서만 쓰여야 하며, 그 외의 쓰임새가 있다면 톱레벨 클래스로 만들어야 한다. 중첩 클래스는 정적 멤버, 비정적 멤버, 익명, 지역 클래스 네가지이다. 이 중 첫번째를 제외한 나머지는 내부 클래스에 해당한다. 정적 멤버 클래스 - 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스 비정적 멤버 클래스 - 바깥 클래스의 인스턴스와 암묵적으로 연결된다. - 어댑터를 정의할 때 자주 쓰인다. - 멤버 클래스에서 바깥 인스턴스를 참조할 필요가 없다면 무조건 정적 멤버 클래스로 만들자. 익명 클래스 - 바깥 클래스의 멤버가 아니며, 쓰이는 시점과 동시에 인스턴스가 만들어진다. - 비정적인 문맥에서 사용될 때..

    23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라 ( 클래스 계층구조 )

    태그 달린 클래스란 두가지 이상의 의미를 표현할 때 그 중 현재 표현하는 의미를 태그값으로 알려주는 클래스를 말한다. 태그 달린 클래스에는 여러 단점이 있다. public class Figure { enum Shape {RECTANGLE, CIRCLE} final Shape shape; // 태그필드 - 현재모양을 나타낸다. double length; double width; // 필드 모양이 사각형(RECTANGLE)일때만 쓰인다. double radius; // 필드 모양이 원(CIRCLE)일때만 쓰인다. public Figure(final double radius) { shape = Shape.CIRCLE; this.radius = radius; } // 원 생성자 public Figure(fina..

    22. 인터페이스는 타입을 정의하는 용도로만 사용하라 ( 상수 공개 )

    인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 한다. 달리 말하면 클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스를 무엇을 할 수 있는지를 클라이언트에게 애기해주는 것이다. 인터페이스는 반드시 위 용도로만 사용이 되어야 한다. 상수 인터페이스는 메서드없이 상수를 뜻하는 static final 필드로만 구성된 인터페이스를 말한다. 이 상수들을 사용하려는 클래스에서는 정규화된 이름을 쓰는 걸 피하고자 그 인터페이스를 구현하곤 한다. public interface Test { public static final String TEST_1 = "테스트1"; public static final String TEST_2 = "테스트2"; } 상수 인터페이스 안티패턴으로 인터페이..