Book 30

[Effective Java]30.이왕이면 제네릭 메서드로 만들라

클래스와 마찬가지로, 메서드도 제네릭으로 만들 수 있다. 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다. 예컨대 Collections의 '알고리즘' 메서드(binarySearch, sort 등)는 모두 제네릭이다. 제네릭 메서드 작성법은 제네릭 타입 작성법과 비슷하다. 단순한 제네릭 메서드라면 아래와 같이 작성할 수 있다. // 코드 30-2 제네릭 메서드 (177쪽) public static Set union(Set s1, Set s2) { Set result = new HashSet(s1); result.addAll(s2); return result; } // 코드 30-3 제네릭 메서드를 활용하는 간단한 프로그램 (177쪽) public static void main(String[]..

Book/Effective Java 2022.07.07

[Effective Java]29.이왕이면 제네릭 타입으로 만들라

아이템 7에서 다룬 Object[]를 사용하는 단순한 스택 코드가 있다. 이 클래스는 원래 제네릭 타입이어야 마땅하다. 일반 클래스를 제네릭 클래스로 만드는 첫 단계는 클래스 선언에 타입 매개변수를 추가하는 일이다. 배열을 사용하는 코드를 제네릭으로 만들려 할 때는 E[]로 만들 수 없다. 적절한 해결책은 두 가지다. 첫 번째는 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법이다. Object 배열을 생성한다음 제네릭 배열로 형변환하는 것이다. 이렇게 한다면 E[]로 생성할 때와는 다르게 비검사 형변환 경고가 발생한다. 문제의 배열은 클라이언트로 반환되거나 다른 메서드에 전달되는 일이 없기에 타입이 안전하다. 비검사 형변환이 안전함을 직접 증명했다면 이전 아이템에서 학습했듯이 범위를 최소로 좁혀..

Book/Effective Java 2022.07.07

[Effective Java]28.배열보다는 리스트를 사용하라

배열과 제네릭 타입에는 중요한 차이가 두 가지 있다. 첫번째, 배열은 공변이다.(공변 - 함께 변한다는 뜻) Sub가 Super의 하위 타입이라면 배열 Sub[]는 배열 Super[]의 하위 타입이 된다. 반면 제네릭은 불공변이다. 즉, 서로 다른 타입 Type1과 Type2가 있을 때, List은 List의 하위 타입도, 상위 타입도 아니다. 이것만 보면 제네릭에 문제가 있다고 생각할 수 있지만 문제가 있는 건 배열 쪽이다. 배열은 런타임 시점에, 제네릭은 컴파일 시점에 문제를 발견할 수 있기 때문이다. 두번째, 배열은 실체화된다. 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 그렇기 때문에 런타임 시점에 문제가 발견되는 것이다. 반면에 제네릭은 타입 정보가 런타임에는 소거된다...

Book/Effective Java 2022.07.07

[Effective Java]27.비검사 경고를 제거하라

제네릭을 사용하기 시작하면 수많은 컴파일러 경고를 보게될 것이다. 비검사 형변환 경고, 비검사 메서드 호출 경고, 비검사 매개변수화 가변인수 타입 경고, 비검사 변환 경고 등이다. 대부분의 비검사 경고는 쉽게 제거할 수 있다. 컴파일러가 무엇이 잘못됐는지 설명해주기에 알려준대로 수정하면 경고가 사라진다. 제거하기 훨씬 어려운 경고도 있다. 할 수 있는 한 모든 비검사 경고를 제거해야 한다. 모두 제거한다면 그 코드는 타입 안전성이 보장된다. 즉, 런타임에 ClassCastException이 발생할 일이 없고 의도한 대로 잘 동작하리라 확신할 수 있다. 경고를 제거할 수는 없지만 타입이 안전하다고 확신할 수 있다면 @SuppressWarnings("unchecked") 애너테이션을 달아 경고를 숨겨도 된다..

Book/Effective Java 2022.07.07

[Effective java]26.로 타입은 사용하지 말라

클래스와 인터페이스 선언에 타입 매개변수가 쓰이면 이를 제네릭 클래스, 제네릭 인터페이스라 한다. 예를 들어 List 인터페이스는 원소의 타입을 나타내는 타입 매개변수 E를 받는다. 그래서 이 인터페이스의 완전한 이름은 List지만 짧게 그냥 List라고 쓴다. 제네릭 타입을 하나 정의하면 그에 딸린 로 타입(raw type)도 함께 정의된다. 로 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다. 예를 들어 List의 로 타입은 List다. 로 타입은 타입 선언에서 제네릭 타입 정보가 전부 지워진 것처럼 동작하는데, 제네릭이 나오기 전 코드와 호환되도록 하기 위한 궁여지책이라 할 수 있다. 오류는 가능한 발생 즉시, 이상적으로는 컴파일할 때 발견하는 것이 좋다. 예를 들어 Lis..

Book/Effective Java 2022.07.07

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

