Reputation: 556
I want to make a class which extends the normal HashMap<K, V>
class but with some restrictions.
V
is always a List
of some kindLimitedSizeMap
are always lists with a set limited size.LimitedSizeMap.java
public class LimitedSizeMap<K, V extends List<Object>> extends HashMap<K, V> {
private int limit = 0;
public void setLimit(int limit) {
this.limit = limit;
}
@Override
public V put(K key, V value) {
if (this.limit > 0) {
return super.put(key, value.subList(0, Math.min(value.size(), this.limit)));
} else {
return super.put(key, value);
}
}
}
This code gives me various warnings with my types and such. I tried changing the List<Object>
to List<?>
and put LimitedSizeMap<K, V> extends HashMap<K, List<Object>>
instead. But I always get different errors.
What would be the best way to solve this, or how to fix my generics?
Thanks to @Andy Turner, I've settled for:
public class LimitedSizeMap<K, V> extends AbstractMap<K, List<V>> {
private final HashMap<K, List<V>> delegateMap = new HashMap<>();
private Integer limit = null;
public void setLimit(int limit) {
this.limit = Math.max(0,limit);
}
@Override
public List<V> put(K key, List<V> value) {
if (this.limit != null) {
return this.delegateMap.put(key, value.subList(0, Math.min(value.size(), this.limit)));
} else {
return this.delegateMap.put(key, value);
}
}
@Override
public Set<Entry<K, List<V>>> entrySet() {
return this.delegateMap.entrySet();
}
public List<V> append(K key, V value) {
List<V> values = this.delegateMap.getOrDefault(key, new ArrayList<>());
if (this.limit != null) {
if (values.size() < this.limit) {
values.add(value);
}
} else {
values.add(value);
}
return this.put(key, values);
}
}
Upvotes: 1
Views: 431
Reputation: 140318
The issue that you've got is that value.subList
returns a List<Object>
, not a V
. There is no requirement for the subList
method to return a List
of the same type as itself: indeed, you often can't return a list of the same type, because a view of a list is fundamentally not the same as the list itself.
You should declare it as:
public class LimitedSizeMap<K, V> extends HashMap<K, List<V>> {
If you really want to have the List
in the V
, I suppose you can do it by supplying an extra argument to the constructor, which does the "limiting" for you:
public class LimitedSizeMap<K, V extends List<Object>> extends HashMap<K, V> {
private final Function<? super V, ? extends V> trimmer;
public LimitedSizeMap(Function<? super V, ? extends V> trimmer) { this.trimmer = trimmer; }
@Override
public V put(K key, V value) {
return super.put(key, trimmer.apply(value));
}
}
(or you can make trimmer an abstract method which you have to implement to instantiate the class).
You then just have to work out how you can implement that function. It's easy if your V
is List<Object>
; it's less easy otherwise.
I'd say the awkwardness of this far outweights whatever benefit you think you'd have to have the List
-ness in the type arguments. Honestly, the bigger issue I see with this class is its name implies that the Map
has limited size, not the lists stored in the values.
I'd also say that extending HashMap
is a rather dubious thing to do.
I'd recommend extending AbstractMap<K, List<V>>
and implementing the methods to delegate to a wrapped HashMap
:
public class LimitedSizeMap<K, V> extends AbstractMap<K, List<V>> {
private final HashMap<K, List<V>> delegate = new HashMap<>();
@Override
public V put(K key, V value) {
if (this.limit > 0) {
return delegate.put(key, value.subList(0, Math.min(value.size(), this.limit)));
} else {
return delegate.put(key, value);
}
}
// ... implement the other methods as required: see Javadoc.
}
Upvotes: 2