62.他の型が適している場合にはStringを避けるべし
Stringが適していない場面でもStringが使われがち
本章では、Stringを使うべきではない場面について述べる。
他の型の代替として使うべきでない
ファイル、ネットワーク、キーボードからくるデータをStringとして扱うことが多いが、本当にStringとして扱う場合のみにとどめるべきである。
入ってくるデータが数字なのであれば、int、float、BigIntegerなどが、真偽値であればenumやbooleanが適切である。
より一般的には、値の適切な型があるのであれば、それを使うべきで、適切な型がないのであれば、作成すべきである。
このアドバイスは当たり前のようであるが、よくおこるミスである。
enumの代替として使うべきでない
Item34で述べられているが、Stringで定数の列挙を行うべきでない。enumを使うべし。
要素を集結させるもの(aggregate type)として使うべきでない
複数の要素を持つものがあったとして、それをStringで表現するのはbadアイデアだ。以下がそれにあたる。
// Inappropriate use of string as aggregate type
String compoundKey = className + "#" + i.next();
この方法には複数の悪い点がある。
- 区切り文字として使われている文字が要素に入ってくると面倒なことになる。
- 個々の要素を取り出すために、パースをしなければならないため、遅く、エラーを生み出しやすい。
- equals、toString、compareToといったメソッドを提供できないが、Stringが提供するメソッドは受け入れることを強制される。(?)
ベターな方法としては、単純に、要素をまとめるクラスを書くことだ。よくprivate staticなメンバークラスが作られる(Item24)。
偽造不可能なキー(capability)として使うべきでない
Stringは何らかの機能へのアクセス手段として使われることがある。
例として、ThreadLocalの変数格納機能を考えてみる。
ThreadLocalはそれぞれのスレッドの固有の値を格納できるものとして機能する。この機能は、Java1.2以降にJavaライブラリに現れたが、それ以前にこういった機能を設計しようとしたら、以下のようにStringを使って実現してしまうかもしれない。
// Broken - inappropriate use of string as capability!
public class ThreadLocal {
private ThreadLocal() { } // Noninstantiable
// Sets the current thread's value for the named variable.
public static void set(String key, Object value);
// Returns the current thread's value for the named variable.
public static Object get(String key);
}
上記のようにしたときの問題点は、Stringのキーがグローバルな名前空間として共有されてしまうことにある。
そのため、意図せず別々のユーザーが1つの変数を共有してしまうかもしれず、エラーを生むことになる。
また、セキュリティの観点からも良くない。
悪意のあるユーザーがわざと他のユーザーのキーと同じ値でThreadLocalから値を取得する可能性があるからだ。
上記を代替不可能なキー(unforgeable key または、capability)を使って書き換えると以下のようになる。
public class ThreadLocal {
private ThreadLocal() { } // Noninstantiable
public static class Key { // (Capability)
Key() { }
}
// Generates a unique, unforgeable key
public static Key getKey() {
return new Key();
}
public static void set(Key key, Object value);
public static Object get(Key key);
}
上記でStringのキーを使った場合の問題は解消されているが、さらに良い方法がある。
staticなメソッドはもう必要なく、インスタンスメソッドがキーとなる(?)。
その時点でキーはスレッドローカルな変数を指し示すキーではなく、キー自体がスレッドローカルな変数となっている(?)。
APIとしては以下のよう。
public final class ThreadLocal {
public ThreadLocal();
public void set(Object value);
public Object get();
}
上記のAPIから値を取り出すときはキャストをしなければならないので、このAPIはタイプセーフではない。
元のStringをキーとしたAPIや、Keyを使ったAPIでもタイプセーフにするのは難しいが、上記のAPIは以下のように、容易にタイプセーフにできる。
public final class ThreadLocal<T> {
public ThreadLocal();
public void set(T value);
public T get();
}
javaライブラリのThreadLocalもざっくりいうと上のようになっている。