EffectiveJava

[EffectiveJava] 8장 메서드

49. 매개변수가 유효한지 검사하라

/**
 * (현재 값 mod m) 값을 반환한다. 이 메서드는
 * 항상 음이 아닌 BigInteger를 반환한다는 점에서 remainder 메서드와 다르다.
 *
 * @param m 계수(양수여야 한다.)
 * @return 현재 값 mod m
 * @throws ArithmeticException m이 0보다 작거나 같으면 발생한다.
 */
public BigInteger mod(BigInteger m) {
    if (m.signum() <= 0) {
        throw new ArithmeticException("계수 (m)는 향수여야 합니다. " + m);
    }
		... // 계산 수행
}
// 49-1 자바의 null 검사 기능 사용하기
this.strategy = Objects.requireNonNull(strategy,"전략");
System.out.println(Objects.requireNonNull(null, "null임").toString());
// 49-2 재귀 정렬용 private 도우미 함수
private static void sort(long a[], int offset, int length) {
    assert a != null;
    assert offset >= 0 && offset <= a.length;
    assert length >= 0 && a.length - offset;
    ... // 계산 수행
}

50. 적시에 방어적 복사본을 만들라

// 50-1 기간을 표현하는 클래스 - 불변식을 지키지 못했다.
public final class Period {
    private final Date start;
    private final Date end;
    
    /**
     * @param start 시작 시각
     * @param end 종료 시각; 시작 시각보다 뒤여야 한다.
     * @throws IllegalArgumentException 시작 시각이 종료 시각보다 늦을 때 발생한다.
     * @throws NullPointerException start나 end가 null이면 발생한다.
     */
     
     public Period(Date start, Date end) {
         if (start.compareTo(end) > 0)
             throw new IllegalArgumentException(
                 start + "가 " + end + "보다 늦다.");
         this.start = start;
         this.end = end;
     }
     
     public Date start() {
     	return start;
     }
     
     public Date end() {
     	return end;
     }
     
     ... // 나머지 코드 생략
}
// 50-2 Period 인스턴스의 내부를 공격해보자.
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
end.setYear(78); // p의 내부를 수정했다!
// 50-3 수정한 생성자 - 매개변수의 방어적 복사본을 만든다.
public Period(Date start, Date end) {
    this.start = new Date(start.getTime());
    this.end = new Date(end.getTime());
    
    if (this.start.compareTo(this.end) > 0)
        throw new IllegalArgumentException(
            this.start + "가 " + this.end + "보다 늦다.");
}
// 코드 50-4 Period 인스턴스를 향한 두 번째 공격
Date start = new Date();
Date end = new Date();
Period p = new Period(start, end);
p.end().setYear(78); // p의 내부를 변경했다!
// 코드 50-5 수정한 접근자 - 필드의 방어적 복사본을 반환한다.
public Date start() {
    return new Date(start.getTime());
}
     
public Date end() {
    return new Date(end.getTime());
}

51. 메서드 시그니처를 신중히 설계하라

// Before
// SUIT는 enum타입이라고 가정 (스페이드, 하트 ...)
public void shuffle(int rank, SUIT suit){
}
// After
// rank, suit를 하나로 묶는 Card 도우미 클래스를 만든다.
public void shuffle(Card card){
}
// 내부 클래스로 정의
private static class Card{
        int rank;
        SUIT suit;
}
public enum TemperatureScale { FAHRENHEIT, CELSIUS }

TemperatureScale.newInstance(true)
TemperatureScale.newInstance(FAHRENHEIT.CELSIUS)

52. 다중정의는 신중히 사용하라

// 52-1 컬렉션 분류기 - 오류! 이 프로그램은 무엇을 출력할까?
public class CollectionClassifier {
    public static String classify(Set<?> s) {
        return "집합";
    }

    public static String classify(List<?> lst) {
        return "리스트";
    }

    public static String classify(Collection<?> c) {
        return "그 외";
    }

    public static void main(String[] args) {
        Collection<?>[] collections = {
                new HashSet<String>(),
                new ArrayList<BigInteger>(),
                new HashMap<String, String>().values()
        };

        for (Collection<?> c : collections)
            System.out.println(classify(c));
    }
}
// 예상) "집합", "리스트", "그 외"
// 실제) "그 외", "그 외", "그 외"
// 52-2 재정의된 메서드 호출 메커니즘 - 이 프로그램은 무엇을 출력할까?
class Wine {
    String name() { return "포도주"; }
}

class SparklingWine extends Wine {
    @Override String name() { return "발포성 포도주"; }
}

class Champagne extends SparklingWine {
    @Override String name() { return "샴페인"; }
}

