EffectiveJava

[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. 박싱된 기본 타입보다는 기본 타입을 사용하라

// 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. 일반적으로 통용되는 명명 규칙을 따르라

Leave a Reply