Can I have a type safe map that either contains A or List<A>?
I need a Map container that contains 开发者_StackOverflow中文版either an
Item Object
or
List<Item> Object
as its value, and I can get it out without casting, is it possible?
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
).
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?
Sounds like you want a MultiMap -- There is an Apache implementation and a Google implementation
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>>(){});
}
}
Not without writing a wrapper class (WrapsA) which holds either a single A or a List, and making your list be List
Functional Java provides a disjoint union data type called Either<A, B>
, so you can define a Map<Key, Either<Item, List<Item>>>
.
精彩评论