-2

I have following interface:

public interface EntityCloneService<T extends AbstractNamedEntityBase> {

 /**
  * Given a UUID load the Entity Type.
  * 
  * @param uuid
  * @return  Entity From DB and 
  */
 public T getByUuid(UUID uuid);


 /**
  * Given the Existing Entity,  clone it and save in DB, then return clone instance.
  */
 public T getCloneAndSave(T existingEntity) throws Exception;

}

Now I have Generic Service, where I have

@Component
public class GenericEntityCloneService {

    private static final Map<String,EntityCloneService<? extends AbstractNamedEntityBase>> registration = 
            new HashMap<String,EntityCloneService<? extends AbstractNamedEntityBase>>(); // here we have registration of all entity by name to service actual implementation.

    public void clone(AbstractNamedEntityBase existingEntity) {
        EntityCloneService<? extends AbstractNamedEntityBase> service = registration.get("SOME KEY");
        AbstractNamedEntityBase  entity =   service.getByUuid(ref.getUuid());  // THIS WORKS because it up casting.

        service.getCloneAndSave(entity);    // now how do I pass entity object such that 
    }
}

When I try to compile this code does not compile. I know that getCloneAndSave() I am passing the type AbstractNamedEntityBase which is not allowed. So how do I make a call service.getCloneAndSave(entity); Any help is greatly appreciated. I am using java 8.

Naman
  • 27,789
  • 26
  • 218
  • 353
Bmis13
  • 550
  • 1
  • 8
  • 27
  • 1
    Related https://stackoverflow.com/questions/2723397/what-is-pecs-producer-extends-consumer-super – Naman Dec 04 '18 at 03:45
  • 1
    Since you don’t know the actual type argument of the particular `EntityCloneService` implementation at this point, it is impossible to do that in a type safe way. – Holger Dec 04 '18 at 08:17

2 Answers2

1

Here you are using the T as both producer and consumer hence you can't use bounded type parameters here. You have to use method level generics instead like so,

public <S> S getCloneAndSave(S existingEntity) throws Exception;
Ravindra Ranwala
  • 20,744
  • 6
  • 45
  • 63
  • This will compile, but an EntityCloneService is supposed to work with T instances, not just any possible type. – VGR Dec 04 '18 at 16:56
1

The problem is this:

AbstractNamedEntityBase entity = service.getByUuid(ref.getUuid());

That line discards T. Each EntityCloneService works with exactly one subclass of AbstractNamedEntityBase. The service’s getCloneAndSave method requires an object of type T, which is some specific subclass of AbstractNamedEntityBase.

There is no way to preserve the generic type of a value in your Map. All you will ever know about it is that it’s an EntityCloneService<? extends AbstractNamedEntityBase>. However, you have some options to work around it.

The easiest is to add a default method inside EntityCloneService. Inside that interface, T is known, and there is no need to refer to AbstractNamedEntityBase directly:

default T getCloneAndSaveFor(UUID uuid)
throws Exception {
    T entity = getByUuid(uuid);
    return getCloneAndSave(entity);
}

Another possibility is writing a separate method in GenericEntityCloneService, which knows each EntityCloneService has a specific type, even if that specific type is not itself known:

private <E extends AbstractNamedEntityBase> E getCloneAndSaveFor(
    UUID uuid,
    EntityCloneService<E> service)
throws Exception {

    E entity = service.getByUuid(uuid);
    return service.getCloneAndSave(entity);
}

Side notes:

  • throws Exception is not a good practice. Don’t force callers to catch unchecked exceptions which are intended to expose programmer errors, like NullPointerException, IndexOutOfBoundsException, IllegalArgumentException, etc.
  • You can shorten your Map declaration to private static final Map<String,EntityCloneService<? extends AbstractNamedEntityBase>> registration = new HashMap<>();.
VGR
  • 40,506
  • 4
  • 48
  • 63