Type safe merging of Map with multiple types for its value; `incompatible types java.lang.Object cannot be converted to capture#1 of ?`

jbx :

I have a container class which has a Map which is carrying multiple types for its value. To make it type safe from the external point of view, the key itself has a generic type, indicating what the type of the value will be.

The key provides some basic facilities to look up items and merge their values. (There might be different merging rules for the type of value that is found).

public abstract class Key<T> {
  @SuppressWarnings("unchecked")
  T get(Map<Key<?>, Object> data) {
      return (T) data.get(this);
  }

  /** 
    * This must be provided by the implementation. 
    *
    * @param old the old value
    * @param new the new value
    * @returns the merged value
    */ 
  abstract T merge(T old, T new);  
}

The Container class then looks like this.

public class Container {

  private final Map<Key<?>, Object> data = new HashMap<>();

  public <T> Optional<T> get(Key<T> key) {
    return Optional.ofNullable(key.get(data));
  }

  public <T> Container set(Key<T> key, T value) {
    data.put(key, value);
    return this;
  }

  @SuppressWarnings("unchecked")
  public <T> Container merge(Key<T> key, T newValue) {
    data.compute(key, (k, oldValue) -> key.merge((T) oldValue, newValue));
    return this;
  }


  public Container mergeAll(Container that) {
    //this does not compile: incompatible types java.lang.Object cannot be converted to capture#1 of ?
    that.data.forEach((key, value) -> this.data.merge(key, value, key::merge));
    return this;
  }
}

All of this works fine, apart from the last mergeAll method. It is looking at all the keys of the that container and merge their values with those of the this container.

However the key::merge doesn't compile with an error incompatible types java.lang.Object cannot be converted to capture#1 of ?.

In the single merge() method I got around it with a type cast to (T). But in this case I don't have a generic type, it is ?, but I cannot do something like: (key, oldValue) -> key.merge((?) oldValue, (?) value) because you cannot cast to (?).

Is there a way around this? Or a better pattern to implement what I am trying to achieve?

Jan Gassen :

You should be able to fix this by writing:

public Container mergeAll(Container that) {
  that.data.forEach((key, value) -> this.data.merge(key, value, ((Key<Object>) key)::merge));
  return this;
}

In your example, key has an unbounded wildcard type Key<?>, so methods accepting generic parameters cannot accept anything but null. As the values passed to merge are of type Object, you can cast your key to Key<Object>.

Another solution might probably be to change Key<?> to Key<Object> if this is applicable in your situation.

I don't think it'll get any more type safe than this, because the map Map<Key<?>, Object> data has no hint for the compiler that the class of the value is actually supposed to be the generic type of the key.

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=159805&siteId=1