Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Incompatible types: capture of ? extends ... is not convertible to capture of ? extends

Tags:

java

generics

I have a variable in the object:

private final Map<Long, ? extends MovieInfoDTO> bElementsToAdd = new HashMap<>();

Using the method in the Builder pattern I want to complete this map.

public Builder withElementsToAdd(@Nullable final Map<Long, ? extends MovieInfoDTO> elementsToAdd) {
     this.bElementsToAdd.clear();
     if(elementsToAdd != null) {
         this.bElementsToAdd.putAll(elementsToAdd);
     }
     return this;
}

However, I receive an error.

    putAll
(java.util.Map<? extends java.lang.Long,? extends capture<? extends com.jonki.popcorn.common.dto.movie.MovieInfoDTO>>)
in Map cannot be applied
to
(java.util.Map<java.lang.Long,capture<? extends com.jonki.popcorn.common.dto.movie.MovieInfoDTO>>)

How do I correct this method of supplementing the map?

like image 747
JONKI Avatar asked Dec 23 '22 07:12

JONKI


2 Answers

Short answer: you can't add values to a map with that type. As a rule of thumb, you can read ? extends as "read-only", so your map is a map of "read-only" MovieInfoDTO objects.

Longer answer: ? extends T is a bit of a weird type; as you're running up against, the type checker considers different syntactic occurrences expressions with type ? extends T to be different, incompatible "captures" that don't type check as equal to each other. So since bElementsToAdd has type Map<Long, ? extends MovieInfoDTO>, then bElementsToAdd.put() takes a value of a new "capture" of ? extends MovieInfoDTO. But there's no way to get that same capture! Even if you did bElementsToAdd.put(k, bElementsToAdd.get(k)), the put and get use different captures so they aren't compatible types!

You clearly don't want a read-only map since you're trying to update it. So, you have a few options. One is to remove the wildcard -- that makes sense if you don't care what subtype of MovieInfoDTO you've got and you'd be fine with mixing them in the same map. If you do care, then you probably meant to add a bound to your overall map:

class MyClass<T extends MovieInfoDTO> {
  // ... 
  private final Map<Long, T> bElementsToAdd = new HashMap<>();

  //...
  public Builder withElementsToAdd(@Nullable final Map<Long, T> elementsToAdd) {
   // ...
}

This enforces the constraint that the type of elements you add in withElementsToAdd must have the same type as the ones in the map, even if you don't care exactly what type that is.

like image 120
jacobm Avatar answered May 22 '23 07:05

jacobm


When you place an upper-bound wildcard in your declaration of bElementsToAdd, the compiler doesn't know which subtype of MovieInfoDTO you have there. You can theoretically and legally assign a Map<Long, MovieInfoSubclassDTO> (MovieInfoSubclassDTO being a theoretical subclass of MovieInfoDTO) to bElementsToAdd.

The method putAll is supposed to be able to place all mappings from another map into the original map. It could have a type as high up as Map<Long, MovieInfoDTO>. The variable bElementsToAdd must be able to accept MovieInfoDTOs as keys.

Solution: Remove the wildcard upper-bound on bElementsToAdd.

private final Map<Long, MovieInfoDTO> bElementsToAdd = new HashMap<>();

You do not need to remove the upper-bound wildcard on elementsToAdd (the parameter to withElementsToAdd), and you should not remove it. Retaining the upper-bound wildcard here allows calling code to pass in a Map<Long, MovieInfoSubclassDTO>, which is acceptable because the type MovieInfoSubclassDTO, the theoretical subclass of MovieInfoDTO, should be allowed as values in bElementsToAdd.

like image 32
rgettman Avatar answered May 22 '23 07:05

rgettman