Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java and generic type boundaries [duplicate]

Some background

I am developing a small DI Container based on Lambda expressions. I have the interface representing the lambda:

@FunctionalInterface
public interface LambdaOp<T> {
    T resolve();
}

And this is the (very simplified) Container:

public class Container {
    private final Map<Class<?>, LambdaOp<?>> bindings;

    public <T> void bind(Class<T> clazz, LambdaOp<? extends T> lambda) {
        bindingd.put(clazz, lambda);
    }

    public <T> T resolve (Class<T> clazz) {
        LambdaOp<?> lambda = bindings.get(clazz);
        return (T) lambda.resolve(); 
    } 

}

With this implementation I can do something like that:

container.bind(
    FooInterface.class,
    () -> {
        FooImplementation foo = new FooImplementation();
        foo.setParameterA(paramA);
        //do whatever needed to configure it
        return foo;  
    }
);

FooInterface foo2 = container.resolve(FooInterface.class);

It is working ok and it is nice because the compiler won't let me do something like this:

container.bind(
    FooInterface.class,
    () -> new BarImplementation() //compiler error; Bar does not implement Foo
);

The problem

The bindings Map itself does not guarantee that LambdaOp will have a generic type which extends the Class used as key.

private final Map<Class<?>, LambdaOp<?>> bindings;

As a result I receive an unchecked warning at the line:

return (T) lambda.resolve();

I suppose that the bind method signature is enough to guarantee the cohesion (is it?) but still feel some bad smell.

Is there any way to implement it on a more cohesive way?

EDIT:

The complete code is at github. I have made a lot of changes recently and README.md is very a little bit outdated.

like image 854
marcellorvalle Avatar asked Feb 12 '16 00:02

marcellorvalle


2 Answers

why not just cast using the Class clazz key? Your logic already guarantees that the cast will always work anyway

return clazz.cast( lambda.resolve() );

edit for clarification: the reason that using the Class object works without a warning but the simple generic cast does not is because at runtime with the generic cast, the generic type may as well be erasured to Object and therefore the compiler can't make guarantees that the cast will work for the intended type. The Class object on the other hand will have all the information needed to do a cast and so the compiler can safely allow it for generic types.

Assuming you don't go out of your way to mess with the generics of course.

like image 106
Jack Ammo Avatar answered Nov 13 '22 23:11

Jack Ammo


There is no way to do this such that the compiler will naturally figure out and guarantee on its own that the generic type is correct. You can, however, tell the compiler to shut up about it, and in this case it is appropriate to do so because your implementation does provide the relevant guarantee.

public <T> T resolve (Class<T> clazz) {
    @SuppressWarnings("unchecked")
    LambdaOp<? extends T> lambda = (LambdaOp<? extends T>) bindings.get(clazz);
    return lambda.resolve(); 
}
like image 32
Douglas Avatar answered Nov 13 '22 21:11

Douglas