Sawyer
Sawyer

Reputation: 15917

Can I have a type safe map that either contains A or List<A>?

I need a Map container that contains either an

Item Object

or

List<Item> Object

as its value, and I can get it out without casting, is it possible?

Upvotes: 0

Views: 768

Answers (6)

thSoft
thSoft

Reputation: 22670

Functional Java provides a disjoint union data type called Either<A, B>, so you can define a Map<Key, Either<Item, List<Item>>>.

Upvotes: 0

henry
henry

Reputation: 6106

Possibly the type safe heterogeneous container pattern is what you are looking for

http://books.google.com/books?id=ka2VUBqHiWkC&pg=PA142&lpg=PA142&dq=type+safe+heterogeneous+container+pattern&source=bl&ots=yYDfNltZS3&sig=giH1d_rIQ-MumOnrMmiT4OgtM7E&hl=en&ei=880dTqHcJYSyhAeVhqS3Bw&sa=X&oi=book_result&ct=result&resnum=5&ved=0CDUQ6AEwBA#v=onepage&q=type%20safe%20heterogeneous%20container%20pattern&f=false

Addressing the good point raised in the comment below re type erasure - using super type tokens. Taken from http://gafter.blogspot.com/2007/05/limitation-of-super-type-tokens.html, which highlights an issue with the approach.

import java.lang.reflect.*;

public abstract class TypeRef<T> {
    private final Type type;
    protected TypeRef() {
        ParameterizedType superclass = (ParameterizedType)
            getClass().getGenericSuperclass();
        type = superclass.getActualTypeArguments()[0];
    }
    @Override public boolean equals (Object o) {
        return o instanceof TypeRef &&
            ((TypeRef)o).type.equals(type);
    }
    @Override public int hashCode() {
        return type.hashCode();
    }
}

public class Favorites2 {
    private Map<TypeRef<?>, Object> favorites =
        new HashMap< TypeRef<?> , Object>();
    public <T> void setFavorite(TypeRef<T> type, T thing) {
        favorites.put(type, thing);
    }
    @SuppressWarning("unchecked")
    public <T> T getFavorite(TypeRef<T> type) {
        return (T) favorites.get(type);
    }
    public static void main(String[] args) {
        Favorites2 f = new Favorites2();
        List<String> stooges = Arrays.asList(
            "Larry", "Moe", "Curly");
        f.setFavorite(new TypeRef<List<String>>(){}, stooges);
        List<String> ls = f.getFavorite(
            new TypeRef<List<String>>(){});
    }
} 

Upvotes: 1

erickson
erickson

Reputation: 269797

No, you can't do this in a type-safe manner.

From a design standpoint, I'd suggest using only List<Item> as values. If some of them are single-element lists, that's just fine; your code is likely to be much cleaner if you handle all values in the same way.

In fact, I'm curious how you plan to use a map with elements and lists of elements. Do you know which type to expect for a given key a priori?

Upvotes: 3

Andrzej Doyle
Andrzej Doyle

Reputation: 103817

Short answer: No.

Union types don't exist in Java. The closest thing you could do in order to get compile-time type checking would be to create list of some custom wrapper class which contained either an A or a List<A>, something like the following:

public class UnionListWrapper<A> {
    private final A item;
    private final List<A> list;

    public UnionListWrapper(A value) {
        item = value;
        list = null;
    }

    public UnionListWrapper(List<A> value) {
        item = null;
        list = value;
    }

    public Object getValue() {
        if (item != null) return item;
        else return list;
    }
}

At least you wouldn't be able to create instances of this class that weren't either an A or a List<A>, but getting the value back out would still have to just be Object, with associated casting. This could get quite clumsy, and on the whole it's probably not worth it.

In practice I'd probably just have a List<Object> with some comments around being very careful about what data types are accepted. The problem is that even run-time checks (sub-optimal) aren't going to be possible, since Java's generic erasure means that you can't do an instanceof A check at runtime (doubly so on the generic parameter of the List).

Upvotes: 3

antlersoft
antlersoft

Reputation: 14751

Sounds like you want a MultiMap -- There is an Apache implementation and a Google implementation

Upvotes: 2

CPerkins
CPerkins

Reputation: 9018

Not without writing a wrapper class (WrapsA) which holds either a single A or a List, and making your list be List

Upvotes: 0

Related Questions