7

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.

marcellorvalle
  • 1,631
  • 3
  • 17
  • 30
  • 3
    As far as I can tell, there is no way around this. I wouldn't consider the warning a problem since it's hidden behind your implementation. A client will always be guaranteed to receive the correct type since (as you mentioned) the only way to add to the map is through your `bind` method. – forrert Feb 12 '16 at 00:41
  • 2
    In Java, any time you cast to a non-reifiable generic type, you will get an unchecked warning. This is because the type information does not exist at run time, and the Java environment has no way to guarantee the type safety of the cast. Situations like this can't be helped .. if you analyze your own code and can prove that it is type safe, then just suppress the warning. – scottb Feb 12 '16 at 00:45
  • Nice. I am using this project as a tool to learn the language and it is always good to have some feedback from more experienced developers. – marcellorvalle Feb 12 '16 at 00:55
  • 1
    Btw., your `LambdaOp` interface could be replaced by the standard [`Supplier`](https://docs.oracle.com/javase/8/docs/api/?java/util/function/Supplier.html) – Holger Feb 12 '16 at 09:09

2 Answers2

5

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.

Jack Ammo
  • 280
  • 1
  • 5
  • 1
    Suggestion accepted. It seems it will not cast any warnings and should be perfect on this situation. http://stackoverflow.com/questions/1555326/java-class-cast-vs-cast-operator – marcellorvalle Feb 12 '16 at 01:54
3

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(); 
}
Douglas
  • 5,017
  • 1
  • 14
  • 28