지역변수의 범위를 최소화 하라

지역변수의 범위를 줄이는 가장 강력한 기법은 ‘가장 처음 쓰일 때 선언 하기’다
사용하려면 멀었는데, 미리 선언 부터 해두면 코드가 어수선해져서 가독성이 떨어진다. 변수를 실제로 사용하는 시점엔 타입과 초깃값이 기억나지 않을 수 도 있다.
지역변수를 생각 없이 선언하다 보면 변수가 쓰이는 범위보다 너무 앞서 선언하거나, 다 쓴 뒤에도 여전히 살아 있게 되기 쉽다.
거의 모든 지역 변수는 선언과 동시에 초기화 해야 한다. 예외 적으로 try-catch에서 변수를 초기화 하는 표현식에서 검사 예외를 던질 가능성이 있다면 try 블록 안에서 초기화 해야 한다.
반복 변수의 값을 반복문의 종료된 뒤에도 써야 하는 상황이 아니라면 while문 보다 for문을 사용하는 편이 낫다.



for 문보다는 for-each 문을 사용하라

for (Iterator<Element> i = c.iterator(); i.hasnext(); ) {

	// 내부 코드 실행

}
  • 1회 반복에서 반복자는 세 번 등장하며, 인덱스는 네 번이나 등장하여 변수를 잘못 사용할 틈새가 넓어진다. 혹시라도 잘못된 변수를 사용했을 때 컴파일러가 잡아주리라는 보장이 없다.


for-each 문을 사용하면 모두 해결된다. 참고로 for-each 문의 정식 이름은 ‘enhanced for statement’ 이다. 반복자와 인덱스 변수를 사용하지 않으니 가독성도 좋아지며, 위에서 말한 오류의 위험도 사라진다.

for-each문을 사용할 수 없는 세가지 상황이 존재한다.

  • Destructive filtering - 컬렉션을 순회하면서 선택된 원소ㅓ를 제거해야 한다면 반복자의 remove 메소드를 호출 해야 한다. 자바 8 부터는 Collection의 removeif 메서드를 사용해 컬렉션을 명시적으로 순회하는 일을 피할 수 있다.
  • Transforming - 리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용해야 한다.
  • Parallel iteration - 여러 컬렉션을 병렬로 순회해야 한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 한다.



라이브러리를 익히고 사용하라

표준 라이브러리를 사용하면 그 코드를 작성한 전문가의 지식과 여러분보다 앞서 사용한 다른 프로그래머들의 경험을 활용할 수 있다.
자바 7 부터는 Random을 사용하지 않는 것이 좋다. ThreadLocalRandom으로 대체해라. Random보다 더 고품질의 무작위 수를 생성할 뿐만 아니라 속도도 더 빠르다.
표준 라이브러리를 쓰는 두 번째 이점은 핵심적인 일과 크게 관련 없는 문제를 해결하느라 시간을 허비 하지 않아도 된다. 프로그래머들은 하부 공사를 하기 보다는 애플리케이션 기능 개발에 집중하고 싶어 한다.
세 번째 이점은 따로 노력하지 않아도 성능이 지속해서 개선된다는 점이다. 사용자가 많고, 업계 표준 벤치마크를 사용해 성능을 확인하기 때문에 표준 라이브러리 제작자들은 더 나은 방법을 꾸준히 모색할 수 밖에 없다.
네 번째 이점은 기능이 점점 많아진다. 개발자 커뮤니티에서 이야기가 나오고 논의된 후 다음 릴리스에 해당 기능이 추가되곤 한다.
마지막 이점은 여러분들이 작성한 코드가 많은 사람에게 낯익은 코드가 된다. 자연스럽게 다른 개발자들이 더 읽기 좋고, 유지보수하기 좋고, 재활용하기 쉬운 코드가 된다.
자바 프로그래머라면 적어도 java.lang, java.util, java.io와 그 하위 패키지들에는 익숙해져야 한다.



박싱된 기본 타입보다는 기본 타입을 사용하라

자바의 데이터 타입은 크게 두 가지로 나눌수 있다. int, double, boolean같은 기본 타입과 String, List 같은 참조 타입이다. 그리고 각각의 기본 타입에는 대응하는 참조 타입이 하나씩 있는데, 이를 박싱된 기본 타입이라고 한다. 예컨대 대응하는 박싱 기본 타입은 Integer, Double, Boolean 이다.
오토박싱과 오토언박싱 덕분에 두 타입을 크게 구분하지 않고 사용할 수는 있지만, 그렇다고 차이가 사라지는건 아니다.
  • 기본 타입은 값만 가지고 있으나, 박싱된 기본 타입은 값에 더해 식별성(identity)란 속성을 갖는다. 달리 말하면 박싱된 기본 타입의 두 인스턴스는 값이 같아도 서로 다르다고 식별될 수 있다.
  • 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 null을 가질 수 있다.
  • 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.
기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 풀린다.
for (long i = 0; i < Integer.MAX_VALUE; i++) {
	sum += i;
}
위 코드는 오류나 경고 없이 컴파일되지만, 박싱과 언박싱이 반복해서 일어나 체감될 정도로 성능이 느려진다.

박싱된 기본 타입은 언제 써야 하는가?

  • 컬렉션의 원소, 키, 값으로 쓴다. 컬렉션은 기본 타입을 담을 수 없으므로 어쩔 수 없이 박싱된 기본 타입을 써야 한다.
  • 리플렉션을 통해 메서드를 호출할 때도 박싱된 기본 타입을 사용해야 한다.



