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

  • 지역변수 범위 최소화
    • 지역변수의 범위를 줄이는 가장 강력한 기법은 ‘가장 처음 쓰일 때 선언 하기’ 이다.
    • 거의 모든 지역변수는 선언과 동시에 초기화해야 한다.
      • try-catch 문은 예외 - 변수를 초기화하는 표현식에서 검사 예외를 던질 가능성이 있다면 try 블록 안에서 초기화하자.
    • 반복문에서 변수 최소화
      for(Element e: c) {
        // 
      }
      
      • 반복 변수(loop variable) 의 범위가 반복문의 몸체, 그리고 for 키워드와 몸체 사이의 괄호 안으로 제한된다.
      • while문보다 for문을 쓰는 편이 낫다.
    • 메서드를 작게 유지하고 한가지 기능에 집중하자.
      • 한 메서드에서 여러 가지 기능을 처리하면 그 중 한 기능과만 관련된 지역변수라도 다른 기능을 수행하는 코드에서 접근할 수 있다.
      • 메서드를 기능별로 쪼개자.

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

  • 전통적인 for문
    // 컬렉션으로 순회하기
    for(Iterator<Element> i = c.iterator(); i.hasNext(); ) {
        Element e = i.next();
        // e로 작업
    }
    
    // 배열로 순회하기
    for(int i = 0; i < a.length; i++) {
        // a[i];
    }
    
    • 반복자와 인덱스 변수는 코드를 지저분하게 할뿐, 필요한건 원소이다.
  • for-each 문
    for(Element e : c) {
        /// e
    }
    
    • for-each 문의 장점
      enum Face = { ONE, TWO, THREE, FOUR, FIVE, SIX};
      Collection<Face> faces = EnumSet.allOf(Face.class);
      for(Iterator<Face> i = faces.iterator(); i.hasNext(); ) {
          for(Iterator<Face> j = faces.iterator(); j.hasNext(); ) {
              System.out.println(i.next() + " " + j.next());
          }
      }
      
      • j반복될 때 i의 next가 호출되는 문제가 있다. 물론 해결할 수는 있다.
      • 다만, for-each문으로 사용하면 해당 버그를 미리 예방할 수 있으며 간편하게 원소들을 사용할 수 있다.
        enum Face = { ONE, TWO, THREE, FOUR, FIVE, SIX};
        Collection<Face> faces = EnumSet.allOf(Face.class);
        for(Face i : faces) {
          for(Face j : faces) {
              System.out.println(i + " " + j);
          }
        }
        
    • for-each 문을 사용하지 못하는 경우
      • 파괴적인 필터링(destructing filtering) : 컬렉션을 순회하면서 선택된 원소를 제거해야 한다면 반복자의 remove 메서드 호출해야 한다. 8부터는 Collection의 removeIf 메서드를 사용해 컬랙션을 명시적으로 수회하는 일을 피할 수 있다.
      • 변형(transforming) : 리스트나 배열을 순회하면서 그 원소의 값 일부 혹은 전체를 교체해야 한다면 리스트의 반복자나 배열의 인덱스를 사용해야 한다.
      • 병렬반복(parallel iteration) : 여러 컬렉션을 병렬로 순회해야 한다면 각각의 반복자와 인덱스 변수를 사용해 엄격하고 명시적으로 제어해야 한다.
    • for-each 문은 컬랙션과 배열 + Iterable 인터페이스를 구현한 객체라면 무엇이든 순회할 수 있다.
      public interface Iterable<E> {
          Iterator<E> iterator();
      }
      

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

  • Random
    static Random rnd = new Random();
    static int random(int n) {
        return Math.abs(rnd.nextInt()) % n;
    }
    
    • n이 그리 크지않은 2의 제곱수라가 아니라면 얼마 지나지 않아 같은 수열이 반복된다.
    • n이 2의 제곱수가 아니라면 몇몇 숫자가 평균적으로 더 자주 반환된다.
    • n이 값이 크면 현상은 더더욱 두드러진다.
  • 표준 라이브러 사용
    • 장점 1. 표준 라이브러리를 사용하면 그 코드를 작성한 전문가의 지식과 경험을 활용할 수 있다.
    • 장점 2. 핵심적인 일과 관련 없는 문제를 해결하기 위한 에포트가 적게 들어간다.
    • 장점 3. 따로 노력하지 않아도 성능이 지속적으로 개선된다.
    • 장점 4. 기능이 점점 많아진다.
    • 장점 5. 자연스럽게 개발자들이 읽기 쉽고, 유지보수하기 좋고, 재활용하기 좋아진다.
    • Random 보다는 ThreadLocalRandom
    • java.lang, java.util, java.io 와 그 하위 패키지들에 익숙해지자

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

  • float 과 double
    • float과 double은 과학, 공학 계산용으로 설계되었다.
    • 이진 부동소수점 연산에 쓰이며, 넓은 범위의 수를 빠르게 정밀한 ‘근사치’로 계산하도록 세심하게 설계되었다.
    • 따라서, 정확한 결과가 필요할 때는 사용하면 안된다.
  • BigDecimal, int 또는 long
    • 정확한 값을 얻고자 한다면 BigDecimal, int, long을 사용하자
    • BigDecimal 단점
      • 기본 타입보다 사용하기 어렵고, 느리다.

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

  • 기본타입과 박싱된 기본 타입의 주된 차이
    • 차이 1. 기본 타입은 값만 가지고 있으나, 박싱된 기본 타임은 값에 더해 식별성이란 속성이 있다.
    • 차이 2. 기본 타입의 값은 언제나 유효하나, 박싱된 기본 타입은 유효하지 않은 값(null)을 가질 수 없다.
    • 차이 3. 기본 타입이 박싱된 기본 타입보다 시간과 메모리 사용면에서 더 효율적이다.
  • 정렬에서 박싱된 기본 타입 사용
    Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : ( i == j ? 0 : 1);
    
    • 박싱된 기본 타입에 == 연산자를 사용하면 오류가 일어난다.
      Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
        int i = iBoxed, j = jBoxed;
        return i < j ? -1 : ( i == j ? 0 ? 1 );
      }
      
  • 초기화를 하지 않은 오류 발생
    public class Unbelivable {
        static Integer i;
        public static void main(String[] args) {
            if(i == 42)
                System.out.println("믿을 수 없군");
        }
    }
    
    • i == 42에서 NullPointerException을 던진다.
    • 기본 타입과 박싱된 기본 타입을 혼용한 연산에서는 박싱된 기본 타입의 박싱이 자동으로 풀린다.
  • 오토 박싱, 언박싱 과정에서 성능 문제가 발생한다.

  • 박싱된 타입을 사용해야 할 때
    • 컬렉션의 원소, 키, 값으로 써야할 때

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

  • 문자열 사용
    • 문자열은 다른 값 타입을 대신하기에 적합하지 않다.
    • 문자열은 열거 타입을 대신하기에 적합하지 않다.
      • 상수를 열거할 때는 문자열보다는 열거 타입이 월등히 낫다.
    • 문자열은 혼합 타입을 대신하기에 적합하지 않다.
      String compoundKey = className + "#" + i.next();
      
      • 단점 1. className 또는 i에 #이 존재한다면 원치않은 결과를 가져올 수 있다.
      • 단점 2. 각 요소를 개별로 접근하려면 문자열 파싱해야 해서 느리고, 귀찮고, 오류 가능성 또한 커진다.\
      • 차라리 전용 클래스를 생성하는 것이 좋다.
    • 문자열은 권한을 표현하기에 적합하지 않다.

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

  • 문자열 연결 (+)
    • 문자열 연결 연산자로 문자열 n개를 잇는 시간은 n^2에 비례한다.
    • 성능을 포기하고 싶지 않다면 String 대신 StringBuilder를 사용하자.

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

  • 적합한 인터페이스만 있다면 매개변수뿐 아니라 반환값, 변수, 필드를 전부 인터페이스 타입으로 선언하라.
    // 좋은 예
    Set<Son> sonSet = new LinkedHashSet<>();
    // 나쁜 예
    LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
    
    • 인터페이스를 타입으로 사용하는 습관을 길러두면 훨씬 유연해진다.
      • 주의 점. 원래의 클래스가 인터페이스의 일반 규약 이외의 특별한 기능을 제공하며, 주변 코드가 이 기능에 기대어 동작한다면 새로운 클래스도 반드시 같은 기능을 제공해야 한다.
    • 클라이언트에서 기존 타입에서만 제공하는 메서드를 사용했거나, 기존 타입을 사용해야 하는 다른 메서드에 그 인스턴스를 넘겼다면 새로운 코드에서 컴파일되지 않는다.
  • 적합한 인터페이스가 없다면 당연히 클래스로 참조하자.
    • String Integer 같은 값 클래스
      • 값 클래스를 여러가지로 구현될 수 있다고 생각하고 설게하지 않기 때문에 final로 작성하는 경우가 많다.
    • 클래스 기반으로 작성된 프레임워크가 제공하는 객체
      • 특정 구현 클래스보다는 기반 클래스를 사용해 참조하자.
    • 인터페이스에는 없는 특별한 메서드를 제공하는 클래스
      • 적합한 인터페이스가 없다면 클래스의 계층 구조 중 필요한 기능을 만족하는 가장 덜 구체적인(상위의) 클래스 타입을 사용하자.

