클라이언트가 클래스의 인스턴스를 얻는 전통적인 수단은 public 생성자다. 하지만 모든 프로그래머가 꼭 알아둬야 할 기법이 하나 더 있다. 클래스는 생성자와 별도로 정적 팩터리 메서드(static factorymethod)를 제공할 수 있다.
점층적으로 필수 매개변수와 선택 매개변수를 모두 받는 생성자까지 생성자를 늘려가는 방식이다. 점층적 생성자 패턴도 쓸 수는 있지만, 매개변수 개수가 많아지면 클라이언트 코드를 작성하거나 읽기 어렵다. 점층적 생성자 패턴은 확장하기 어렵다. ...
싱글턴(singleton)이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다.싱글턴의 전형적인 예로는 함수와 같은 무상태(stateless) 객체나 설계상 유일해야 하는 시스템 컴포넌트를 들 수 있다.클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트를 테스트
java.lang.Math, java.util.Arrays처럼 기본 타입 값이나 배열 관련 메서드들을 모아놓을 수 있다.java.util.Collections처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩터리)를 모아놓을 수도 있다(자바 8부터는
dictionary 필드에서 final 한정자를 제거하고 다른 사전으로 교체하는 메서드를 추가할 수 있지만, 이 방식은 어색하고 오류를 내기 쉬우며 멀티스레드 환경에서는 쓸 수 없다.사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이
똑같은 기능의 객체를 매번 생성하기보다는 객체 하나를 재사용하는 편이 나을 때가 많다.특히 불변 객체는 언제든 재사용할 수 있다.위 문장은 실행될 때마다 String 인스턴스를 새로 만든다.new 키워드는 이전에 동일한 값이 사용되었는지 여부에 관계없이 항상 새 인스턴
이 스택을 사용하는 프로그램을 오래 실행하다 보면 점차 가비지 컬렉션 활동과 메모리 사용량이 늘어나 결국 성능이 저하될 것이다.상대적으로 드문 경우긴 하지만 심할 때는 디스크 페이징이나 OutOfMemoryError를 일으켜 프로그램이 예기치 않게 종료될 수도 있다.이
자바는 두 가지 객체 소멸자를 제공한다.그중 finalizer는 예측할 수 없고, 상황에 따라 위험할 수 있어 일반적으로 불필요하다.cleaner는 finalizer보다는 덜 위험하지만, 여전히 예측할 수 없고, 느리고, 일반적으로 불필요하다.자바에서는 접근할 수 없게
자바 라이브러리에는 close 메서드를 호출해 직접 닫아줘야 하는 자원이 많다.InputStream, OutputStream, java.sql.Connection 등이 좋은 예다.전통적으로 자원이 제대로 닫힘을 보장하는 수단으로 try-finally가 쓰였다.이는 예외
각 인스턴스가 본질적으로 고유하다.값을 표현하는 게 아니라 동작하는 개체를 표현하는 클래스가 여기 해당한다.인스턴스의 '논리적 동치성(logical equality)'을 검사할 일이 없다.논리적 동치성을 검사할 필요가 없다면, Object의 기본 equals만으로 충분
equals를 재정의한 클래스 모두에서 hashCode도 재정의해야 한다.그렇지 않으면 hashCode 일반 규약을 어기게 되어 해당 클래스의 인스턴스를 HashMap이나 HashSet 같은 컬렉션의 원소로 사용할 때 문제를 일으킬 것이다.다음은 Object 명세에서
Object의 기본 toString 메서드는 단순히 클래스이름@16진수로표시한\_해시코드를 반환할 뿐이다.toString의 일반 규약에 따르면 '간결하면서 사람이 읽기 쉬운 형태의 유익한 정보'를 반환해야 한다.toString을 잘 구현한 클래스는 사용하기에 훨씬 즐겁
Cloneable은 복제해도 되는 클래스임을 명시하는 용도의 믹스인 인터페이스(mixin interface)이다.하지만 큰 문제가 있는데, clone 메서드가 선언된 곳이 Cloneable이 아닌 Object이고, 그마저도 protected라는 것이다.그래서 Clone
compareTo는 Object의 메서드가 아니다. 하지만, 성격은 두 가지만 빼면 Object의 equals와 같다. compareTo는 단순 동치성 비교에 더해 순서까지 비교할 수 있으며, 제네릭하다.
어설프게 설계된 컴포넌트와 잘 설계된 컴포넌트의 가장 큰 차이는 바로 클래스 내부 데이터와 내부 구현 정보를 외부 컴포넌트로부터 얼마나 잘 숨겼느냐다.
API를 수정하지 않고 내부 표현을 바꿀 수 있다.불변식을 보장할 수 있다.외부에서 필드에 접근할 때 부수 작업을 수행할 수 있다.패키지 바깥에서 접근할 수 있는 클래스라면 접근자를 제공함으로써 클래스 내부 표현 방식을 언제든 바꿀 수 있는 유연성을 얻을 수 있다.pu
불변 클래스란 그 인스턴스 내부 값을 수정할 수 없는 클래스다.이는 객체가 파괴되는 순간까지 절대 달라지지 않는다.ex. String, 기본 타입의 박싱된 클래스들, BigInteger, BigDecimal ...불변 클래스는 가변 클래스보다 설계하고 구현하고 사용하기
상속은 코드를 재사용하는 강력한 수단이지만, 항상 최선은 아니다.
메서드를 재정의하려면 어떤 일이 일어나는지를 정확히 정리하여 문서로 남겨야 한다. 즉, 상속용 클래스는 재정의할 수 있는 메서드들을 내부적으로 어떻게 이용하는지(자기사용) 문서로 남겨야 한다.
자바가 제공하는 다중 구현 메커니즘은 인터페이스와 추상 클래스, 이렇게 두 가지다.
자바 8 이전에는 기존 구현체를 깨뜨리지 않고는 인터페이스에 메서드를 추가할 방법이 없었다.
인터페이스는 자신을 구현한 클래스의 인스턴스를 참조할 수 있는 타입 역할을 한다.
두 가지 이상의 의미를 표현할 수 있으며, 그중 현재 표현하는 의미를 태그 값으로 알려주는 클래스가 있다.
중첩 클래스(nested class)란 다른 클래스 안에 정의된 클래스를 말한다.
소스 파일 하나에 톱레벨 클래스를 여러 개 선언하더라도 자바 컴파일러는 불평하지 않는다.
클래스와 인터페이스 선언에 타입 매개변수(type parameter)가 쓰이면, 이를 제네릭 클래스 혹은 제네릭 인터페이스라 한다.
컴파일러 경고는 다양하다. 비검사 형변환 경고, 비검사 메서드 호출 경고, 비검사 매개변수화 가변인수 타입 경고, 비검사 변환 경고 등이 있다.
배열과 제네릭에는 중요한 차이가 두 가지 있다. 공변(variant)과 실체화(reify)이다.
JDK가 제공하는 제네릭 타입과 메서드를 사용하는 일은 일반적으로 쉬운 편이다. 하지만 제네릭 타입을 새로 만드는 일은 조금 더 어렵다.
클래스와 마찬가지로, 메서드도 제네릭으로 만들 수 있다. 매개변수화 타입을 받는 정적 유틸리티 메서드는 보통 제네릭이다.
서로 다른 타입 Type1과 Type2가 있을 때 List<Type1>은 List<Type2>의 하위 타입도 상위 타입도 아니다. 즉, List<String>은 List<Object>의 하위 타입이 아니다.
가변인수는 메서드에 넘기는 인수의 개수를 클라이언트가 조절할 수 있게 해주는데, 구현 방식에 허점이 있다.가변인수 메서드를 호출하면 가변인수를 담기 위한 배열이 자동으로 하나 만들어진다. ...
제네릭은 컬렉션과 단일원소 컨테이너에도 흔히 쓰인다. 이런 모든 쓰임에서 매개변수화되는 대상은 원소가 아닌 컨테이너 자신이다.
열거 타입은 일정 개수의 상수 값을 정의한 다음, 그 외의 값은 허용하지 않는 타입이다.
대부분의 열거 타입 상수는 순서대로 하나의 정숫값에 대응된다. 모든 열거 타입은 해당 상수가 그 열거 타입에서 몇 번째 위치인지를 반환하는 ordinal이라는 메서드를 제공한다.
열거한 값들이 단독이 아닌 집합으로 사용될 경우, 예전에는 각 상수에 서로 다른 2의 거듭제곱 값을 할당한 정수 열거 패턴을 사용했다.
이따금 배열이나 리스트에서 원소를 꺼낼 때 ordinal 메서드로 인덱스를 얻는 코드가 있다. 동작은 하지만 문제가 많다.
열거 타입은 확장할 수 없다. 이때 열거 타입이 임의의 인터페이스를 구현할 수 있다는 사실을 이용하여 확장과 비슷한 효과를 낼 수 있다.
전통적으로 도구나 프레임워크나 특별히 다뤄야 할 프로그램 요소에는 딱 구분되는 명명 패턴을 적용해왔다.
@Override는 메서드 선언에만 달 수 있으며, 이 애너테이션이 달렸다는 것은 상위 타입의 메서드를 재정의했음을 뜻한다. 이 애너테이션을 일관되게 사용하면 여러 가지 악명 높은 버그들을 예방해준다.
아무 메서드도 담고 있지 않고, 단지 자신을 구현하는 클래스가 특정 속성을 가짐을 표시해주는 인터페이스를 마커 인터페이스(marker interface)라 한다.
예전에는 자바에서 함수 타입을 표현할 때 추상 메서드를 하나만 담은 인터페이스(드물게는 추상 클래스)를 사용했다. 이런 인터페이스를 함수 객체(function object)라고 하여, 특정 함수나 동작을 나타내는 데 썼다.
람다가 익명 클래스보다 나은 점 중에서 가장 큰 특징은 간결함이다. 자바에는 함수 객체를 람다보다도 더 간결하게 만드는 방법이 있는데, 메서드 참조(method reference)다.
자바가 람다를 지원하면서 상위 클래스의 기본 메서드를 재정의해 원하는 동작을 구현하는 템플릿 메서드 패턴의 매력이 크게 줄었다. 같은 효과의 함수 객체를 받는 정적 팩터리나 생성자를 제공하는 것으로 대체할 수 있기 때문이다.
스트림 API는 다량의 데이터 처리 작업(순차적이든 병렬적이든)을 위해 자바 8에 추가되었다.
스트림은 함수형 프로그래밍에 기초한 패러다임이다. 스트림 패러다임의 핵심은 계산을 일련의 변환(transformation)으로 재구성하는 부분이다.
자바 7까지는 일련의 원소들을 반환하는 메서드의 반환 타입으로 Collection, Set, List 같은 컬렉션 인터페이스, 혹은 Iterable이나 배열을 썼다.
동시성 프로그래밍을 할 때는 안전성(safety)과 응답 가능(liveness) 상태를 유지하도록 해야 한다. 이는 병렬 스트림 파이프라인 프로그래밍에서도 마찬가지다.
메서드와 생성자 대부분은 입력 매개변수의 값이 특정 조건(불변식)을 만족했을 때 제대로 동작해야 한다. 그리고 이런 제약은 반드시 문서화해야 하며 메서드 몸체가 시작되기 전에 검사해야 한다.
자바는 안전한 언어이다. 네이티브 메서드를 사용하지 않으니 C, C++ 같이 안전하지 않은 언어에서 흔히 보는 버퍼 오버런, 배열 오버런, 와일드 포인터 같은 메모리 충돌 오류에서 안전하다.
다음은 API 설계 요령들을 모아 놓은 것이다. 이 요령들을 잘 활용하면 배우기 쉽고, 쓰기 쉬우며, 오류 가능성이 적은 API를 만들 수 있을 것이다.