Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic interface and implementation - type cannot be converted

I have a generic interface defined as:

public interface ItemService<T extends Slicer, E extends ItemSlice> {
    E getItemHistory(final String id, final T slicer);
}

And an implementation:

public class DynamoDbItemService implements ItemService<DynamoDbSlicer, DynamoDbItemSlice> {
    public DynamoDbItemSlice getItemHistory(final String id, final DynamoDbSlicer slicer) {
    }
}

Here are the definitions of the four classes referenced above:

public interface Slicer {
    Map<String, ? extends Serializable> pointer();
}

public class DynamoDbSlicer implements Slicer {
    public Map<String, AttributeValue> pointer() {
    }
}

public interface ItemSlice extends Slice<Item> {
}

public interface Slice<T> {
    List<T> data();
    Slicer next();
}

public class DynamoDbItemSlice implements ItemSlice {
    publi ImmutableList<Item> data() {}
    public DynamoDbSlicer next() {}
}

I would like to reference the ItemService interface but for it to be bound to the DynamoDbItemService implementation so I can swap it out if necessary which I can do like so:

ItemService<? extends Slicer, ? extends ItemSlice> itemService = new DynamoDbItemService itemService();

but if I try to use itemService like this:

ItemSlice slice = itemService.getItemHistory(itemId, DynamoDbSlicer.first(1));
slice = itemService.getItemHistory(itemId, slice.next());

I get these two compilation errors:

Error:(231, 82) java: incompatible types: item.pagination.DynamoDbSlicer cannot be converted to capture#1 of ? extends pagination.Slicer

Error:(238, 62) java: incompatible types: pagination.Slicer cannot be converted to capture#2 of ? extends pagination.Slicer

I understand from this question that ? wildcards cannot be identical so my question is - can I do what I want - work with the interface? If so, how? Or am I approaching this incorrectly?

I have asked two previous questions related to this which have helped along the way (first and second)

like image 387
Stuart Leyland-Cole Avatar asked Mar 05 '26 17:03

Stuart Leyland-Cole


1 Answers

I don't see a reason why the following wouldn't work for you:

ItemService<DynamoDbSlicer, DynamoDbItemSlice> itemService = new DynamoDbItemService();
ItemSlice slice = itemService.getItemHistory("",  new DynamoDbSlicer());

But if you would like to make the code as modular as possible you can use this method to perform an unchecked cast (pure evil some say) and get the result you want:

public static void main(String[] args) {

    ItemSlice slice = getMySlice(new DynamoDbItemService());
}

@SuppressWarnings("unchecked")
public static <T extends Slicer, E extends ItemSlice> E getMySlice(ItemService<T, E> service) {
    return service.getItemHistory("", (T) new DynamoDbSlicer());
}

And then of course there is the solution of passing the type inference responsibility to the actual field that is storing the value. I myself would go for this solution, as I think it offers the most flexibility:

public class DynamoDbItemService<T extends Slicer, E extends ItemSlice> implements ItemService<T, E>

ItemService<DynamoDbSlicer, DynamoDbItemSlice> itemService = new DynamoDbItemService();
ItemSlice slice = itemService.getItemHistory("", new DynamoDbSlicer());
like image 111
Matthew Avatar answered Mar 08 '26 07:03

Matthew



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!