简体   繁体   中英

Java generics: incompatible wildcard capture

I have a repository interface parameterized with stored entities type. Among other methods in it, I have two of interest: the create() method which instantiates an entity, and the save() method that saves the entity:

public interface INamedEntityRepository<T extends INamedEntity> {
    T create(String name);
    void save(T namedEntity);
}
...

Next follows a snippet of how this interface is used. I am getting a compilation error saying the type returned from the create() and that passed into save() are incompatible.

INamedEntityRepository<? extends INamedEntity> repo = getEntityRepository();

INamedEntity toSave = repo.create("Named");
... // configure the entity more...
repo.save(toSave);
      ^

The method save(capture#7-of ? extends INamedEntity) in the type INamedEntityRepository<capture#7-of ? extends INamedEntity> is not applicable for the arguments (INamedEntity) The method save(capture#7-of ? extends INamedEntity) in the type INamedEntityRepository<capture#7-of ? extends INamedEntity> is not applicable for the arguments (INamedEntity) . I can understand this, because really INamedEntity is not necessarily the expected type.

But even doing this

repo.save(repo.create("Named")));

won't help:

The method save(capture#7-of ? extends INamedEntity) in the type INamedEntityRepository<capture#7-of ? extends INamedEntity> is not applicable for the arguments (capture#8-of ? extends INamedEntity) The method save(capture#7-of ? extends INamedEntity) in the type INamedEntityRepository<capture#7-of ? extends INamedEntity> is not applicable for the arguments (capture#8-of ? extends INamedEntity) .

What's the problem? How to correctly handle this situation? Thanks in advance.

You will not be able to call .save on any INamedEntityRepository instance which is declared using a capturing generic definition (ie ? extends). This is similar to how you cannot call add on a List.

How can I add to List<? extends Number> data structures?

The problem is that the compiler doesn't know the unknown type is the same unknown type for both usages. The solution is to "lock" the type to a particular type for the two usages. If this were a method for example, you could type the method:

public <T extends INamedEntity> void someMethod(String name) {
    INamedEntityRepository<T> repo = getEntityRepository(); // may need cast here
    T toSave = repo.create(name);
    // do stuff with toSave
    repo.save(toSave);
}

You haven't shown the signature for getEntityRepository(), so a cast may be required, so if it too is a typed method, you can use the syntax:

INamedEntityRepository<T> repo = MyClass.<T>getEntityRepository();

if static, or

INamedEntityRepository<T> repo = this.<T>getEntityRepository();

if non-static, to pass the type through.

No matter whether you store the result of repo.create("Named") in a variable or not, the compiler checks its type, and since the type returned by create is INamedEntity , this won't work. To fix this, the compiler has to know that create returns something that is a subtype of what save can accept, and that is exactly when you don't use wildcards.

In this particular case, you know of course that the types are correct, so you can use an unsafe cast like this:

((INamedEntityRepository<INamedEntity>) repo).save(repo.create("Named"));

or you can delegate the issue of the unsafe operation to another location, eg declare repo without the wildcard in the first place.


In case that getEntityRepository() actually returns some concrete type, not the wildcarded one, you could use something like this:

void <T extends INamedEntity> test() {
    INamedEntityRepository<T> repo = getEntityRepository();
    T toSave = repo.create("Named");
    repo.save(toSave);
}

Which looks pretty obvious now that I typed it out...

The Silly Freak's and Bohemian's solutions with a type-locking method indeed give me a warning and make me do a cast when doing getEntityRepository() . So I am putting here a variant which compiles for me without any warning:

protected abstract INamedEntityRepository<? extends INamedEntity> getEntityRepository();
...

// the method that extracts the code of interest
private <T extends INamedEntity> void test(String name, INamedEntityRepository<T> repo) {
   T toSave = repo.create(name);
   repo.save(toSave);
}
...

test("Named", getEntityRepository());

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM