(EffectiveJava) 9장 일반 프로그래밍 원칙


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

// 57-1 컬렉션이나 배역을 순회하는 권장 관용구
for (Element e : c) {
	... // e로 무언가를 한다
}
// 57-2 반복자가 필요할 때의 관용구
for (Iterator<Element> i = c.iterator(); i.hasNext(); ) {
	Element e = i.next(); 
  ... // e와 i로 무언가를 한다
}
Iterator<Element> i = c.iterator();
while(i.hasNext()){
	doSomething(i.next());
}
...
Iterator<Element> i2 = c2.iterator();
while(i.hasNext()){				//버그!
doSomething(i2.next()); }
for(Iterator<Element> i = c.iterator(); i.hasNext();){
	Element e = i.next();
	... // e와 i로 무언가를 한다.

} ... // 다음 코드는 "i를 찾을 수 없다"는 컴파일 오류를 낸다.

for(Iterator<Element> i2 = c2.iterator(); i.hasNext();){ Element e2 = i2.next(); ... // e2와 i2로 무언가를 한다.

}
for (int i=0, n=expensiveComputation(); i<n; i++) {
	... // i로 무언가를 한다
}

58. 전통적인 for 문 대신 for-each 문을 사용하세요.

// 58-1 컬렉션 순회하기 - 더 나은 방법이 있다.

for (Iterator<Element> i = list.iterator(); i.hasNext(); ) { Element e = i.next(); ... // a(i)로 무언가를 한다.

}
// 58-2 배열 순회하기 - 더 나은 방법이 있다.

for (int i = 0; i < list.size(); i++) { ... // a(i)로 무언가를 한다.

}
// 58-3 컬렉션과 배열을 순회하는 올바른 관용구
for (for (Element e : elements) { // ':' 은 in 이라고 읽으면 된다
    ...
}
// 58-4 버그를 찾아보자.
enum Suit { CLUB, DIAMOND, HEART, SPADE }
enum Rank { ACE, DEUCE, THREE, FOUR, FIVE, SIX, SEVEN, EIGHT,
    NINE, TEN, JACK, QUEEN, KING }
...

static Collection<Suit> suits = Arrays.asList(Suit.values());
static Collection<Rank> ranks = Arrays.asList(Rank.values());

List<Card> deck = new ArrayList<>();
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) {
    for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); ) {
        deck.add(new Card(i.next(), j.next()));
    }
}
// 58-5 같은 버그, 다른 증상!
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());
// 58-6 문제는 고쳤지만 보기 좋진 않다.

더 나은 방법이 있다!
for (Iterator<Suit> i = suits.iterator(); i.hasNext(); ) { Suit suit = i.next(); for (Iterator<Rank> j = ranks.iterator(); j.hasNext(); ) { deck.add(new Card(suit, j.next())); } }
// 58-7 컬렉션이나 배열의 중첩 반복을 위한 권장 관용구
for (Suit suit : suits)
	for (Rank rank : ranks)
		deck.add(new Card(suit, rank));
public interface Iterable<E> {
    // 이 객체의 원소들을 순회하는 반복자를 반환한다.

Iterator<E> iterator(); }

59. 도서관 학습 및 이용

// 59-1 흔하지만 문제가 심각한 코드!
static Random rnd = new Random(); static int random(int n) { return Math.abs(rnd.nextInt()) % n; }
public static void main(String() args) {
        int n = 2 * (Integer.MAX_VALUE / 3);
        int low = 0;
        for (int i = 0; i < 1000000; i++)
            if (random(n) < n/2)
                low++;
        System.out.println(low);
    }
// 코드 59-2 transferTo 메서드를 이용해 URL의 내용 가져오기 - 자바 9부터 가능하다.

(353쪽) public class Curl { public static void main(String() args) throws IOException { try (InputStream in = new URL("<https://www.naver.com>").openStream()) { in.transferTo(System.out); } } }

60. 정확한 답변이 필요한 경우 float 및 double을 피하십시오.

//답을 구하는 어설픈 코드 .. 
System.out.println(1.03 - 0.42);
//답을 구하는 어설픈 코드 .. 
System.out.println(1.00 - 9 * 0.10);
// 60-1 오류 발생!
금융 계산에 부동소수 타입을 사용했다.

public static void main(String() args) { double funds = 1.00; int itemsBought = 0; for (double price = 0.10; funds >= price; price += 0.10) { funds -= price; itemsBought++; } System.out.println(itemsBought + "개 구입"); System.out.println("잔돈(달러):" + funds); }
// 60-2 BigDecimal을 사용한 해법. 속도가 느리고 쓰기 불편하다.

public static void main(String() args) { final BigDecimal TEN_DENTS = new BigDecimal(".10"); int itemBought = 0; BigDecimal funds = new BigDecimal("1.00"); for (BigDecimal price = TEN_DENTS; funds.compareTo(price) >= 0; price = price.add(TEN_DENTS)) { funds = funds.subtract(price); itemBought++; } System.out.println(itemBought + "개 구입"); System.out.println("잔돈(달러) : " + funds); }
// 60-3 정수 타입을 사용한 해법
public static void main(String() args) {
	int itemsBought = 0;
	int funds = 100;
	for (int price = 10;  funds < price; price += 10) {
	  funds -= price;
	  itemsBought++;
	}
	System.out.println(itemsBought + "개 구입");
	System.out.println("잔돈(달러):" + funds);
}

