JAVA
-
29. 이왕이면 제네릭 타입으로 만들라JAVA/Effective java 2021. 3. 14. 15:02
해당 Stack 클래스는 원래 제네릭 타입이어야 마땅하다. 그러니 제네릭으로 만들어보자. 지금 상태에서의 클라이언트는 스택에서 꺼낸 객체를 형변환해야 하는데, 이 때 런타임 오류가 날 위험이 있다. 일반 클래스를 제네릭 클래스로 만드는 첫 단계는 클래스 선언에 타입 매개변수를 추가하는 일이다. 이때 타입 이름으로는 보통 E를 사용한다. 그런 후에 Object를 적절한 타입 매개 변수로 바꾸고 컴파일 해보자. 이 단계에서 대체로 하나 이상의 오류가 발생하는데 E와 같은 실체화 불가 타입으로는 배열을 만들 수 없다. 배열을 사용하는 코드를 제네릭으로 만들려 할 때 이 문제는 항상 발목을 잡는다. 적절한 해결책은 두 가지다. 1. 제네릭 배열 생성을 금지하는 제약을 대놓고 우회하는 방법 Object 배열을 생..
-
28. 배열보다는 리스트를 사용하라JAVA/Effective java 2021. 3. 13. 14:48
배열과 제네릭 타입에는 중요한 차이가 두 가지 있다. 공변 첫 번째, 배열은 공변이다. Sub가 Super의 하위 타입이라면 배열 Sub[]는 Super[]의 하위 타입이 된다. (공변 : 함께 변한다) 반면 제네릭은 불공변이다. 서로 다른 타입 Type1과 Type2가 있을 때, List은 List의 하위 타입도 아니고 상위 타입도 아니다. 이때 문제가 있는 건 배열쪽이다. 어느 쪽이든 Long 용 저장소에 String을 넣을 수는 없다. 배열에서는 그 실수를 런타임에야 알게 되지만, 리스트를 사용하면 컴파일 때 바로 알 수 있다. 실체화 배열은 런타임에도 자신이 담기로 한 원소의 타입을 인지하고 확인한다. 앞 서 코드에서 보듯 Long 배열에 String을 넣으려 하면 ArrayStoreExcepti..
-
27. 비검사 경고를 제거하라.JAVA/Effective java 2021. 3. 12. 13:59
제네릭을 사용하기 시작하면 수많은 컴파일러 경고를 보게 된다. 비검사 형 변환, 메서드 호출, 매개변수화 가변 인수 타입 경고, 변환 경고 등이 있는데 제네릭에 익숙해질수록 마주치는 경고 수는 줄겠지만 새로 작성한 코드가 한번에 깨끗하게 컴파일되리라는 법은 없다. 대부분의 비검사 경고는 쉽게 제거할 수 있다. Set exaltation = new HashSet(); 코드를 다음처럼 잘못 작성했다고 해보자. 그러면 컴파일러는 무엇이 잘못됐는지 친절히 설명해준다. 사실 컴파일러가 알려준 타입 매개변수를 명시하지 않고, 자바 7부터 지원하는 다이아몬드 연산자()만으로 해결할 수 있다. 그러면 컴파일러는 올바른 실제 타입 매개변수를 추론해준다. 이 경우는 Lark이다. Set exaltation = new Ha..
-
26. 로(Raw) 타입은 사용하지 말라JAVA/Effective java 2021. 3. 11. 13:45
클래스와 인터페이스 선언에 타입 매개변수가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 한다. List 인터페이스는 원소의 타입을 나타내는 타입 매개변수 E를 받는다. 그래서 이 인터페이스의 완전한 이름은 List 이지만, 짧게 List라고 쓴다. 이러한 제네릭 클래스와 제네릭 인터페이스를 통틀어 제네릭 타입이라 한다. List을 예로 이는 원소의 타입이 String인 리스트를 뜻하는 매개변수화 타입이다. 여기서 String이 정규 타입 매개변수 E에 해당하는 실제 타입 매개변수이다. 제네릭 타입을 하나 정의하면 그에 딸린 로 타입(raw type)도 함께 정의된다. 로 타입이란 제네릭 타입에서 타입 매개변수를 전혀 사용하지 않을 때를 말한다. 따라서 List의 로 타입은 List이다. 로 타입..
-
25. 톱레벨 클래스는 한 파일에 하나만 담으라JAVA/Effective java 2021. 3. 9. 14:28
소스 파일 하나에 톱레벨 클래스를 여러 개 선언하더라도 자바 컴파일러는 불평하지 않는다. 하지만 아무런 득이 없을 뿐더러 심각한 위험을 감수해야 하는 행위다. 이렇게 하면 한 클래스를 여러 가지로 정의할 수 있으며, 그 중 어느 것을 사용할지는 어느 소스 파일을 먼저 컴파일하느냐에 따라 달라지기 때문이다. 다음 소스 파일은 Main 클래스 하나를 담고 있고, Main 클래스는 다른 톱레벨 클래스 2개(Utensil과 Dessert)를 참조한다. Utensil과 Dessert 클래스가 Utensil.java 라는 한 파일에 정의되어 있다고 해보자. 물론 Main을 실행하면 pancake를 출력한다. 이제 우연히 똑같은 클래스를 담은 Dessert.java라는 파일을 만들었다고 해보자. 운 좋게 javac ..
-
24. 멤버 클래스는 되도록 static으로 만들라JAVA/Effective java 2021. 3. 8. 14:19
중첩 클래스란 다른 클래스 안에 정의된 클래스를 말한다. 중첩 클래스의 종류는 정적 멤버 클래스, (비정적) 멤버 클래스, 익명 클래스, 지역 클래스, 이렇게 네 가지다. 이번 아이템에서는 각각의 중첩 클래스를 언제 그리고 왜 사용해야 하는지 이야기한다. 정적 멤버 클래스와 비정적 멤버 클래스 정적 멤버 클래스는 다른 클래스 안에 선언되고, 바깥 클래스의 private 멤버에도 접근할 수 있다는 점만 제외하고는 일반 클래스와 똑같다. 정적 멤버 클래스는 흔히 바깥 클래스와 함께 쓰일 때만 유용한 public 도우미 클래스로 쓰인다. 계산기가 지원하는 연산 종류를 정의하는 열거 타입을 예로 생각해보자. Operation 열거 타입은 Calculator 클래스의 public 정적 멤버 클래스가 되어야 한다...
-
23. 태그 달린 클래스보다는 클래스 계층구조를 활용하라JAVA/Effective java 2021. 3. 7. 14:23
두 가지 이상의 의미를 표현할 수 있으며, 그 중 현재 표현하는 의미를 태그 값으로 알려주는 클래스를 본 적이 있을 것이다. 다음 코드는 원과 사각형을 표현할 수 있는 클래스다. 문제점 열거 타입 선언, 태그 필드, switch 문 등 쓸데없는 코드가 많다. 여러 구현이 한 클래스에 혼합돼 있어서 가독성도 나쁘다. 다른 의미를 위한 코드도 언제나 함께 해서 메모리도 많이 사용하며 또 다른 의미를 추가하려면 코드를 수정해야 한다. 예를 들어 새로운 의미를 추가할 때마다 모든 switch 문을 찾아 새 의미를 처리하는 코드를 추가해야 하며, 하나라도 빠뜨리면 런타임에 문제가 불거져 나올 것이다. 마지막으로 인스턴스 타입만으로는 현재 나타내는 의미를 알 길이 전혀 없으며 한마디로, 태그 달린 클래스는 장황하고..
-
22. 인터페이스는 타입을 정의하는 용도로만 사용하라JAVA/Effective java 2021. 3. 6. 19:34
인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 한다. 즉, 클래스가 어떤 인터페이스를 구현한다는 것은 자신의 인스턴스로 무엇을 할 수 있는지를 클라이언트에게 이야기하는 것이며 인터페이스는 오직 이 용도로만 사용해야 한다. 이 지침에 맞지 않는 예로 소위 상수 인터페이스라는 것이 있다. 상수를 뜻하는 static final 필드로만 가득 찬 인터페이스가 이를 말한다. 이 상수들을 사용하려는 클래스에서는 정규화된 이름을 쓰는 걸 피하고자 그 인터페이스를 구현하곤 한다. 상수 인터페이스 안티패턴은 인터페이스를 잘못 사용한 예다. 클래스 내부에서 사용하는 상수는 외부 인터페이스가 아니라 내부 구현에 해당한다. 따라서 상수 인터페이스를 구현하는 것은 이 내부 구현을 클래스의 API로 ..