我正在使用Java 6.
假设我有一个类,我想将其实例保存到地图中。稍后我想仅使用“关键字段”检索实例。我将忽略字段修饰符,getter和setter以简洁。
class A {
String field1;
String field2;
String field3;
String field4;
//more fields
public int hashCode(){
//uses only field1 and field2
}
public boolean equals(Object o){
//uses only field1 and field2
}
}
由于Java的标准API没有MultikeyMap,我不想使用第三方库,我可以选择
1)创建一个新的类KeyA
来表示地图的关键字
2)当我需要从地图中检索对象时,使用A
本身作为键并仅填充“关键字段”
3)嵌套地图,例如HashMap<String, HashMap<String, A>>
4)其他解决方法
人们通常使用什么以及何时使用?
答案 0 :(得分:2)
鉴于您最近的编辑,在这种情况下,您应该可以使用A类实例作为键。查找将基于equals()
和hashCode()
的语义完成,因此这将导致仅通过“关键字段”检索实例。因此,以下代码可以按预期工作:
final Map<A, String> map = new HashMap<A, Object>();
final A first = new A("fe", "fi", "fo", "fum");
map.put(first, "success");
// later on
final A second = new A ("fe", "fi", "foo", "bar");
System.out.println(map.get(second)); // prints "success";
话虽如此,您对选项2的描述让我有点担心这可能不是最明智的选择。如果您创建Map<A, String>
,那就是从类A 的实例到字符串的映射。然而,您的第二点暗示您希望将其视为从对关键字段到字符串的映射。如果您通常会根据一些“原始”字符串查找值,那么我建议不要这样做。对我来说,创建A的“假”实例只是为了进行查找是错误的 - 所以在这种情况下,你可能应该创建一个包含选项1中描述的字符串对的键类。(你可以甚至在你的A
对象中嵌入这些实例来保存关键字段。
对选项3也有类似的论据。如果字符串确实在概念上是分层的,那么它可能很有意义。例如,如果field1
是国家/地区,而field2
是城镇,则可以肯定地说嵌套地图是有意义的 - 您有从国家/地图到城镇地图的映射 - &gt;内部关系那个国家。但是如果你的密钥不以这种方式自然地组成(比如,如果它们是(x,y)坐标),那么这也不是表示数据的非常自然的方式,而是来自{{1的单级映射价值会更明智。 (同样地,如果你从不使用两级地图,除了总是直接穿过两个层,人们可能会认为单层地图仍然更有意义。)
最后,对于选项4 - 如果你总是映射到XYPoint
本身,并将密钥存储为自己的值(例如,如果你想规范你的{{ 1}}实例,有点像A
)然后根据指出你根本不需要使用A
,而String.intern()
将在这里完成工作。当您想要在不同的对象之间建立关系时,Map
非常有用,而Set
会自动为您提供对象的唯一性,而不会产生任何额外的概念开销。
如果您确实使用类本身作为键,请注意,如果对象Map
(以及Set
的行为)不会随时间变化,通常只能用作键。 。通常这意味着密钥是不可变的,但在这里你可以负担得到可变的“非密钥”字段。如果你打破这个规则,你会看到奇怪的行为,如下所示:
hashCode
答案 1 :(得分:0)
我创建了一个Index
类,类似这样(警告:未经测试的代码),以抽象出索引功能。为什么Java没有这样的东西已经令我感到困惑。
interface Indexer<T, K>
{
/** extract key from index */
public K getIndexKey(T object);
}
class Index<T,K>
{
final private HashMap<K,List<T>> indexMap = new HashMap<K,List<T>>();
final private Indexer<T,K> indexer;
public Index(Indexer<T,K> indexer)
{
this.indexer = indexer;
}
public void add(T object) {
K key = this.indexer.getIndexKey(object);
List<T> values = this.indexMap.get(key);
if (values == null)
{
values = new ArrayList<T>();
this.indexMap.put(key, values);
}
values.add(object);
}
public void remove(T object) {
K key = this.indexer.getIndexKey(object);
List<T> values = this.indexMap.get(key);
if (values != null)
{
values.remove(object);
}
}
public List<T> lookup(K key) {
List<T> values = this.indexMap.get(key);
return values == null
? Collections.emptyList()
: Collections.unmodifiableList(values);
}
}
与您的class A
相关的示例:
Index<A,String> index1 = new Index<A,String>(new Indexer<A,String>() {
@Override public String getIndexKey(A object)
{
return object.field1;
}
});
Index<A,String> index2 = new Index<A,String>(new Indexer<A,String>() {
@Override public String getIndexKey(A object)
{
return object.field2;
}
});
/* repeat for all desired fields */
您需要手动添加和删除索引中的条目,但这些操作下面的所有垃圾工作都由Index类处理。
答案 2 :(得分:0)
您的班级有“关键字段”。我建议使用这些关键字段(当然映射到您域中的概念)创建父类ParentA
,并在子类A
中继承此类。
覆盖hashCode()
类中的equals()
和ParentA
。
使用Map<ParentA, A>
存储您的A
个实例,并将该实例作为键和值。
要检索特定的A
实例,请设置新的ParentA
实例pA
,并设置关键字段,然后执行
A a = map.get(pA);
就是这样。
另一种方法是使用关键字段创建AIdentifier
类,并将实例添加为A
属性id
。因此,您使用map.put(a.id, a);
添加实例,这是继承与组合模式讨论:)