/**
* (현재 값 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,"전략");
// 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))
}
// 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.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();
}
}