作为设计某些 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;
}
此时,您将受益于成为真正恒定的 enumeration 值(以及随之而来的所有好处),以及 interface 的独特实现,但您拥有全局enum 所需的可访问性。
显然,这会增加冗长,从而可能导致复制/粘贴错误。您可以将enums 设为public 并简单地为他们的访问添加一个额外的层。
倾向于使用这些功能的设计往往会受到脆弱的equals 实现的影响,因为它们通常与其他一些独特的值(例如名称)结合在一起,这些值可能会在代码库中出于相似但不同的目的而不知不觉地重复.通过全面使用enums,平等是一种免费赠品,不受这种脆弱行为的影响。
除了冗长之外,此类系统的主要缺点是在全局唯一键之间来回转换的想法(例如,与 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;
}
这提供了与第一个选项相同的灵活性,并且它提供了一种通过反射获取引用的机制,如果以后有必要,因此避免了以后需要实例化。它还避免了第一个选项提供的许多容易出错的复制/粘贴错误,因为如果第一个方法错误,它将无法编译,而第二个方法不需要更改。唯一需要注意的是,您应该确保以这种方式使用的enums 是public,以避免任何人因为无法访问内部enum 而出现访问错误;如果您不想让那些MetaDataKeys 穿过编组线,那么可以使用将它们隐藏在外部包之外来自动丢弃它们(在编组期间,反射性地检查enum 是否可以访问,如果不是,然后忽略键/值)。如果保持更明显的static 引用(因为enum 实例就是这样),则将其设为public 除了提供两种访问实例的方法外,没有任何收获或损失。
我只是希望他们能够做到这一点,以便enums 可以在 Java 中扩展对象。也许在 Java 9 中?
最后一个选项并不能真正解决您的需求,因为您要求的是价值,但我怀疑这会达到实际目标。