아이템 13 - clone 재정의는 주의해서 진행하라 - 완벽 공략
이 글은 백기선 님의 이펙티브 자바 강의와 이펙티브 자바 3 / E 편을 참고하여 작성하였습니다.
UncheckedException
우리는 보통 UncheckedException 을 선호햔다.
public class MyException extends RuntimeException {
}
UncheckedException 은 RuntimeException 을 상속 받은 Exception 이다.
public class MyException extends Error {
}
Error 를 상속받은 클래스들 역시 UncheckedException 이라고 부를 수 있다.
RuntimeException 과 Error 를 확장한 클래스들은 UncheckedException 이다.
우리는 왜 UncheckedException 을 선호할까?
public class MyApp {
public void hello(String name) {
throw new MyException();
}
public static void main(String[] args) {
MyApp myApp = new MyApp();
myApp.hello("junhyun");
}
}
UncheckedException 던지는 코드는 작성이 쉽다.
호출을 하더라도 사용하는 쪽에서 조치를 할 필요가 없다.
만약 CheckedException 이라면
public class MyException extends Exception {
}
public class MyApp {
public void hello(String name) {
throw new MyException();
}
public static void main(String[] args) {
MyApp myApp = new MyApp();
try {
myApp.hello("푸틴");
} catch (MyException e) {
e.printStackTrace();
}
}
}
해당 코드를 호출하는 쪽에서 try-catch
블럭을 사용하거나
public void hello(String name) throws MyException {
throw new MyException();
}
throws
를 사용해 에러를 던져야한다.
만약 throws
를 하게되면 해당 코드를 받는 쪽에서 다시한번 처리를 해주어야한다.
이러한 번거로움 떄문에 UncheckedException 을 선호하게 된다.
하지만 이러한 이유만으로 UncheckedException 사용을 결정하는 건 잘못된 선택이다.
절대 편리하다는 이유만으로 UncheckedException 을 사용하면 안된다.
CheckedException 의 try-catch
나 throws
가 있는 이유는
CheckedException 자체만으로 API 이기 떄문이다.
클라이언트에게 해당 메서드 호출 시 CheckedException 이 발생할 수 있음을 알려준다.
public class MyApp {
/**
*
* @param name
* @throws MyException
*/
public void hello(String name) throws MyException {
if (name.equals("푸틴")) {
throw new MyException();
}
System.out.println("hello");
}
public static void main(String[] args) {
MyApp myApp = new MyApp();
try {
myApp.hello("푸틴");
} catch (MyException e) {
e.printStackTrace();
}
}
}
위의 코드는 잘못된 값이 들어왔을 떄 예외를 던지게 된다.
잘못된 값이 들어오면 코드를 사용하는 쪽에서 대응을 할 수 있다.
무언가 복구를 하는 시도를 할 수 있도록 API 를 사용하는 쪽에 정보를 제공한다.
이떄 CheckedException 은 문서화 가 되어 있어야한다.
public class MyApp {
/**
*
* @param name
* @throws MyException
*/
public void hello(String name) throws MyException, NullPointerException {
if (name.equals("푸틴")) {
throw new MyException();
}
System.out.println("hello");
}
public static void main(String[] args) {
MyApp myApp = new MyApp();
try {
myApp.hello("푸틴");
} catch (MyException e) {
e.printStackTrace();
}
}
}
위의 코드와 같이 UncheckedException 계열의 예외 역시 try-catch
나 throws
를 사용할 수 있다.
하지만 UncheckedException 은 복구할 수 있는 방법이 없기 때문에 try-catch
나 throws
를 강제 하지 않는다.
UncheckedException 은 언제 어디서든 발생할 수 있기 때문에 try-catch
나 throws
를 사용하면
비효율적인 동시에 프로그램의 명확도를 떨어뜨린다.
클라이언트 코드에서 해당 예외를 복구할 수 있는 방법 이 있다면 CheckedException 을 사용하도록 하자.
TreeSet
TreeSet 은 AbstractSet 확장해 만든 정렬된 컬렉션이다.
정렬된 컬렉션이란 데이터를 넣을때부터 정렬이 된 상태로 들어간다는 걸 뜻한다.
엘리먼트를 추가한 순서는 중요하지 않고 기본적으로 오름차순 으로 정렬한다.
데이터를 넣을 때 엘리먼트가 지닌 자연적인 순서(natural order) 를 사용해 정렬한다.
자바에서 제공하는 기본적인 타입들에는 자연적인 순서(natural order) 가 구현되어있다.
만약 우리가 생성한 클래스같은 경우에는 우리가 자연적인 순서(natural order) 를 정의해주어야한다.
자연적인 순서(natural order) 정의는 Comparable 인터페이스를 통해 구현한다.
TreeSet 은 Thread-safety 하지 않으므로 다중 스레드 환경에서 정렬된 컬렉션을 사용하고 싶다면
Collections 를 통해 Thread-safety 한 Set 을 얻어오면 된다.
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<Integer> numbers = new TreeSet<>();
numbers.add(10);
numbers.add(4);
numbers.add(6);
for (PhoneNumber number : numbers) {
System.out.println(number);
}
}
}
엘리먼트를 추가한 순서와 상관없이 엘리먼트들이 정렬되어 있는 걸 확인할 수 있다.
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<PhoneNumber> numbers = new TreeSet<>();
phoneNumbers.add(new PhoneNumber(123, 456, 780));
phoneNumbers.add(new PhoneNumber(123, 456, 7890));
phoneNumbers.add(new PhoneNumber(123, 456, 789));
for (PhoneNumber number : numbers) {
System.out.println(number);
}
}
}
우리가 생성한 클래스의 인스턴스를 TreeSet 에 넣으면 에러가 발생한다.
자연적인 순서(natural order) 가 없기 때문이다.
TreeSet 에 값을 넣으면 내부적으로 Comparable 타입으로 형변환을 해 자연적인 순서(natural order) 를 알아내려한다.
우리가 생성한 클래스에는 Comparable 이 구현되어 있지 않기 때문에 에러가 발생한다.
우리가 생성한 클래스에 Comparable 를 구현하거나
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<PhoneNumber> numbers = new TreeSet<>(Comparator.comparingInt(PhoneNumber::hashCode));
Set<PhoneNumber> phoneNumbers = Collections.synchronizedSet(numbers);
phoneNumbers.add(new PhoneNumber(123, 456, 780));
phoneNumbers.add(new PhoneNumber(123, 456, 7890));
phoneNumbers.add(new PhoneNumber(123, 456, 789));
for (PhoneNumber number : numbers) {
System.out.println(number);
}
}
}
TreeSet 생성시 자연적인 순서(natural order) 를 넘겨주는 방법이 있다.
TreeSet<PhoneNumber> numbers = new TreeSet<>(Comparator.comparingInt(PhoneNumber::hashCode));
Comparator.comparingInt()
를 통해 자연적인 순서(natural order) 를 넘겨주는 방법이다.
public class TreeSetExample {
public static void main(String[] args) {
TreeSet<PhoneNumber> numbers = new TreeSet<>(Comparator.comparingInt(PhoneNumber::hashCode));
Set<PhoneNumber> phoneNumbers = Collections.synchronizedSet(numbers);
phoneNumbers.add(new PhoneNumber(123, 456, 780));
phoneNumbers.add(new PhoneNumber(123, 456, 7890));
phoneNumbers.add(new PhoneNumber(123, 456, 789));
for (PhoneNumber number : numbers) {
System.out.println(number);
}
}
}
Set<PhoneNumber> phoneNumbers = Collections.synchronizedSet(numbers);
Collections 에 있는 synchronizedSet
을 통해 Thread-safety 한 Set 을 얻을 수 있다.
HashSet 은 내부적으로 이진 검색 트리 중 레드 블랙 트리 를 사용한다.
데이터를 넣거나 꺼낼 때 O(logN)
만큼의 시간이 걸린다.
TreeSet 은 내부적으로 TreeMap 을 사용한다.
TreeMap 은 정렬된 Map 이다.
'개발 공부 > Java' 카테고리의 다른 글
이펙티브 자바 아이템 14 - Comparable 을 구현할지 고민하라 - 완벽 공략 (0) | 2022.10.31 |
---|---|
이펙티브 자바 아이템 14 - Comparable 을 구현할지 고민하라 - 핵심 정리 (0) | 2022.10.31 |
이펙티브 자바 아이템 13 - clone 재정의는 주의해서 진행하라 - 핵심 정리 (0) | 2022.10.27 |
이펙티브 자바 아이템 12 - toString 을 항상 재정의하라 - 핵심 정리 (0) | 2022.10.26 |
이펙티브 자바 아이템 11 - equals 를 재정의하려거든 hashCode 도 재정의하라 - 완벽 공략 (0) | 2022.10.26 |
댓글