Map<key, List/Set<objects of type defined by key>>

4
也许我在这里想错了,但是假设我想返回不同类型的一堆对象。例如,很多个人、个人资料、账户。我想把它们放在一个映射中,其中一个键将是Person.class之类的东西,而值将是Person实例的列表。首先,我考虑了EnumMap,但如果我可以以某种方式使用类本身,为什么要为类列表创建ENUM呢?
我试图用泛型来实现,但无法理解定义。
这可能吗?还是我的设计有问题?
我可以提供检索部分结果的不同方法。或者创建一个包含它的类。但Map更灵活,以防将来我想使用更多的类。
编辑:
我得到了一些答案,似乎没有特别针对我正在寻找的内容,因此需要澄清:
我想要的是:
{
 Person.class : [person1Instance, person2Instance,...], 
 Account.class : [account1Instance, account2Instance, account3Instance, ...], 
 Profile.class : [profile1Instance...]
}

我希望避免强制类型转换。可以利用密钥应定义项目列表的类型(安全性)这一事实来解决。


那么,这张地图看起来会是这样的吗?{Person.class: [Person1, Person2], Profile.class: [Profile1, Profile2], ...} - tobias_k
我认为使用普通的通用映射表是不可能实现这个功能的,但是你可以创建一个包装映射表的类,并带有一个类似<T> List<T> get(Class<T> key)的方法。 - tobias_k
1
也许我在这里想错了什么,但是假设我想返回不同类型的一堆对象。在强类型语言中,通常这种想法都是错误的。虽然有一些边缘情况下这种设计可能是合适的(如 DI 容器、ORM 映射器),但通常情况下并不是这样的。而且,在这种情况下,对象可以作为 Object 引用存储,因为它们只能通过反射来访问。 - Sebastian Redl
@tobias_k 没错。这个get方法看起来非常有趣,我会试一下。 - Ev0oD
4个回答

3
我已在TypedMap中实现了这个功能:http://blog.pdark.de/2010/05/28/type-safe-object-map/。这是一些演示代码:
TypedMap map = new TypedMap();

String expected = "Hallo";
map.set( KEY1, expected );
String value = map.get( KEY1 ); // Look Ma, no cast!
assertEquals( expected, value );

List<String> list = new ArrayList<String> ();
map.set( KEY2, list );
List<String> valueList = map.get( KEY2 ); // Even with generics
assertEquals( list, valueList );

魔力在于密钥:
final static TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
final static TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );

为了解决您的问题,您需要为要在地图中保存的不同类型创建键:
TypedMapKey<List<Account>> ACCOUNTS = new TypedMapKey<List<Account>>(  "ACCOUNTS" );

那么您需要常规的获取/创建代码:
public <T> List<T> getList( TypedMapKey<List<T>> key ) {
    List<T> result = map.get(key);
    if(null == result) {
        result = new ArrayList<T>();
        map.put(key, result);
    }
    return result;
}

这将允许您通过以下方式访问列表:

List<Account> accounts = getList(ACCOUNTS);

为什么我们不能使用您的TypedMapKey来使用private final Class<T> type;而不是字符串?类型将确保更安全和类型推断,而不是玩弄字符串,不是吗? - Ev0oD
在我的设计中,我需要一个具有最少依赖关系的键。但是您可以使用自己的键类型或扩展现有类型。虽然它不是“更类型安全”,但代码在编译时由编译器100%类型检查->如果它在您的一侧没有任何转换就编译通过,则不会出错。记住字段中实际的Java类型对此并非必要。 - Aaron Digulla

1

不允许使用不同类型的对象,因为在检索它们时,我们会面临一个问题,即我们不知道将要出现什么类型的对象。

这是我的建议:

创建类似地图的结构:

Map<Object, String> map = new HashMap<Object, String>();

让您放置一些值,例如:

Person p = new Person();

map.put(p, "Person");

Account a = new Account();

map.put(a, "Account");

假设您将传递不同的对象。
在检索类似以下内容时:
for(Entry<Object, String> entry : map.entrySet()) {
  String choiceClassName = entry.getValue();

 switch(choiceClassName) {
   case "Person" : Person p = (Person) entry.getKey();
break;

case "Account" : Account a = (Account) entry.getKey();

break;


} 

}

