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

지역변수의 범위를 줄이는 방법

  • 지역변수의 범위를 줄이는 가장 강력한 기법은 역시 '가장 처음 쓰일 때 선언하기'다.
  • 거의 모든 지역변수는 선언과 동시에 초기화해야 한다.
    • try-catch 문은 이 규칙에서 예외다.
  • while 문 보다는 for 문을 사용하자.
  • 메서드를 작게 유지하고 한 가지 기능에 집중하자.

item 58. 전통적인 for 문보다는 for-each 문을 사용하라

item 45에서 이야기했듯, 스트림이 제격인 작업이 있고 반복이 제격인 작업이 있다.

전통적인 for 문과 비교했을 때 for-each 문은 명료하고, 유연하고, 버그를 예방해준다. 성능 저하도 없다.

가능한 모든 곳에서 for 문이 아닌 for-each 문을 사용하자.

for-each 문을 사용할 수 없는 상황

  • 파괴적인 필터링(원소 제거)
  • 변형(원소 수정, 전체 수정)
  • 병렬 반복(각각의 반복자와 인덱스 변수가 필요할 경우)

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

메이저 릴리스마다 주목할 만한 수많은 기능이 라이브러리에 추가된다.

자바 프로그래머라면 적어도 jaav.lang, java.util, java.id와 그 하위 패키지들에는 익숙해져야 한다.

다른 라이브러리들은 필요할 때마다 익히자.

알아두면 좋은 라이브러리

  • 컬렉션 프레임워크
  • 스트림 라이브러리 (item 45~48)
    java.util.concurrent의 동시성 기능
  • 멀티스레드 프로그래밍 작업을 단순화해주는 고수준의 편의 기능 제공 (item 80~81)
    • 고수준 개념을 직접 구현할 수 있도록 도와주는 저수준 요소들을 제공

자바 표준 라이브러리 다음 선택지는 고품질의 서드파티 라이브러리이다.

  • 구글의 구아바 라이브러리가 대표적이다.

item 60. 정확한 답이 필요하다면 float와 double은 피하라

float와 double 타입은 특히 금융 관련 계산과는 맞지 않는다.

정확한 답이 필요한 계산에는 float이나 double을 피하라.

  • 0.1 혹은 10의 음의 거듭 제곱 수(10^-1, 10^-2 등)를 표현할 수 없기 때문이다.

BigDecimal 사용

  • BigDecimal이 제공하는 여덟가지 반올림 모드를 이용하여 반올림을 완벽히 제어할 수 있다.
  • 소수점 추적은 시스템에 맞긴다.
  • 코딩 시에 불편함하다.
  • 기본 타입보다 훨씬 느리다.

성능이 중요하고 소수점을 직접 추적할 수 있고 숫자가 너무 크지 않다면 int나 double을 사용하라.

int, long 사용

다룰 수 있는 값의 크기가 제한되고, 소수점을 직접 관리해야 함

  • 숫자를 아홉 자리 십진수로 표현할 수 있다면 int를 사용
  • 열여덟 자리 십진수로 표현할 수 있다면 long을 사용
  • 이를 넘어가면 BigDecimal을 사용

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

기본 타입과 박싱된 기본 타입 중 하나를 선택해야 한다면 가능하면 기본 타입을 사용하자.

기본 타입은 간단하고 빠르다.

기본타입

  • 값만 가지고 있다.
  • 값은 유효 하다.
  • 박싱된 기본타입보다 시간과 메모리 사용율에서 더 효율적이다.

박싱된 기본타입

  • 값에 더해서 식별성(identity)이란 속성을 가진다.
    • 값은 같아도 서로 다르게 인식할수있다.
  • 유효하지 않은 값을 가질수 있다.
    • null을 가질수 있다.

박싱된 기본 타입을 써야 한다면 주의를 기울이자.

  • 두 박싱된 기본 타입을 == 연산자로 비교한다면 동일성 비교를 하게 된다.
  • 같은 연산에서 기본 타입과 박싱된 기본 타입을 혼용하면 언박싱이 이뤄지며, 언박싱 과정에서 NullPointerException을 던질 수 있다.
  • 기본 타입을 박싱하는 작업은 필요 없는 객체를 생성하는 부작용을 나을 수 있다.

박싱된 기본 타입을 언제 써야 되나?

  • 컬렉션의 원소, 키값으로 쓴다.
  • 컬렉션은 기본 타입을 가질수 없으므로 어쩔수 없이 박싱된 기본 타입을 써야 된다.
  • 매개변수화 타입이나 매개변수화 매서드의 타입 매개변수로는 박싱된 기본타입을 써야 된다.

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

더 적합한 데이터 타입이 있거나 새로 작성할 수 있다면, 문자열을 쓰고 싶은 유혹을 뿌리쳐라.

문자열은 잘못 사용하면 번거롭고, 덜 유연하고, 느리고, 오류 가능성도 크다.

문자열을 잘못 사용하는 흔한 예로는 기본 타입, 열거 타입, 혼합 타입이 있다.