ITEM 65. 리플렉션보다는 인터페이스를 사용하라.

  • 리플렉션 기능
    • java.lang.reflect
    • 프로그램에서 임의의 클래스에 접근할 수 있다.
    • Class 객체가 주어지면 Constructor, Method, Field 인스턴스를 가져올 수 있고, 이 인스턴스들로 그 클래스의 멤버 이름, 필드 타입, 메서드 시그니처 등을 가져올 수 있다.
    • 단점
      • 컴파일 타입 타입 검사가 주는 이점을 누릴 수 없다. 런타임 오류가 발생한다.
      • 리플렉션을 이용하면 코드가 지저분하고 장황해진다.
      • 성능이 덜어진다. 리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다.
    • 리플렉션은 아주 제한된 형태로만 사용해야 이점을 취할 수 있다.
      • 리플렉션은 인스턴스 생성에만 사용하고, 이렇게 만든 인스턴스는 인터페이스나 상위 클래스로 참조해 사용하자.
    • 드물긴 하지만, 리플렉션은 런타임에 존재하지 않을 수도 잇는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다.

ITEM 66. 네이티브 메서드는 신중히 사용하라

  • 네이티브 메서드
    • C나 C++같은 네이티브 프로그래밍 언어로 작성한 메서드
    • 주요 쓰임
      • 레지스트리 같은 플랫폼 특화 기능 사용
      • 네이티브 코드로 작성된 기존 라이브러리를 사용한다. 예시로는 레거시 데이터를 사용하는 레거시 라이브러리가 있다.
      • 성능 개선을 목적으로 성능에 결정적인 영향을 주는 영역만 따로 네이티브 언어로 작성한다.
    • 성능 개선 목적으로 네이티브 메서드를 사용하는 것은 권장하지 않는다.
      • 안전하지 않다, 이식성이 낮으며 디버깅도 어렵다.

ITEM 67. 최적화는 신중히 하라

  • 최적화를 할 때는 다음 두 규칙을 따르라. 첫번째는 하지 마라. 두번째는 (전문가 한정)아직 하지 마라. 다시 말해, 완전히 명백하고 최적화되지 않은 해법을 찾을 때까지는 하지 마라.
  • 빠른 프로그램보다는 좋은 프로그램을 작성하자.
  • 성능을 제한하는 설계를 피하라
  • API를 설계할 때 성능에 주는 영향을 고려하라
  • 최적화를 진행했다면, 성능 테스트를 진행하라

ITEM 68. 일반적으로 통용되는 명명 규칙을 따르라.