请看一下我编辑过的问题 - 我可能没有解释清楚,因此您的答案正在解决不同的问题。 - Ev0oD

0

你不需要任何枚举。你可以使用 instanceof 来检查对象的类型。这是我通常的做法,但我很好奇其他人的答案。

通常我只使用一个父类型和我需要用于我的映射的泛型方法(通常是接口)。 但有时我会在我真的不知道用户行为将放入地图的情况下使用此方法。

以这张地图为例:

HashMap<String,Object> stuff = new HashMap<String,Object>();
stuff.add("joe",new Person());
stuff.add("new york", new City());

Iterator it = stuff.iterator();
while(it.hasNext()) {
   Object obj = it.next();
   if(obj instanceof Person) {
      Person p = (Person)obj;
   }
   if(obj instanceof City) {
      City c = (City)obj;
   }
}

我本意是要一个对象列表,而不仅仅是哈希映射值中的一个对象。希望以某种方式拥有类型安全的对象列表,而不必迭代列表并转换每个对象,因为从地图键中应该很明显知道它是哪种类型。 - Ev0oD
我同意这个答案。你不需要用一个类对象来复杂化“键”,你可以只使用一个简单的字符串。对于“值”,只需使用一个通用列表来保存对象列表。虽然这并不能确保在“人”键的列表中的所有元素都是人物对象。 - Michael Sanchez
啊哈,现在我明白了。所以你想要一个包含该类型的列表的列表。 - Tschallacka
不不不.. :D 抱歉,我应该一开始就更好地解释清楚自己。请查看我编辑过的问题。 - Ev0oD

0
最终,我创建了自己的TypedMultimap,使用了tobias_k的提示comment
我创建了这个类:
import com.google.common.collect.ForwardingMultimap;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import java.util.Collection;

/**
 *
 * @author mimkorn
 */
public class TypedMultimap extends ForwardingMultimap<Class, Object> {

    private final HashMultimap<Class, Object> delegate;

    public TypedMultimap() {
        this.delegate = HashMultimap.create();
    }


   // IMPORTANT PART
    public <T> Collection<T> getSafely(Class<T> key) {
        return (Collection<T>) delegate().get(key);
    }

    public <T> boolean putSafely(
            Class<T> key, T value) {
        return delegate.put(key, value);
    }
   // TILL HERE    

   // CONVENIENCE
    public <T> boolean putSafely(T value) {
        return delegate.put(value.getClass(), value);
    }
   // TILL HERE

    @Override
    public Collection<Object> get(Class key) {
        return getSafely(key);
    }

    @Override
    public boolean put(Class key, Object value) {
        return putSafely(key, value);
    }

    @Override
    protected Multimap<Class, Object> delegate() {
        return delegate;
    }

}

编辑:现在您可以使用(new SomeClass());将其放入映射中,在SomeClass.class下进行映射。这样,您只需添加数据而不必关心类别,映射将为您进行排序。然后,您可以使用<T> Collection<T> getSafely(Class<T> key)获取特定类型的所有实例。

现在当您调用getSafely或putSafely时,它会为您检查类型。缺点是,用户需要知道他应该使用单个参数put方法添加数据。此外,只有getSafely和putSafely会检查编译时错误。

现在您可以这样做:

    TypedMultimap typedMultimap = new TypedMultimap();
    typedMultimap.putSafely(new ExportThing("prvý"));
    typedMultimap.putSafely(new ExportThing("druhý"));
    typedMultimap.putSafely(ExportThing.class, new ExportThing("prvý"));
    typedMultimap.putSafely(new ExportPhoneNumber("prvý"));
    typedMultimap.putSafely(ExportPhoneNumber.class, new ExportPhoneNumber("druhý"));
    Collection<ExportPhoneNumber> safely = typedMultimap.getSafely(ExportPhoneNumber.class);
    for (ExportPhoneNumber safely1 : safely) {
        System.out.println(safely1.getPhoneNumber());
    }

将返回:
druhý
prvý

正在尝试做

typedMultimap.putSafely(ExportThing.class, new ExportPhoneNumber("prvý")); 

会导致编译时错误。

这是一个有点问题的Map>。如果你需要Map>,例如代替HashMultimap使用ArrayListMultimap作为委托。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接