문자열은 다른 값을 대신하기 적절하지 않다.

  • 숫자를 표현할때는 int, float 등등의 타입이 좋고 예/아니오는 boolean 타입이 좋다

    문자열은 열거타입을 대신하기에 적합하지 않다.

  • 상수를 열거할 때는 문자열보다는 열거 타입이 월등히 낫다. (item 34)

    문자열은 혼합타입을 대신하기 적합하지 않다.

  • // 문자열로 혼합 타입을 작성
    String compoundKey = className + "#" + i.next();
    구분을 #으로만 하고 두 타입 중에 하나라도 #이 쓰이면 문제가 된다. 그리고 타입을 나눌 때는 파싱을 해야 되기 때문에 느리다.

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

    에를 들어 스레드 지역변수 기능을 설계한다고 해보자. 그 이름처럼 각 스레드가 자신만의 변수를 갖게 해주는 기능이다.
// 잘못된 예 - 문자열을 사용해 권한을 구분하였다.
public class ThreadLocal {
    private ThreadLocal() { } // 객체 생성 불가

    // 현 스레드의 값을 키로 구분해 저장한다.
    public static void set(String key, Object value);

    // (키가 가르키는) 현 스레드의 값을 반환한다.
    public static Object get(String key);
}
  • 각 클라이언트가 고유한 키를 제공해야 하지만 문제점이 존재한다.
  • 이 방식의 문제는 스레드 구분용 문자열 키가 전역 이름공간에서 공유된다는 점이다.

이 API는 문자열 대신 위조할 수 없는 키를 사용하면 해결된다. 이 키를 권한(capacity)이라고도 한다.

// Key 클래스로 권한을 구분했다.
public class ThreadLocal {
    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);
}
  • 이 방법은 위의 문제를 해결해주지만 개선할 여지가 있다.
  • set과 get은 이제 정적 메서드일 이유가 없으니 Key클래스의 인스턴스 메서드로 바꾸자.
  • 이렇게 하면 Key는 더 이상 스레드 지역변수를 구분하기 위한 키가 아니라, 그 자체가 스레드 지역변수가 된다.
  • 중첩 클래스 Key의 이름을 ThreadLocal로 바꿔버리자.
// 리팩터링하여 Key를 ThreadLocal로 변경
public final class ThreadLocal {
    public ThreadLocal();
    public static void set(Object value);
    public static Object get();
}
  • 이 API에서는 get으로 얻은 Object를 실제 타입으로 형변환해 써야 해서 타입 안전하지 않다.
  • ThreadLocal을 매개변수화 타입(item 29)으로 선언하면 간단하게 문제가 해결된다.
// 매개변수화하여 타입안전성 확보
public final class ThreadLocal<T> {
    public ThreadLocal();
    public static void set(T value);
    public static T get();
}
  • 이제 자바의 java.lang.ThreadLocal과 흡사해졌다.
  • 문자열 기반 API의 문제를 해결해주며, 키 기반 API보다 빠르고 우아하다.

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

성능에 신경 써야 한다면 많은 문자열을 연결할 때는 문자열 연결 연산자(+)를 피하자.

문자열 연결 연산자로 문자열 n개를 연결하는 시간은 n^2에 비례한다.

문자열은 불변(item 17)이라서 두 문자열을 연결할 경우 양쪽의 내용을 모두 복사해야 하므로 성능 저하가 생긴다.

문자열 연결을 잘못 사용한 예 - 느리다!
public String statement() {
    String result = "";
    for (int i = 0; i < numItems(); i++) {
        result += lineForItem(i); //문자열 연결
    }
    return result;
}
StringBuilder를 사용하면 문자열 연결 성능이 크게 개선된다.
public String statement2() {
    StringBuilder sb = new StringBuilder(numItems() * LINE_WIDTH);
    for (int i = 0; i < numItems(); i++) {
       sb.append(lineForItem(i));
    }
    return sb.toString();
}
  • StringBuilder를 전체 결과를 담기에 충분한 크기로 초기화한 점을 잊지 말자.

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

적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.

객체의 실제 클래스를 사용해야 할 상황은 '오직' 생성자로 생성할 때뿐이다.

// 좋은 예
Set<Son> sonSet = new LinkedHashSet<>();

// 나쁜 예
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();

인터페이스 타입의 장점

  • 인터페이스 타입을 사용하면 클라이언트 코드를 수정하지 않고도 참조 객체를 변경할 수 있다.
  • 다른 타입의 객체를 사용하더라도 컴파일에러/런타임에러에 대한 걱정을 하지 않아도 된다.

인터페이스 타입의 단점

  • 인터페이스 타입에 선언된 메서드를 구현한 메서드만 사용이 가능하다.
  • 특정 구현체의 내부 메서드를 사용할 수 없다.

클래스를 참조해야 하는 경우

  • String, BigInteger 같은 값 클래스처럼 적합한 인터페이스가 없을 경우
    • Integer, Long과 같은 타입을 사용할 때는 Number와 같은 상위 타입을 사용하지 말아야 한다.
  • 인터페이스에는 없는 메서드를 사용할 경우
    • PriorityQueue 클래스에는 Queue 인터페이스에는 없는 comparator 메서드를 제공한다.
  • 적합한 인터페이스가 없다면 클래스의 계층구조 중 필요한 기능을 만족하는 가장 덜 구체적인(상위의)클래스를 타입으로 사용하자.

+ 따끈한 최근 게시물