분류 전체보기
-
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로 ..
-
21. 인터페이스는 구현하는 쪽을 생각해 설계하라JAVA/Effective java 2021. 3. 4. 14:54
자바 8 전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었다. 인터페이스에 메서드를 추가하면 보통은 컴파일 오류가 나는데, 추가된 메서드가 우연히 기존 구현체에 이미 존재할 가능성은 아주 낮기 때문이다. 자바 8에 와서 기존 인터페이스에 메서드를 추가할 수 있도록 디폴트 메서드를 소개했지만, 위험이 완전히 사라진 것은 아니다. 디폴트 메서드를 선언하면, 그 인터페이스를 구현한 후 디폴트 메서드를 재정의하지 않은 모든 클래스에서 디폴트 구현이 쓰이게 된다. 이처럼 자바에도 기존 인터페이스에 메서드를 추가하는 길이 열렸지만 모든 기존 구현체들과 매끄럽게 연동된다는 보장은 없다. 디폴트 메서드는 구현 클래스에 대해 아무것도 모른 채 합의 없이 무작정 '삽입'될 뿐이다. 자바 8에서..
-
20. 추상 클래스보다는 인터페이스를 우선하라JAVA/Effective java 2021. 3. 3. 12:22
자바 8부터 인터페이스도 디폴트 메서드를 제공할 수 있게 되어, 이제는 추상 클래스와 인터페이스 모두 인스턴스 메서드를 구현 형태로 제공할 수 있다. 둘의 가장 큰 차이는 추상 클래스가 정의한 타입을 구현하는 클래스는 반드시 추상 클래스의 하위 클래스가 되어야 한다는 것이다. 자바는 단일 상속만 지원하니, 추상 클래스 방식은 새로운 타입을 정의하는 데 커다란 제약을 안게 된다. 반면 인터페이스가 선언한 메서드를 모두 정의하고 그 일반 규약을 잘 지킨 클래스라면 어떤 클래스를 상속했든 같은 타입으로 취급된다. 기존 클래스에도 손쉽게 새로운 인터페이스를 구현해 넣을 수 있다. 인터페이스가 요구하는 메서드를 추가하고, 클래스 선언에 implemenets 구문만 추가하면 끝인데, 자바 플랫폼에서도 Compara..
-
19. 상속을 고려해 설계하고 문서화하라. 그러지 않았다면 상속을 금지하라JAVA/Effective java 2021. 3. 2. 12:20
상속을 고려한 설계와 문서화란 무얼 뜻할까? 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지 문서로 남겨야 한다. API 설명과 덧붙여서 어떤 순서로 호출하는지, 각각의 호출 결과가 이어지는 처리에 어떤 영향을 주는지도 담아야 한다. 여기서 재정의 가능이란 pulbic과 protected 메서드 중 final이 아닌 모든 메서드를 뜻한다. Implementation Requirements로 시작하는 절을 API 문서의 메서드 설명에서 볼 수 있다. 이는 그 메서드의 내부 동작 방식을 설명하는 곳이다. 이 설명에 따르면 iterator 메서드를 재정의하면 remove 메서드의 동작에 영향을 줌을 확실히 알 수 있다. iterator 메서드로 얻은 반복자의 동작이 remove 메서드..
-
상속보다는 컴포지션을 사용하라JAVA/Effective java 2021. 2. 28. 13:58
상속은 코드를 재사용하는 강력한 수단이지만, 항상 최선은 아니다. 잘못 사용하면 오류를 내기 쉬운 소프트웨어를 만들게 된다. 상위 클래스와 하위 클래스를 모두 같은 프로그래머가 통제하는 패키지 안이라면 상속도 안전한 방법이다. 확장할 목적으로 설계되고 문서화 잘 된 클래스 역시 안전하다. 하지만 일반적인 구체 클래스를 패키지 경계를 넘어, 다른 패키지의 구체 클래스를 상속한다면 이는 위험할 수 있다. 메서드 호출과 달리 상속은 캡슐화를 깨뜨린다. 즉 상위 클래스가 어떻게 구현되느냐에 따라 하위 클래스의 동작에 이상이 생길 수 있다. 상위 클래스는 릴리즈마다 내부 구현이 달라질 수 있으며, 그 여파로 한 줄 건드리지 않은 하위 클래스가 오동작할 수 있다. getAddCount 메서드를 호출하면 3을 반환하..