핵심 정리
핵심은 데이터의 성격에 맞는 적절한 데이터 타입을 사용하라는 것이다.
적절한 데이터 타입이 없다면, 새로운 데이터 타입을 작성하는 것도 좋은 방법이다.
문자열(String) 사용에 대한 주의점
: 문자열을 다른 데이터 타입의 대용으로 사용하지 마라.
- 문자열은 다양한 데이터를 표현하는 데 편리하지만, 데이터가 원래 다른 타입(수치, 객체 등)일 경우에는 해당 타입을 직접 사용하는 것이 좋다.
- 예를 들어, 파일, 네트워크, 사용자 입력으로부터 데이터를 받을 때, 이 데이터가 숫자나 다른 타입(수치, 객체 등)일 경우에는 해당 타입을 직접 사용하는 것이 좋다.
- 즉, 숫자를 입력 받는 경우에는 문자열 대신 int, float, BigInteger와 같은 적절한 수치형 타입으로 변환해야 하며, 사용자 정의 객체를 입력 받는 경우에는 해당 객체의 타입으로 변환해야 한다.
문자열(String) 사용에 대한 주의점
: 문자열은 열거 타입을 대신하기에 적합하지 않다.(item 34)
아이템 34에서 다뤘으나 간략하게 되짚어보자.
- 타입 안전(Type-Safe): 문자열이 틀렸거나 잘못 입력되는 경우를 컴파일 시에 잡아낼 수 있다. 반면 문자열은 컴파일 시 오타나 오류를 발견하기 어렵다.
- 코드 가독성: 열거 타입을 사용하면 해당 값이 어떤 목적으로 사용되는지 코드를 통해 명확하게 알 수 있다.
- 기능의 확장성: 열거 타입에는 메서드를 추가하거나 필드를 정의할 수 있다. 즉, 각 상수가 자신만의 동작를 정의할 수 있다.
- 싱글턴 특성: 각각의 Enum 값은 하나의 인스턴스로서 싱글턴 특성을 가진다.
- 네임스페이스: 열거 타입은 상수 값들을 그룹으로 묶을 수 있다. 이는 이름 충돌을 방지하고 관련된 상수 값들을 구조화한다.
문자열(String) 사용에 대한 주의점
: 문자열은 혼합 타입을 대신하기에 적합하지 않다.
여러 종류의 값을 하나의 문자열로 합치는 것은 적절하지 않다는 뜻이다.
예시 1 - 여러 요소가 혼합된 데이터를 하나의 문자열로 표현한 경우
String compoundKey = className + "#" + i.next();
- 문제점
- parsing이 필요하다.
- 문자열에서 각 요소를 추출하려면 복잡한 문자열 파싱 과정이 필요하다.
- 처리 시간이 오래 걸리며, 오류를 유발할 가능성이 있다.
- 각 요소별로 적용할 수 있는 기능이 제한적이다.
- 예를 들어, 각 요소에 대해 별도의 equals(), toString(), compareTo() 메소드를 제공할 수 없다.
- parsing이 필요하다.
- 해결 방법
- 각 요소를 별도의 필드로 갖는 전용 클래스로 만든다.
- 이 클래스는 주로 private static 멤버 클래스로 선언한다.(item 24)
public class Student {
// ...
private static class Info {
String name;
int number;
Info(String name, int number) {
this.name = name;
this.number = number;
}
// equals, hashCode, toString 등의 메소드를 재정의할 수 있다.
}
}
문자열(String) 사용에 대한 주의점
: 문자열은 권한을 표현하기에 적합하지 않다.
예시 2
// 잘못된 예 - 문자열을 사용해 권한을 구분하였다.
public class ThreadLocal {
private ThreadLocal() {} // 객체 생성 불가
// 현 스레드의 값을 키로 구분해 저장
public static void set(String key, Object value);
// (키가 가리키는) 현 스레드의 값을 변환하다.
public static Object get(String key);
}
- 문제점
- 문자열 키는 전력 네임스페이스를 사용한다. 즉, 모든 스레드가 동일한 키 네임스페이스를 공유한다.
- 따라서 다른 스레드에서 동일한 키를 사용해 set을 호출하면, 현재 스레드의 값이 덮어씌워진다.
- 이는 예상치 못한 부작용을 초래하고, 결국 잘못된 결과를 일으킨다.
- 보안 또한 취약하다. 악의적인 클라이언트가 다른 클라이언트의 값을 get할 수 있다.
// 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(Key key);
}
- 코드 설명
- ThreadLocal 클래스의 생성자는 private로 선언되어 있어, 외부에서 인스턴스화할 수 없다.
- Key는 ThreadLocal 클래스 내부에 정의된 정적 내부 클래스이다.
- 이 클래스의 인스턴스는 고유한 키 역할을 한다.
- Key의 생성자 역시 private으로 선언되어 있어서 이 클래스는 오직 ThreadLocal 클래스에서만 인스턴스화할 수 있다.
- getKey() 메소드는 Key 클래스의 새로운 인스턴스를 생성하고 반환한다.
- 개선할 점
- set()과 get()은 정적 메소드일 필요가 없으니 Key 클래스의 인스턴스 메소드로 바꿀 수 있다.
- 따라서 Key 인스턴스는 스레드 지연 변수 그 자체가 되었다.
- 결과적으로 ThreadLocal 클래스가 필요없으니 제거하자.
이를 반영한 코드는 아래와 같다.
// 리팩터링하여 Key를 ThreadLocal로 변경
public final class ThreadLocal {
public ThreadLocal() { }
public void set(Object value);
public Object get();
}
- 코드 문제점
- get 메소드가 Object 타입을 반환하므로, 이 값을 사용하려면 적절한 타입으로 형변환해야 한다.
- 만약 잘못된 타입으로 형변환을 시도하면 ClassCastException이 발생하게 된다.
// 매개변수화하여 타입안전성 확보
public final class ThreadLocal<T> {
public ThreadLocal();
public void set(T value);
public T get();
}
- 제네릭을 도입하여 잘못된 형변환으로 인한 오류를 줄인다.
'Language > Java' 카테고리의 다른 글
| [effective java] 아이템 64. 객체는 인터페이스를 사용해 참조하라. (0) | 2023.07.27 |
|---|---|
| [effective java] 아이템 63. 문자열 연결은 느리니 주의하라. (0) | 2023.07.27 |
| [effective java] 아이템 61. 박싱된 기본 타입보다는 기본 타입을 사용하라. (0) | 2023.07.27 |
| [effective java] 아이템 60. 정확한 답이 필요하다면 float와 double은 피하라. (0) | 2023.07.17 |
| [effective java] 아이템 59. 라이브러리를 익히고 사용하라. (0) | 2023.07.17 |