public class Overriding {
    public static void main(String[] args) {
        List<Wine> wineList = List.of(
                new Wine(), new SparklingWine(), new Champagne());

        for (Wine wine : wineList)
            System.out.println(wine.name());
    }
}

//예상) "포도주", "발포성 포도주", "샴페인"
//실제) "포도주", "발포성 포도주", "샴페인"
public static String classify(Collection<?> c) {
	return c instance of Set ? "집합" :
					c instance of List ? "리스트" : "그 외";
}
public class SetList {
	public static void main(String[] args) {
    Set<Integer> set = new TreeSet<>();
    List<Integer> list = new ArrayList<>();

    for (int i = -3; i < 3; i++) {
        set.add(i);
        list.add(i);
    }

    for (int i = 0; i < 3; i++) {
        set.remove(i); // remove(Object) : 0 이상의 수를 제거 - 기대하는 동작
        //list.remove(i); // remove(int index) : 지정 된 위치 제거 - 문제가 되는 동작
        list.remove((Integer) i); // 형변환
    }
    System.out.println(set + " " + list); // [-3, -2, -1] [-2, 0, 2]
	}
}
for (int i=0; i<3; i++) {
		list.remove(i); // 인덱스를 지움	
		list.remove((Integer)i); // 값을 지움
		// or list.remove(Integer.valueOf(i))
	
}
// 1번. Thread의 생성자 호출
new Thread(System.out::println).start();

// 2번. Exeutor의 submit 메서드 호출
ExecurotService exec = Executors.newCachedThradPool();
exec.submit(System.out::println);
// 52-3 인수를 포워드하여 두 메서드가 동일한 일을 하도록 보장한다.
public boolean contentEquals(StringBuffer sb){
    return contentEquals((CharSequence) sb);
}

53. 가변인수는 신중히 사용하라

// 53-1 간단한 가변인수 활용 예
static int sum(int... args) {

  int sum = 0;
  for (int arg : args) {
      sum += arg;
  }
  return sum;
}
// 53-2 인수가 1개 이상이어야 하는 가변인수 메서드 - 잘못 구현한 예! 
static int min(int... args) {
    if (args.length == 0)
        throw new IllegalArgumentException("인수가 1개 이상 필요합니다.");
    int min = args[0];
    for (int i = 1; i < args.length; i++)
        if (args[i] < min)
            min = args[i];
    return min;
}
// 53-3 인수가 1개 이상이어야 할 때 가변인수를 제대로 사용하는 방법
static int min(int firstArg, int... remainingArgs) {
	int min = firstArg;
	for (int arg : remainingArgs)
		if (arg < min)
			min = arg;
	return min;
}
public void foo() { }
public void foo(int a1) { }
public void foo(int a1, int a2) { }
public void foo(int a1, int a2, int a3) { }
public void foo(int a1, int a2, int a3, int.. rest) { } // 단 5%로만 사용

54. null이 아닌, 빈 컬렉션이나 배열을 반환하라

// 54-1 컬렉션이 비었으면 null을 반환한다. -따라 하지 말 것!
private final List<Cheese> cheeseInStock = ...;
/**
* @return 매장 안의 모든 치즈가 목록을 반환한다
* 단, 재고가 없다면 null 반환
*/
public List<Cheese> getCheese() {
	return cheesesInStock.isEmpty() ? null 
						: new ArrayList<>(cheesesInStock);
}
List<Cheese> cheese = shop.getCheeses();
if (cheeses != null && cheeses.contains(Cheese.STILTON))
	System.out.println("좋았어, 바로 그거야.");
// 54-2 빈 컬렉션을 반환하는 올바른 예
public List<Cheese> getCheese() {
	return new ArrayList<>(cheesesInStock);
}
// 54-3 최적화 - 빈 컬렉션을 매번 새로 할당하지 않음
public List<Cheese> getCheese() {
	return cheesesInStock.isEmpty() ? Collections.emptyList() 
			: new ArrayList<>(cheesesInStock);
}
// 54-4 길이가 0일 수도 있는 배열을 반환하는 올바른 방법
public Cheese[] getCheeses() {
	return cheeseInStock.toArray(new Cheese[0]);
}
// 54-5 최적화 - 빈 배열을 매번 새로 할당하지 않도록 했다.
private static final Cheese[] EMPTY_CHEESE_ARRAY = new Cheese[0];

public Cheese[] getCheese() {
	return cheesesInStock.toArray(EMPTY_CHEESE_ARRAY);
}
// 54-6 나쁜 예 - 배열을 미리 할당하면 성능이 나빠진다.
return cheesesInStock.toArray(new Cheese[cheesesInStock.size()]);

55. 옵셔널 반환은 신중히 하라

