Reputation: 4869
Let's say I have simple class
public class MyObject {
}
And the handler interface for processing the child classes of MyObject
public interface MyObjectHandler<V extends MyObject>{
List<V> handle(List<V> objects);
}
Suppose, I have BigObjects and SmallObjects (both of them extends MyObject) and I want to have separate handlers for them. So, I create two intefaces of MyObjectHandler with specific generics.
class BigObject extends MyObject {}
class SmallObject extends MyObject {}
// Handlers interfaces
interface BigObjectHandler extends MyObjectHandler<BigObject>{}
interface SmallObjectHandler extends MyObjectHandler<SmallObject>{}
// Concrete handlers
class BigHandler1 implements BigObjectHandler {....}
class BigHandler2 implements BigObjectHandler {....}
class SmallHandler1 implements SmallObjectHandler {....}
class SmallHandler2 implements SmallObjectHandler {....}
Now let's imagine that we have created AbstractHandlerChain<...>
abstract class. So, we can create BigHandlerChain class and inject our BigHandlers (and the same thing with SmallHandlerChain).
class BigHandlerChain extends AbstractHandlerChain<BigObjectHandler> {
// Inject only childs of BigObjectHandler. E.g. via spring @Autowired
public BigHandlerChain(List<BigObjectHandler> handlers) {
this.handlers = handlers;
}
}
The question: is it possible to create perfect AbstractHandlerChain for this case?
public abstract class HandlerChain<T extends MyObjectHandler> {
private List<T> handlers;
public HandlerChain(List<T> handlers) {
this.handlers = handlers;
}
public <V extends MyObject> List<V> doChain(List<V> objects) {
for (T handler : handlers) {
objects = handler.handle(objects);
}
return objects;
}
}
This one works, but in handler.handle(objects)
I get Unchecked call to 'handle(List<V>)' as a member of raw type 'MyObjectHandler'
, so I should add @SuppressWarnings("unchecked")
that is not very good.
public abstract class HandlerChain<T extends MyObjectHandler<? extends MyObject>> {
...
public <V extends MyObject> List<V> doChain(List<V> objects) {
for (T handler : handlers) {
objects = handler.handle(objects);
}
return objects;
}
}
Doesn't work. In handler.handle(objects)
I get handle (java.util.List<capture<? extends MyObject>>) cannot be applied to (java.util.List<V>)
. Why I can't pass objects to handlers in this case? Wildcard extends MyObject and V extends MyObject. Isn't it enought?
public abstract class HandlerChain<T extends MyObjectHandler<V>, V extends MyObject> {
...
public List<V> doChain(List<V> objects) {
for (T handler : handlers) {
objects = handler.handle(objects);
}
return objects;
}
}
This one works, but in this case I should define BigHandlerChain as class BigHandlerChain extends AbstractHandlerChain<BigObjectHandler, BigObject>
. But BigObjectHandler
already contains information about classes, that can be handled by it, so it's information duplication.
public abstract class HandlerChain<T extends MyObjectHandler<V extends MyObject>> {
...
public List<V> doChain(List<V> objects) {
for (T handler : handlers) {
objects = handler.handle(objects);
}
return objects;
}
}
Here is a solution, that I expect from java, but it doesn't work! I can't declare class like this ...class HandlerChain<T extends MyObjectHandler<V extends MyObject>
. Why I can use wildcards after MyObjectHandler, but can't use this construction?
Upvotes: 10
Views: 5971
Reputation: 8783
Fixing Solution 4 is simple as this: The Java compiler expects the wildcards to be declared all together, and then to be used:
public abstract class HandlerChain<V extends MyObject, T extends MyObjectHandler<V>>
{
private List<T> handlers;
public List<V> doChain(List<V> objects)
{
for (T handler : handlers)
{
objects=handler.handle(objects);
}
return objects;
}
}
Upvotes: 0
Reputation: 86193
You don’t need the handler to be generic, only the object:
public abstract class HandlerChain<V extends MyObject> {
private List<MyObjectHandler<V>> handlers;
public HandlerChain(List<MyObjectHandler<V>> handlers) {
this.handlers = handlers;
}
public List<V> doChain(List<V> objects) {
for (MyObjectHandler<V> handler : handlers) {
objects = handler.handle(objects);
}
return objects;
}
}
This allows:
new HandlerChain<BigObject>(Arrays.asList(new BigHandler1(), new BigHandler2())) {
// ...
};
EDIT: If you compare G_H’s solution and mine, the only dfference is that G_H uses List<? extends MyObjectHandler<V>>
. This allows you to pass a list with the element type declared to be more specific than just MyObjectHandler<V>
. I think it’s unlikely you will need this, but you may just as well take this extra flexibility in case.
Upvotes: 3
Reputation: 11999
Solution 1
This one works, but in handler.handle(objects) I get Unchecked call to 'handle(List)' as a member of raw type 'MyObjectHandler', so I should add @SuppressWarnings("unchecked") that is not very good.
Indeed, because MyObjectHandler
is a generic type but you don't specify the type for it in HandlerChain
's type parameter.
Solution 2
Doesn't work. In handler.handle(objects) I get handle (java.util.List>) cannot be applied to (java.util.List). Why I can't pass objects to handlers in this case? Wildcard extends MyObject and V extends MyObject. Isn't it enough?
Nope. ? extends MyObject
basically says it's for some unspecified type extending MyObject
, but you're not saying which. You could create a class public class BigHandlerChain<BigObjectHandler>
but supply a list of SmallObject
instances to doChain
.
Solution 3
This one works, but in this case I should define BigHandlerChain as class BigHandlerChain extends AbstractHandlerChain. But BigObjectHandler already contains information about classes, that can be handled by it, so it's information duplication.
Indeed, there's some duplicate information here, but when compositing generic types like this, that's likely to happen. Since your doChain
method operates on some type, it must be specified what that type can do. See it as saying you'll want to handle BigObject
lists and the handlers you provide must be able to handle BigObjects.
Solution 4
Here is a solution, that I expect from java, but it doesn't work! I can't declare class like this ...class HandlerChain. Why I can use wildcards after MyObjectHandler, but can't use this construction?
The problem is that V
indicates some specific type, not a wildcard, so you'll need to specify what V is.
In other words, your solution 3 is the correct approach, despite some duplicate information. Unfortunately, you're gonna see more of this in Java. Getters/setters are arguably unnecessary boilerplate when specific modifier keywords on the fields could achieve the same (like in Ruby).
Note however that if you specify public abstract class HandlerChain<T extends MyObjectHandler<V>, V extends MyObject>
, you're specifying the chain as only being suitable for one specific type of MyObjectHandler
. Since I'm thinking you'll probably need a chain of different handlers that are all capable of handling the same object types, you're better off only specifying that object type:
public abstract class HandlerChain<V extends MyObject> {
private List<MyObjectHandler<V>> handlers;
public HandlerChain(List<MyObjectHandler<V>> handlers) {
this.handlers = handlers;
}
public List<V> doChain(List<V> objects) {
for (MyObjectHandler<V> handler : handlers) {
objects = handler.handle(objects);
}
return objects;
}
}
Upvotes: 4