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);
}