4

I want to write kind of function factory. It should be a function, which is called once which different strategies as parameters. It should return a function, which selects one of this strategies dependent on the parameter, which is to be fulfilled by a predicate. Well, better look at condition3 for better understanding. The problem is, that it is not compiling. I think because the compiler can't figure out, that the functional interface H can be realized by the implementation. Without generics it is working fine.

@FunctionalInterface
public interface Finder<T, S> {

    Stream<S> findBest (T t);

    // Valid code
    static <P, Q> Finder<P, Q> condition1 () {
        return p -> null;
    }

    // Valid code, but selects just one of the H's when the method is invoked
    static <P, Q, H extends Finder<P, Q>> H condition2 (Pair<Predicate<P>, H>... hs) {
        return hs[0].getRight ();
    }

    // Should return a method, which selects the appropiate H 
    // whenever it is invoked with an P
    // Compiler complain: 
    // The target type of this expression must be a functional interface
    static <P, Q, H extends Finder<P, Q>> H condition3 (Pair<Predicate<P>, H>... hs) {
        return p -> stream (hs).filter (pair -> pair.getLeft ().test (p))
                               .findFirst ()
                               .map (Pair::getRight)
                               .map (h -> h.findBest (p))
                               .orElseGet (Stream::empty);
    }
}

So what's the problem here? Can I solve it and if it's possible with Java: how?

2
  • Please provide the full compilation error message. Commented Jun 23, 2014 at 12:47
  • "The target type of this expression must be a functional interface." It is in the comment above the method. Commented Jun 23, 2014 at 12:53

1 Answer 1

5

Look at the signature of your method and try to tell what the exact return type is:

static <P, Q, H extends Finder<P, Q>> H condition3(…

Lambdas can only implement an interface known at compile-time. But the actual type argument for H is not known to the compiler.

Your first method works because it returns the type Finder<P, Q> which the lambda can implement, your second works because it doesn’t use a lambda for implementing the return type H extends Finder<P, Q>.

Only your third method attempts to specify a lambda expression for a type argument H extends Finder<P, Q>.


A solution is not to give the caller the freedom to mandate a particular sub-type of Finder as the method’s return type:

static <P, Q, H extends Finder<P, Q>>
    Finder<P, Q> condition3(Pair<Predicate<P>, H>... hs) {

To illustrate what implications your original method signature has, look at the following example:

final class HImpl implements Finder<String,String> {
    public Stream<String> findBest(String t) {
        return null; // just for illustration, we never really use the class
    }
}

HImpl x=Finder.<String,String,HImpl>condition3();

Given your original method signature this compiles without any error. But how ought the method condition3 provide an instance of HImpl here using your lambda expression?

Sign up to request clarification or add additional context in comments.

4 Comments

Aren't generics resolved at compile time? I think when I use condition3() with an argument, the type of H is defined and it replaces the generic return type H at compile time with the specific type?
They are resolved at compile time but only the caller of method condition3 knows the actual type arguments as every call site might use a different type for H. Compare with Collections.emptyList(): it returns a List<T> which might List<Integer> or List<String> depending on how you use it. This works only because the returned list is empty as the implementation of Collections.emptyList() never knows the type of T and can’t provide real values for it.
But in Collections.emptyList() there are no provided type arguments. I think with Pair<Predicate<P>, H>... hs the type should be defined. And when I provide an argument of Type Pair<Predicate<PImpl>, HImpl> I would expect that the compiler knows that the return type is HImpl. And since HImpl is because of the restriction H extends Finder<P, Q> a functional interface, the compiler should be able to find out the type of the lambda expression as HImpl.
You seem to be confused about what Generics do. H extends Finder<P, Q> does not required that H is a functional interface. It just says that H must fulfill the Finder interface. It might be an arbitrary type extending or implementing Finder. It might be an interface extending Finder and adding more methods thus not being a functional interface. The actual type argument for H might be even a final class. See my updated answer.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.