// 55-1 컬렉션에서 최댓값을 구한다. - 컬렉션이 비었으면 예외를 던진다
    public static <E extends Comparable<E>> E max(Collection<E> c) {
        if (c.isEmpty())
            throw new IllegalArgumentException("빈 컬렉션");
        E result = null;
        for (E e : c)
            if (result == null || e.compareTo(result) > 0)
                result = Objects.requireNonNull(e);
        return result;
    }
// 55-2 컬렉션에서 최댓값을 구해 Optional<E>로 반환한다
public static <E extends Comparable<E>>
				Optional<E> max(Collection<E> c) {
    if (c.isEmpty())
        return Optional.empty();

    E result = null;
    for (E e : c)
        if (result == null || e.compareTo(result) > 0)
            result = Objects.requireNonNull(e);

    return Optional.of(result);
}
// 55-3 컬렉션에서 최댓값을 구해 Optional<E>로 반환한다. -스트림 버전
public static <E extends Comparable<E>>
							Optional<E> max(Collection<E> c) {
        return c.stream().max(Comparator.naturalOrder());
}
// 55-4 기본값을 정해둘 수 있다.
String lastWordInLexicon = max(words).orElse("단어없다");
// 55-5 원하는 예외를 던질 수 있다.
Toy myToy = max(toys).orElseThrow(TemperTantrumException::new);
// 55-6 옵셔널에 항상 값이 채워져 있다고 가정한다.
Element lastNobelGas = max(Elements.NOBLE_GASES).get();
Optional<ProcessHandle> parentProcess = ph.parent();
System.out.println("부모 PID: " + (parentProcess.isPresent() ?
        String.valueOf(parentProcess.get().pid()) : "N/A"));

// 같은 기능을 Optional의 map를 이용해 개선한 코드
System.out.println("부모 PID: " +
    ph.parent().map(h -> String.valueOf(h.pid())).orElse("N/A"));
streamOfOptionals
		.filter(Optional::isPresent)
		.map(Optional::get)
streamOfOptionals.flatMap(Optional::stream) // flatMap()과 stream()으로 다듬을 수 있다.

56. 공개된 API 요소에는 항상 문서화 주석을 작성하라

/**
 * Returns the element at the specified position in this list.
 *
 * <p>This method is <i>not</i> guaranteed to run in constant
 * time. In some implementations it may run in time proportional
 * to the element position.
 *
 * @param index index of the element to return; must be
 *        non-negative and less than the size of this list
 * @return the element at the specified position in this list
 * @throws IndexOutOfBoundsException if the index is out of range
 *         ({@code index < 0 || index >= size()})
 */
E get(int index);
/**
* Returns true if this collection is empty.
*
* @implSpec This implementation returns {@code this.size() == 0}.
*
* @return true if this collection is empty
*/
public boolean isEmpty() {
	return false;
}
* A geometric series converges if {@literal |r| <1}.
/**
* A susprct, such as Colonel Mustard or {@literal Mrs.} Peacock.
*/
public class Suspect { ... }
/**
* {@summary A suspect, such as Colonel Mustard or Mrs. Peacock.} 
*/
public class Suspect { ... }
/**
* An object that maps keys to values.  A map cannot contain duplicate keys;
* each key can map to at most one value. 
*
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*/
public interface Map<K,V> {
	...
}
/**
 * 심포니 오케스트라의 악기 세션.
 */
public enum OrchestraSection {
  /** 플루트, 클라리넷, 오보에 같은 목관악기 */
  WOODWIND,

  /** 프렌치 혼이나 트럼펫 같은 금관악기 */ 
  BRASS,

  /** 팀파니나 심벌즈 같은 타악기 */
  PRECUSSION,

  /** 바이올린이나 첼로 같은 현악기 */
  STRING;
}
/**
* Indicates that the annotated method is a test method that
* must throw the designated exception to pass.
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface ExceptionTest {
	/**
	* The exception that the annotated test method must throw
	* in order to pass. (The test is permitted to throw any
	* subtype of the type described by this class object.)
	*/
	Class<? extends Throwable> value();
}
* @param <K> the type of keys maintained by this map
* @param <V> the type of mapped values
*
* @author  Josh Bloch
* @see HashMap
* @see TreeMap
* @see Hashtable
* @see SortedMap
* @see Collection
* @see Set
* @since 1.2
*/
public interface Map<K, V> { ... }
public abstract class AbstractMemberBuilder extends AbstractBuilder {
	/**
	 * Returns true if this subbuilder has anything to document.
	 *
	 * @return true if this subbuilder has anything to document
	 */
	public abstract boolean hasMembersToDocument();
	...
}
public class MethodBuilder extends AbstractMemberBuilder {

	/**
	 * {@inheritDoc}
	 */
	@Override
	public boolean hasMembersToDocument() {
	    return !methods.isEmpty();
	}
}

Leave a Reply