소스 파일하나에 톱레벨 클래스를 여러개 선언할 수 있다. 하지만 이러한 선언은 아무런 득이 없고 심각한 위험을 감수해야 한다. 한 클래스를 여러가지로 정의하게 되는데, 그 중 어느 것을 사용할지는 어느 소스 파일을 먼저 컴파일하냐에 따라 달라진다. 구체적인 예로 확인해보자. 다음 소스 파일은 Main 클래스 하나를 담고 있고, Main 클래스는 다른 톱레벨 클래스 2개(Utensil과 Dessert)를 참조한다. public class Main { public static void main(String[] args) { System.out.println(Utensil.NAME + Dessert.NAME); } } 그리고 Utensil과 Dessert 클래스가 Utensil.java라는 한 파일에 정의되어..

Book/Effective Java 2022.07.06

[Effective Java]24.멤버 클래스는 되도록 static으로 만들라

중첩 클래스란 다른 클래스 안에 정의된 클래스다. 중첩 클래스는 자신을 감싼 바깥 클래스에서만 쓰여야 하며, 그 외에 쓰임새가 있다면 톱레벨 클래스로 만들어야 한다. 중첩 클래스의 종류는 정적 멤버 클래스, (비정적) 멤버 클래스, 익명 클래스, 지역 클래스 4가지가 있다. 이 중 첫번째를 제외한 나머지는 내부 클래스에 해당한다. 이번 아이템에서는 각각의 중첩 클래스를 언제, 왜 사용해야하는지 알아볼 것이다. 먼저 가장 간단한 정적 멤버 클래스이다. 정적 멤버 클래스는 다른 클래스 안에 선언되고 바깥 클래스의 private 멤버에도 접근할 수 있다는 점만 빼면 일반 클래스와 똑같다. 정적 멤버 클래스는 다른 정적 멤버와 똑같은 접근 규칙을 적용받는다. 정적 멤버 클래스는 흔히 바깥 클래스와 함께 쓰일 때..

Book/Effective Java 2022.07.06

[Effective Java]23.태그 달린 클래스보다 클래스 계층구조를 활용하라

두 가지 이상의 의미를 표현할 수 있으며 그중 현재 표현하는 의미를 태그 값으로 알려주는 클래스가 있다. 태그 달린 클래스에는 단점이 많다. 열거 타입 선언, 태그 필드, switch 문 등 쓸데 없는 코드가 많다. 여러 구현이 한 클래스에 혼합돼 있어서 가독성도 나쁘다. 다른 의미를 위한 코드도 언제나 함께 하니 메모리도 많이 사용한다. 그리고 인스턴스의 타입만으로는 현재 나타내는 의미를 알 길이 전혀 없다. 한마디로, 태그 달린 클래스는 장황하고 오류를 내기 쉽고 비효율적이다. 자바와 같은 객체 지향 언어는 타입 하나로 다양한 의미의 객체를 표현하는 훨씬 나은 수단을 제공한다. 바로 클래스 계층구조를 활용하는 서브타이핑이다. 태그 달린 클래스는 클래스 계층구조를 어설프게 흉내낸 아류일 뿐이다. 태그 ..

Book/Effective Java 2022.07.06

[Effective Java]22.인터페이스는 타입을 정의하는 용도로만 사용해라

인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입의 역할을 한다. 달리 말해 클래가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에게 얘기해주는 것이다. 인터페이스는 오직 이 용도로만 사용해야 한다. 이 지침에 맞지 않는 예로 상수 인터페이스라는 것이 있다. 상수 인터페이스란 메서드 없이 상수를 뜻하는 static final 필드로만 가득 찬 인터페이스이다. 이 상수들을 사용하려는 클래스에서는 정규화된 이름을 쓰는 걸 피하고자 그 인터페이스를 구현하곤 한다. 상수 인터페이스 안티패턴은 인터페이스를 잘못 사용한 예다. 클래스 내부에서 사용하는 상수는 외부 인터페이스가 아니라 내부 구현에 해당한다. 따라서 상수 인터페이스를 구현하는 것은 이 내부 구현..

Book/Effective Java 2022.07.06

[Effective Java]21.인터페이스는 구현하는 쪽을 생각해 설계해라

자바 8 이전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었다. 자바 8에서 디폴트 메서드를 추가할 수 있도록 변경되었지만 이를 믿고 그대로 사용하는 것은 위험하다. 만약 인터페이스에 디폴트 메서드를 선언한다면 디폴트 메서드를 재정의하지 않은 해당 인터페이스를 구현한 모든 클래스에서 디폴트 구현이 쓰이게 된다. 이 부분이 주의해야 할 점이라고 한다. 그 이유는 매끄럽게 연동되리라는 보장이 없기 때문이다. 자바 7까지는 "현재의 인터페이스에 새로운 메서드가 추가될 일은 영원히 없다"고 가정하고 작성되었다. 이러한 디폴트 메서드는 구현 클래스를 고려하지 않고 무작정 '삽입'만 될 뿐이다. 자바 8에서는 핵심 컬렉션 인터페이스들에 다수의 디폴트 메서드가 추가되었다. 주로 람다를 참..

Book/Effective Java 2022.07.05