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(); }
- for-each 문의 장점
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로 작성하는 경우가 많다.
- 클래스 기반으로 작성된 프레임워크가 제공하는 객체
- 특정 구현 클래스보다는 기반 클래스를 사용해 참조하자.
- 인터페이스에는 없는 특별한 메서드를 제공하는 클래스
- 적합한 인터페이스가 없다면 클래스의 계층 구조 중 필요한 기능을 만족하는 가장 덜 구체적인(상위의) 클래스 타입을 사용하자.
- String Integer 같은 값 클래스
ITEM 65. 리플렉션보다는 인터페이스를 사용하라.
- 리플렉션 기능
- java.lang.reflect
- 프로그램에서 임의의 클래스에 접근할 수 있다.
- Class 객체가 주어지면 Constructor, Method, Field 인스턴스를 가져올 수 있고, 이 인스턴스들로 그 클래스의 멤버 이름, 필드 타입, 메서드 시그니처 등을 가져올 수 있다.
- 단점
- 컴파일 타입 타입 검사가 주는 이점을 누릴 수 없다. 런타임 오류가 발생한다.
- 리플렉션을 이용하면 코드가 지저분하고 장황해진다.
- 성능이 덜어진다. 리플렉션을 통한 메서드 호출은 일반 메서드 호출보다 훨씬 느리다.
- 리플렉션은 아주 제한된 형태로만 사용해야 이점을 취할 수 있다.
- 리플렉션은 인스턴스 생성에만 사용하고, 이렇게 만든 인스턴스는 인터페이스나 상위 클래스로 참조해 사용하자.
- 드물긴 하지만, 리플렉션은 런타임에 존재하지 않을 수도 잇는 다른 클래스, 메서드, 필드와의 의존성을 관리할 때 적합하다.
ITEM 66. 네이티브 메서드는 신중히 사용하라
- 네이티브 메서드
- C나 C++같은 네이티브 프로그래밍 언어로 작성한 메서드
- 주요 쓰임
- 레지스트리 같은 플랫폼 특화 기능 사용
- 네이티브 코드로 작성된 기존 라이브러리를 사용한다. 예시로는 레거시 데이터를 사용하는 레거시 라이브러리가 있다.
- 성능 개선을 목적으로 성능에 결정적인 영향을 주는 영역만 따로 네이티브 언어로 작성한다.
- 성능 개선 목적으로 네이티브 메서드를 사용하는 것은 권장하지 않는다.
- 안전하지 않다, 이식성이 낮으며 디버깅도 어렵다.
ITEM 67. 최적화는 신중히 하라
- 최적화를 할 때는 다음 두 규칙을 따르라. 첫번째는 하지 마라. 두번째는 (전문가 한정)아직 하지 마라. 다시 말해, 완전히 명백하고 최적화되지 않은 해법을 찾을 때까지는 하지 마라.
- 빠른 프로그램보다는 좋은 프로그램을 작성하자.
- 성능을 제한하는 설계를 피하라
- API를 설계할 때 성능에 주는 영향을 고려하라
- 최적화를 진행했다면, 성능 테스트를 진행하라