mortom123
mortom123

Reputation: 556

Java Generics - Map with Lists of Limited Size

I want to make a class which extends the normal HashMap<K, V> class but with some restrictions.

  1. The values type V is always a List of some kind
  2. The values inside the LimitedSizeMap 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

Answers (1)

Andy Turner
Andy Turner

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

Related Questions