문자열은 잘못 사용하면 번거롭고, 덜 유연하고, 느리고, 오류 가능성도 크다.
더 적합한 데이터 타입이 있거나 새로 만들 수 있다면, 문자열을 쓰고 싶은 유혹을 뿌리쳐라.
1️⃣ 문자열은 다른 값 타입을 대신하기에 적합하지 않다.
- 입력받을 데이터가 진짜 문자열일 때만 사용하는 것이 좋다.
- 받을 데이터가 수치형 → int, float, double, BigInteger 등 적당한 수치 타입으로 변환하라.
- 받을 데이터가 참/거짓 → boolean 혹은 Enum
- 기본 타입이든 참조 타입이든 적절한 값 타입이 있다면 그걸 사용하고, 없으면 새로 하나 만들어라.
2️⃣ 문자열은 열거 타입을 대신하기에 적합하지 않다.
public static final int APPLE_FUJI = 0;
public static final int APPLE_PIPPIN = 1;
public static final int APPLE_GRANNY_SMITH = 2;
public static final int ORANGE_NAVEL = 0;
public static final int ORANGE_TEMPLE = 1;
public static final int ORANGE_BLOOD = 2;
하지 마라. 진짜 하지 마라.
- 정수 열거 패턴의 문제점
- 타입 안정성 보장 힘들다.
- 별도의 이름 공간이 없어서 접두어를 사용하여 이름 충돌 방지해야 한다.
- 프로그램이 깨지기 쉽다.
- 정수 상수는 문자열로 출력하기도 힘들다.
- 문자열 패턴의 문제점
- 상수의 의미를 출력하는 것은 좋지만 하드 코딩 해야 한다.
- 오타가 있어도 컴파일러가 확인할 방도가 없다.
3️⃣ 문자열은 혼합 타입을 대신하기에 적합하지 않다.
String compoundKey = className + "#" + i.next();
- 구분자로 쓰인 "#"이 두 요소 중 하나에서 쓰였다면 혼란스러운 결과를 초래한다.
- 각 요소를 분리해서 사용하기 위해 파싱을 해야 해서 느리고, 귀찮고, 오류 가능성을 증가시킨다.
- equals, toString, compareTo 메서드 제공이 불가능하며, String 클래스 메서드에 의존해야 한다.
🤚 이따구로 할 바에 전용 클래스를 새로 만들어라! (Item 24. private 정적 멤버 클래스)
public class Compound {
private CompoundKey compoundKey;
public Compound(int itemNum, String chapter) {
this.compoundKey = new CompoundKey(itemNum, chapter);
}
private static class CompoundKey {
private int itemNum;
private String chapter;
public CompoundKey(int itemNum, String chapter) {
this.itemNum = itemNum;
this.chapter = chapter;
}
}
public int getItemNum() {
return compoundKey.itemNum;
}
public String getChapter() {
return compoundKey.chapter;
}
}
class Main {
public static void main(String[] args) {
ArrayList<Integer> itemList = new ArrayList<>(List.of(60,61,62));
Iterator<Integer> item = itemList.iterator();
Compound student1 = new Compound(item.next(), "정확한 답이 필요하다면 float와 double은 피하라");
Compound student2 = new Compound(item.next(), "박싱된 기본 타입보다는 기본 타입을 사용하라");
Compound student3 = new Compound(item.next(), "다른 타입이 적절하다면 문자열 사용을 피하라");
System.out.println("아이템 #" + student1.getItemNum() + " : " + student1.getChapter());
System.out.println("아이템 #" + student1.getItemNum() + " : " + student2.getChapter());
System.out.println("아이템 #" + student1.getItemNum() + " : " + student3.getChapter());
}
}
4️⃣ 문자열은 권한을 표현하기에 적합하지 않다.
권한(capacity)의 예시로는 ThreadLocal처럼 thread 별로 지역 변수를 갖게 해주는 경우가 있다.
{
int a = 10;
}
- 일반 변수의 경우에는 수명이 코드 블록 내에서만 가능하다.
public class ThreadLocal {
private ThreadLocal() { } //객체 생성 불가
// 현 스레드의 값을 키로 구분해 저장
public static void set(String key, Object value);
// (키가 가리키는) 현 스레드의 값을 반환한다.
public static Object get(String key);
}
- 직접 ThreadLocal을 구현해야 했던 Java2 이전에서는 결국 모든 개발자가 Client가 제공한 String 타입 key로 Thread별 지역 변수를 식별했다.
- 문제는 String 타입 key가 전역 namespace에서 공유되므로, 중복될 경우 같은 변수를 공유하게 된다. (보안에도 취약하다.)
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);
}
- 문자열 보다는 별도의 클래스(Key)로 분리하는 게 좋은 해법일 수 있다.
- 위조할 수 없는 이 Key를 권한(capacity)이라고도 한다.
// Key를 ThreadLocal로 변경
public final class ThreadLocal {
public ThreadLocal();
public void set(Object value);
public Object get();
}
- set, get이 정적 메서드일 이유가 없으므로 Key 클래스의 인스턴스 메서드로 바꾸었다.
- 이러면 Key는 더 이상 Thread 지역변수 구분을 위한 키가 아니라, 그 자체로 Thread 지역변수가 된다.
- 위 코드는 Key를 ThreadLocal로 바꿔버린 형태다.
// 매개변수화로 타입안전성 확보
public final class ThreadLocal<T> {
public ThreadLocal();
public void set(T value);
public T get();
}
- 이전 방식은 get으로 얻은 Object를 실제 타입으로 형변환 해야 하므로 타입 안전하지 않다.
- 문자열 기반 API나, Key를 사용한 API 둘 다 타입 안전하게 만들기 어렵다.
- ThreadLocal을 매개변수화 타입으로 선언하면 해결할 수 있다.
- 이 클래스는 문자열 기반 API 문제를 해결해주며, 키 기반 API보다 빠르고 우아하다.
🤔 사용 방법에 대하여
public class ThreadLocalTest {
private static final ThreadLocal<String> threadLocal = new ThreadLocal<>();
public static void main(String[] args) {
threadLocal.set("Value 1");
Thread thread1 = new Thread(() -> {
threadLocal.set("Value 2");
System.out.println("Thread 1: " + threadLocal.get()); // Output: Thread 1: Value 2
});
Thread thread2 = new Thread(() -> {
threadLocal.set("Value 3");
System.out.println("Thread 2: " + threadLocal.get()); // Output: Thread 2: Value 3
});
thread1.start();
thread2.start();
System.out.println("Main Thread: " + threadLocal.get()); // Output: Main Thread: Value 1
}
}
- ThreadLocal 객체가 내부적으로 key를 생성하여 저장하고 관리하므로 안전하다.