public static String[] unjoinToArray(String rawSection) {
if (StringUtils.isBlank(rawSection)) {
return null; // null을 리턴하는게 좋은 방법은 아니지만..! 일단은 아래 split에서의 NPE 방지
}
return rawSection.split(SECTION_SEPARATOR);
}
// 코드 7-2
public Object pop() {
if (size == 0)
throw new EmptyStackException();
Object result = elements[--size];
elements[size] = null; // 다 쓴 참조 해제
return result;
}
// if(size == 0)이 없어도 예외는 던져지긴 하지만,
// 이후 로직에서 size의 값이 음수가 되기 때문에 다음번 호출에서 발생할 수 있는 ArrayIndexOutOfBoundsException은 상황에 어울리지 않음!
### 방법 3. 실패할 가능성이 있는 모든 코드를, 객체의 상태를 바꾸는 코드보다 앞에 배치한다.
- 계산을 수행해보기 전에 인수의 유효성을 검사하기 어려울 때 사용할 수 있는 방법입니다.
- ```TreeMap.java```를 예시로 생각해봅시다.
> 참고(StackOverflow) : [Why does TreeSet throw a ClassCastException?](https://stackoverflow.com/questions/15943031/why-does-treeset-throw-a-classcastexception)
```java
public static void main(String[] args) {
Map<FakeKey, String> s = new TreeMap<>();
s.put(new FakeKey(), "1"); // ClassCastExcpetion 발생
s.put(new FakeKey(), "2");
}
Exception in thread "main" java.lang.ClassCastException: org.nypark.study.effectivejava3.item76.FakeKey cannot be cast to java.base/java.lang.Comparable
at java.base/java.util.TreeMap.compare(TreeMap.java:1291)
at java.base/java.util.TreeMap.put(TreeMap.java:536)
at org.nypark.study.effectivejava3.item76.Item76Application.main(Item76Application.java:14)
// TreeMap.put
/**
* Associates the specified value with the specified key in this map.
* If the map previously contained a mapping for the key, the old
* value is replaced.
*
* ...생략...
*
* @throws ClassCastException if the specified key cannot be compared
* with the keys currently in the map
* ...생략...
*
*/
public V put(K key, V value) {
Entry<K,V> t = root;
if (t == null) {
compare(key, key); // type (and possibly null) check, 요기서 Exception 발생!
root = new Entry<>(key, value, null);
size = 1;
modCount++;
return null;
}
// 생략 ...
}
// TreeMap.compare
/**
* Compares two keys using the correct comparison method for this TreeMap.
*/
@SuppressWarnings("unchecked")
final int compare(Object k1, Object k2) {
return comparator==null ? ((Comparable<? super K>)k1).compareTo((K)k2)
: comparator.compare((K)k1, (K)k2);
}
방법 4. 객체의 임시 복사본에서 작업을 수행한 다음, 작업이 성공적으로 완료되면 원래 객체와 교체한다.
데이터를 임시 자료구조에 저장해 작업하는게 더 빠를 때 적용하기 좋은 방법!
참고(StackOverflow) : Why does Java's sort implementation convert a list to an array before sorting?
참고(javadocs) : java.util.List#sort(Comparator)
The default implementation obtains an array containing all elements in this list, sorts the array, and iterates over this list resetting each element from the corresponding position in the array. (This avoids the n2 log(n) performance that would result from attempting to sort a linked list in place.)
// List.java > sort
default void sort(Comparator<? super E> c) {
Object[] a = this.toArray(); // 정렬하기 전에 배열로 변환. 정렬 알고리즘에서 배열을 이용하면 성능이 향상되기 때문
Arrays.sort(a, (Comparator) c);
ListIterator<E> i = this.listIterator();
for (Object e : a) {
i.next();
i.set((E) e);
}
}
방법 5. 작업 도중 발생하는 실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 되돌린다.
(디스크 기반의) 내구성(durability)를 보장해야 하는 자료구조에 쓰임
자주 쓰이진 않습니다.
실패 원자성은 무조건 지켜야 하나요?
권장되는 덕목이지만 항상 달성할 수 있는 것은 아닙니다.
두 스레드가 동기화 없이 같은 객체를 동시에 수정한다면 객체의 일관성이 깨집니다.
이때 ConcurrentModificationException을 잡아냈다고 해도 그 객체가 여전히 사용가능한 상태라고 가정하면 위험!!
Error는 복구할 수 없으므로, AssertionError에 대해서는 실패 원자적으로 만들려는 시도조차 할 필요가 없습니다.
실패 원자적으로 만들 순 있어도, 이를 달성하기 위한 비용과 복잡도가 어마어마하다면 고민을 해보는 것도...
그래도 원인을 찾는다면 실패 원자성을 공짜로 얻는 경우가 더 많습니다!
메서드 명세에 기술한 예외의 경우, 예외가 발생하면 객체의 상태는 메서드 호출 전과 똑같이 유지되어야하는 것이 기본 규칙입니다.
실패 원자적(failure-atomic)이란?
방법 1. 불변 객체(#17)로 설계한다.
방법 2. 로직을 수행하기에 앞서 매개변수의 유효성을 검사한다.
// if(size == 0)이 없어도 예외는 던져지긴 하지만, // 이후 로직에서 size의 값이 음수가 되기 때문에 다음번 호출에서 발생할 수 있는 ArrayIndexOutOfBoundsException은 상황에 어울리지 않음!
방법 4. 객체의 임시 복사본에서 작업을 수행한 다음, 작업이 성공적으로 완료되면 원래 객체와 교체한다.
방법 5. 작업 도중 발생하는 실패를 가로채는 복구 코드를 작성하여 작업 전 상태로 되돌린다.
실패 원자성은 무조건 지켜야 하나요?
Error
는 복구할 수 없으므로,AssertionError
에 대해서는 실패 원자적으로 만들려는 시도조차 할 필요가 없습니다.