Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Mark generic type parameter as functional interface in Java 8

I want to restrict the type parameter of a function to be a functional interface.

Something like this:

public static <@FunctionalInterface T extends Function<A, B>> T foo () {
    return a -> bar;
}

The @FunctionalInterface is not allowed here.

The aim is, to make it possible to return a lambda with the type of the type parameter. Since T can be a normal class, a return of a lambda is not allowed.

Is there a possibility to restrict the type parameter to be a functional interface?

like image 646
F. Böller Avatar asked Feb 12 '23 16:02

F. Böller


2 Answers

As already discussed on your other question it is impossible to do that. Not only annotating the type parameter but especially implementing an unknown interface via lambda expression.

When compiling your method

public static <@FunctionalInterface T extends Function<A, B>> T foo() {
    return a -> bar;
}

the compiler must produce code capable of returning an instance of the appropriate type but it doesn’t know what T will be as this depends on the caller of foo() but there is no connection between compiling foo() and compiling the caller of foo(). The latter might happen years later on the other side of the our planet.

Maybe you are not aware of Type Erasure. There will be only one compiled version of your method foo() but it has to fulfill the generic contract of returning an instance of the appropriate type. Without knowing what T is.

This works when returning an existing instance like when returning an element of a collection or one of the values passed as parameters. But generic methods are unable to return new instances of a type parameter. Not using new and neither using a lambda expression.


Note that it is still possible to have sub-interface implementations of the desired function if you let the caller which knows the type perform the “up-level” operation. Suppose the you have a Generic factory method like:

public static <A,B> Function<A,B> getter(Map<?, ? extends B> map) {
    return a->map.get(a);
}

This code works with the unknown types A and B and the unknown parametrization of Map because the only constraint is the method Map.get accepts instances of A which it does as it accepts anything and returns an instance of B which it does as any type ? extends B will be assignable to B.

Now, if your caller has an arbitrary sub-type of Function, X, e.g.

interface X extends Function<String, Integer> {}

it can use your factory method to produce an instance of X which decorates the function, like:

Map<String, Integer> map=new HashMap<>();
X x=getter(map)::apply;
x.apply("foo");

Here, the constraint on X being a functional interface is checked on the caller site.

like image 128
Holger Avatar answered Feb 15 '23 11:02

Holger


My generics-fu is weak, but I think that would be:

public static <T, R> Function<T, R> foo() {
    // ...
}

But I don't think you can instantiate R, you'd have to be able to get it from T. Your code doesn't know the runtime type of R, so new R() is out of bounds.

But for example, if T can give you R, as with a Map:

public static <K, R, T extends Map<K,R>> Function<T, R> makeGetter(K key) {
    return a -> a.get(key);
}

That returns a getter that, when called with a given map, will return the entry with the key you used to create the getter:

import java.util.function.Function;
import java.util.*;
public class Example {

    public static final void main(String[] args) {
        Map<String,Character> mapLower = new HashMap<String,Character>();
        mapLower.put("alpha", 'a');
        mapLower.put("beta",  'b');

        Map<String,Character> mapUpper = new HashMap<String,Character>();
        mapUpper.put("alpha", 'A');
        mapUpper.put("beta",  'B');

        Function<Map<String, Character>, Character> getAlpha = makeGetter("alpha");

        System.out.println("Lower: " + getAlpha.apply(mapLower));
        System.out.println("Upper: " + getAlpha.apply(mapUpper));
    }

    public static <K, R, T extends Map<K,R>> Function<T, R> makeGetter(K key) {
        return a -> a.get(key);
    }
}

Output:

Lower: a
Upper: A

I don't think type erasure lets you get much closer than that, barring using an instance method and parameterizing your containing class.

like image 31
T.J. Crowder Avatar answered Feb 15 '23 10:02

T.J. Crowder