61. boxed primitive type보다 primitive type을 선호하라

// 61-1 잘못 구현된 비교자 - 문제를 찾아보자!
Comparator<Integer> naturalOrder = (i, j) -> (i < j) ? -1 : (i == j ? 0 : 1);
// 61-2 문제를 수정한 비교자
Comparator<Integer> naturalOrder = (iBoxed, jBoxed) -> {
    int i = iBoxed, j = jBoxed; // 오토박싱
    return i < j ? -1 : (i == j ? 0 : 1);
};
// 61-3 기이하게 동작하는 프로그램 - 결과를 맞혀보자!
public class Unbelieable { static Integer i; public static void main(String() args) { if (i == 42) System.out.println("믿을 수 없군!
"); } }
// 61-4 끔찍이 느리다!
객체가 만들어지는 위치를 찾았는가? 코드 6-3과 같음 public static void main(String() args){ Long sum = 0L; for (long i = 0; i <= Integer.MAX_VALUE; i++) { sum += i; } System.out.println(sum); }

62. 다른 유형이 적절할 때 문자열을 사용하지 마십시오.

// 62-1 혼합 타입을 문자열로 처리한 부적절한 예
String compoundKey = className + "#" + i.next(); // 혼합 타입을 문자열로 처리한 부적절한 예
// 62-2 잘못된 예 - 문자열을 사용해 권한을 부여하였다.

public class ThreadLocal { private ThreadLocal() { } // 객체 생성 불가 // 현 스레드의 값을 키로 구분해 저장한다.

public static void set(String key, Object value); // (키가 가리키는) 현 스레드의 값을 반환한다.

public static Object get(String key); }
// 62-3 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(String key); }
// 62-4 리팩터링하여 Key를 ThreadLocal로 변경
public final class ThreadLocal {
    public THreadLocal();
    public void set(Object value);
    public Object get();
}
// 62-5 매개변수화하여 타입안정성 확보
public final class ThreaddLocal<T> {
    public ThreadLocal();
    public void set(T value);
    public T get();
}

63. 문자열 연결이 느리니 주의하세요.

// 63-1 문자열 연결을 잘못 사용한 예 - 느리다!
public String statement() { String result = ""; for (int i = 0; i < numItems(); i++) result += lineForItem(i); // 문자열 연결 return result; }
// 63-2 StringBuilder를 사용하면 문자열 연결 성능이 크게 개선된다.

public String statement2() { StringBuilder b = new StringBuilder(numItems() * LINE_WIDTH); for (int i = 0; i < numItems(); i++) b.append(lineForItem(i)); // 문자열 연결 성능이 크게 개선된다.

return b.toString(); }

64. 인터페이스를 사용하여 개체 참조

// 좋은 예. 인터페이스를 타입으로 사용했다.

Set<Son> sonSet = new LinkedHashSet<>();
// 나쁜 예. 클래스를 타입으로 사용했다!
LinkedHashSet<Son> sonSet = new LinkedHashSet<>();
Set<Son> sonSet = new HashSet<>(); 
// LinkedHashSet()에서 HashSet()으로 쉽게 변환했다.

65. 리플렉션을 통한 인터페이스

// 65-1 리플렉션으로 생성하고 인터페이스로 참조해 활용한다.

public static void main(String() args) { // 클래스 이름을 Class 객체로 변환 Class<? extends Set<String>> cl = null; try{ cl = (Class<? extends Set<String>>) // 비검사 형변환!
Class.forName(args(0)); } catch(ClassNotFoundException e) { fatalError("클래스를 찾을 수 없습니다.

"); } // 생성자를 얻는다.

Constructor<? extends Set<String>> cons = null; try{ cons = cl.getDeclaredConstructor(); } catch (NoSuchMethodException e) { fatalError("매개변수 없는 생성자를 찾을 수 없습니다.

"); } // 집합의 인스턴스를 만든다.

Set<String> s = null; try { s = cons.newInstance(); } catch (IllegalAccessException e){ fatalError("생성자에 접근할 수 없습니다.

"); } catch (InstantiationException e) { fatalError("클래스를 인스턴스화할 수 없습니다.

"); } catch (InvocationTargetException e) { fatalError("생성자가 예외를 던졌습니다.

" + e.getCause()); } catch (ClassCastException e ) { fatalError("Set을 구현하지 않은 클래스입니다.

"); } // 생성한 집합을 사용한다.

s.addAll(Arrays.asList(args).subList(1, args.length)); System.out.println(s); } private static void fatalError(String msg){ System.err.println(msg); System.exit(1); }

66. 네이티브 메소드를 현명하게 사용하라

67. 신중하게 최적화

68. 일반적으로 허용되는 명명 규칙을 따르십시오.