다른 타입이 적절하다면 문자열 사용을 피하라

문자열(String)은 텍스트를 표현하도록 설계되었다. 워낙 흔하고 자바가 잘 지원해주기 때문에 원래 의도하지 않은 용도로 쓰이는 경향이 있다.
문자열은 다른 값 타입을 대신하기에 적합하지 않다. 많은 사람이 파일, 네트워크, 키보드 입력으로부터 데이터를 받을 때 주로 문자열을 사용한다. 사뭇 자연스러워 보이지만, 입력받을 데이터가 진짜 문자열일 때만 그렇게 하는게 좋다.
받은 데이터가 수치형이라면 int, float, BigInteger 등 적당한 수치 타입으로 변환해야 한다.
예/아니오 형식의 답이라면 적절한 열거 타입이나 boolean 타입으로 변환해야 한다.
일반화 하자면, 기본 타입이든, 참조 타입이든 적절한 값 타입이 있다면 그것을 사용하고, 없다면 새로 하나 작성하라.

문자열은 권한을 표현하기에 적합하지 않다.

public class ThreadLocal {
	private ThreadLocal() {}
	// 현 스레드의 값을 키로 구분해 저장한다.
	public static void set(String key, Object value);
	// (키가 가리키는) 현 스레드의 값을 반환한다.
	public static Object get(String key);
}
이 방식의 문제는 스레드 구분용 문자열 키가 전역 이름 공간에서 공유된다는 점이다. 이 방식이 의도대로 동작하려면 각 클라이언트가 고유한 키를 제공해야 한다. 근데 만약 두 클라이언트가 서로 소통하지 못해 같은 키를 쓰기로 결정한다면, 의도치 않게 같은 변수를 공유를 하게 된다.
public class TreadLocal {
	private ThreadLocal() {}
	
	public static class Key {
		Key() {}
	}
	
  // 위조 불가능한 고유 키를 생성한다.
	public static Key getKey() {
		return new Key();
	}
	
	public static void set(Key key, Object value);
	public static Object get(Key key);
}
위 코드는 앞서의 문자열 기반 API의 문제 두 가지를 모두 해결해 주지만, 개선할 여지가 있다. set과 get은 이제 정적 메서드일 이유가 없으니 Key 클래스의 인스턴스 메소드로 바꾸자. 이렇게 하면 Key는 더 이상 스레드 지역변수를 구분하기 위한 키가 아니라, 그 자체가 스레드 지역 변수가 된다. 결과 적으로 지금의 톱 레벨 클래스인 ThreadLocal은 별달리 하는 일이 없어지므로, 중첩 클래스 Key이름을 ThreadLocal로 바꾸자. 결과적으로
public final class ThreadLocal<T> { // 매개화 변수 타입을 통해 형변환해 쓰지 않도록 하여 타입 오류를 방지
	public ThreadLocal();
	public void set(Object value);
	public Object get();
}

문자열 연결은 느리니 주의하라

문자열 연결 연산자로 문자열 n개를 잇는 시간은 n^2에 비례한다.
StringBuilder를 사용하자
public String statement() {
	StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH);
	for (int i = 0; i < numItems(); i++)
		b.append(lineForItem(i));
	return b.toString();
}
  • String + 연산은 n의 개수의 제곱에 비례하여 늘어나고, StringBuilder는 선형으로 늘어난다. n^2은 수가 늘어날 수록 성능 격차도 매우 벌어지게 된다.
  • StringBuilder 생성자에 크기를 지정해주지 않고 기본값을 사용하면 성능이 조금 떨어진다. 하지만 String + 연산보다는 훨씬 빠르다.



객체는 인터페이스를 사용해 참조하라

객체는 클래스가 아닌 인터페이스로 참조하라. 적합한 인터페이스가 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라. 객체의 실제 클래스를 사용해야 할 상황은 ‘오직’ 생성자로 생성할 때 뿐이다.
인터페이스를 타입으로 사용하는 습관을 길러두면 프로그램이 훨씬 유연해질 것이다. 나중에 구현 클래스를 교채하고자 한다면 그저 새 클래스의 생성자(혹은 다른 정적 팩터리)를 호출해주기만 하면 된다.
적합한 인터페이스가 없다면 당연히 클래스로 참조해야 한다. String, BigInteger 같은 값 클래스가 그렇다.
이런 값 클래스는 매개변수, 변수, 필드, 반환 타입으로 사용해도 무방하다.
적합한 인터페이스가 없는 두 번째 부류는 클래스 기반으로 작성된 프레임 워크가 제공하는 객체들이다. 이런 경우라도 특정 구현 클래스 보다는, 보통 추상 클래스를 사용해 참조하는게 좋다. OutputStream 등 java.io 패키지의 여러 클래스가 이 부류에 속함



일반적을 통용되는 명령 규칙을 따르라

패키지와 모듈 이름은 각 요소를 점(.)으로 구분하여 계층적으로 짓는다. 요소들은 모두 소문자 알파벳 혹은 드물게 숫자로 이뤄진다.
패키지는 조직의 인터넷 도메인 이름을 역순으로 사용한다.
  • e.g) google.com => com.google
각 요소는 일반적으로 8자 이하의 짧은 단어로 한다. (길 경우 약어로)