特定の API を設計する Java 開発者として、この問題に頻繁に遭遇します。この投稿に出くわしたとき、私は自分の疑問を再確認していましたが、詳細な回避策があります。
// class name is awful for this example, but it will make more sense if you
// read further
public interface MetaDataKey<T extends Serializable> extends Serializable
{
T getValue();
}
public final class TypeSafeKeys
{
static enum StringKeys implements MetaDataKey<String>
{
A1("key1");
private final String value;
StringKeys(String value) { this.value = value; }
@Override
public String getValue() { return value; }
}
static enum IntegerKeys implements MetaDataKey<Integer>
{
A2(0);
private final Integer value;
IntegerKeys (Integer value) { this.value = value; }
@Override
public Integer getValue() { return value; }
}
public static final MetaDataKey<String> A1 = StringKeys.A1;
public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}
その時点で、真に一定のenum
eration 値 (およびそれに付随するすべての特典) であり、 の独自の実装であるという利点が得られinterface
ますが、 が必要とするグローバルなアクセシビリティが得られますenum
。
明らかに、これにより冗長性が増し、コピー/貼り付けのミスが発生する可能性が生じます。enum
sを作成public
して、アクセスにレイヤーを追加するだけです。
これらの機能を使用する傾向がある設計は、脆弱なequals
実装に悩まされる傾向があります。これは、通常、名前などの他の一意の値と結合されているためです。これらの値は、コードベース全体で無意識のうちに類似しているが異なる目的で複製される可能性があります。enum
全面的に s を使用することにより、平等は、そのような脆弱な動作の影響を受けない景品となります。
システムなどの主な欠点は、冗長性を超えて、グローバルに一意のキー間で相互に変換するという考え方です (たとえば、JSON との間のマーシャリング)。それらが単なるキーである場合、メモリを浪費して安全に再インスタンス化 (複製) できますが、以前は弱点equals
だったものを利点として使用します。
これには回避策があり、グローバル インスタンスごとに匿名型を乱雑にすることで、グローバルな実装の一意性を提供します。
public abstract class BasicMetaDataKey<T extends Serializable>
implements MetaDataKey<T>
{
private final T value;
public BasicMetaDataKey(T value)
{
this.value = value;
}
@Override
public T getValue()
{
return value;
}
// @Override equals
// @Override hashCode
}
public final class TypeSafeKeys
{
public static final MetaDataKey<String> A1 =
new BasicMetaDataKey<String>("value") {};
public static final MetaDataKey<Integer> A2 =
new BasicMetaDataKey<Integer>(0) {};
}
各インスタンスは匿名の実装を使用しますが、それを実装するために他に何も必要ないため、{}
は空であることに注意してください。これは紛らわしく面倒ですが、経験の浅い Java 開発者にはわかりにくいかもしれませんが、インスタンス参照が望ましく、混乱を最小限に抑えれば機能します。
最後に、グローバルな独自性と再割り当てを提供する唯一の方法は、起こっていることに対してもう少し創造的になることです。私が見たグローバル共有インターフェイスの最も一般的な用途は、多くの異なる値と異なるタイプ (T
キーごとの )を混在させる傾向があるメタデータ バケットです。
public interface MetaDataKey<T extends Serializable> extends Serializable
{
Class<T> getType();
String getName();
}
public final class TypeSafeKeys
{
public static enum StringKeys implements MetaDataKey<String>
{
A1;
@Override
public Class<String> getType() { return String.class; }
@Override
public String getName()
{
return getDeclaringClass().getName() + "." + name();
}
}
public static enum IntegerKeys implements MetaDataKey<Integer>
{
A2;
@Override
public Class<Integer> getType() { return Integer.class; }
@Override
public String getName()
{
return getDeclaringClass().getName() + "." + name();
}
}
public static final MetaDataKey<String> A1 = StringKeys.A1;
public static final MetaDataKey<Integer> A2 = IntegerKeys.A2;
}
これにより、最初のオプションと同じ柔軟性が提供され、後で必要になった場合にリフレクションを介して参照を取得するメカニズムが提供されるため、後でインスタンス化可能にする必要がなくなります。また、最初の方法が間違っているとコンパイルされず、2 番目の方法を変更する必要がないため、最初の方法で発生するエラーが発生しやすいコピー/貼り付けの間違いの多くを回避できます。唯一の注意点は、そのenum
ように使用されることを意図した s がpublic
、内部にアクセスできないためにアクセス エラーが発生しないようにすることenum
です。それらをマーシャリングされたワイヤに渡したくない場合は、MetaDataKey
それらをパッケージの外部から隠しておくことで、それらを自動的に破棄することができます (マーシャリング中に、enum
アクセス可能でない場合は、キー/値を無視します)。public
インスタンスにアクセスする 2 つの方法を提供することを除いて、インスタンスをstatic
作成することによって得られることも失われることもありませんenum
。
enum
s が Java でオブジェクトを拡張できるようにしてくれればよかったのにと思います。多分Java 9で?
値を求めていたので、最後のオプションは実際にはニーズを解決しませんが、これは実際の目標に向かっていると思います。