Ev0oD
Ev0oD

Reputation: 1871

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

Maybe I am thinking something wrong here, but let's say I want to return a bunch of objects of different types. For example a lot of Persons, Profiles, Accounts. I wanted to put them in a map where a key would be something like Person.class and value would be a list of Person instances. First I thought about an EnumMap, but why should I create an ENUM for a list of classes, if I could somehow use the classes themselves?

I tried to get there with generics, but cannot wrap my head around the definition.

Is this even possible? Or am I thinking a bad design?

I could provide different methods for retrieval partial results. Or create a class that would hold it. But Map is more flexible, in case I want to use more classes in the future.

Edit:

I got some answers, which does not seem to address specifically what I am looking for, so for clarification:

I want something like:

{
 Person.class : [person1Instance, person2Instance,...], 
 Account.class : [account1Instance, account2Instance, account3Instance, ...], 
 Profile.class : [profile1Instance...]
}

And I want to avoid casting. Somehow use the fact that the key should define type (safety) for the list of items.

Upvotes: 4

Views: 1777

Answers (4)

Ev0oD
Ev0oD

Reputation: 1871

In the end I created my own TypedMultimap, using a hint from tobias_k comment.

I created this class:

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;
    }

}

Edit: Now you can just put(new SomeClass()); and it will put it into the map mapping it under the SomeClass.class. This way you only add data without caring about the class and the map will sort it out for you. Then you can fetch all instances of a particular type with <T> Collection<T> getSafely(Class<T> key)

Now when you call getSafely or putSafely it will check the types for you. The drawback is, that the user somehow needs to know, that he should add data with the single argument put method. Also, that only getSafely and putSafely, will check for compile time errors.

Now you can do this:

    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());
    }

will return:

druhý
prvý

Trying to do

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

will result in a compile time error.

This is a somewhat Map>. If you need Map> use as the delegate ArrayListMultimap for example instead of HashMultimap

Upvotes: 0

Aaron Digulla
Aaron Digulla

Reputation: 328810

I've implemented this in TypedMap: http://blog.pdark.de/2010/05/28/type-safe-object-map/

Here is some demo code:

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 );

The magic is in the key:

final static TypedMapKey<String> KEY1 = new TypedMapKey<String>( "key1" );
final static TypedMapKey<List<String>> KEY2 = new TypedMapKey<List<String>>( "key2" );

To solve your problem, you need to create keys for the different types that you want to save in the map:

TypedMapKey<List<Account>> ACCOUNTS = new TypedMapKey<List<Account>>(  "ACCOUNTS" );

then you need the usual get/create code:

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;
}

which will allow you to access the list with:

List<Account> accounts = getList(ACCOUNTS);

Upvotes: 3

Prashant
Prashant

Reputation: 4644

Object of different type are not allowed, because while retrieving them we face problem we don't know what type of object is going to come out.

This is my suggestion :

Make map like :

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

Lets u put some values like :

Person p = new Person();

map.put(p, "Person");

Account a = new Account();

map.put(a, "Account");

Assuming you will pass different object.

While retrieving something like this :

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;


} 

}

Upvotes: 1

Tschallacka
Tschallacka

Reputation: 28742

You don't need any enums. You can just use the instanceof to check what type an object is. This is how I usally do it, but i'm curious about other answers by others.

Usually I only use one parent type with generic methods that I need for my maps(usually an interface). But sometimes I use this method in cases where I really don't know what will be put into the map by user actions.

Take for example this map:

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;
   }
}

Upvotes: 0

Related Questions