Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
1.2k views
in Technique[技术] by (71.8m points)

java - Is there any way to circumvent the typedness of lambda expressions?

This is a question that I wondered about since the lambdas had been introduced in Java, and inspired by a related question, I thought that I might bring it up here, to see whether there are any ideas.

(Side notes: There is a similar question for C#, but I did not find one for Java. The questions for Java about "storing a lambda in a variable" always referred to cases where the type of the variable was fixed - this is exactly what I'm trying to circumvent)


Lambda expressions receive the type that they need, via target type inference. This is all handled by the compiler. For example, the functions

static void useF(Function<Integer, Boolean> f) { ... }
static void useP(Predicate<Integer> p) { ... }

can both be called with the same lambda expression:

useF(x -> true);
useP(x -> true);

The expression will once manifest itself as a class implementing the Function<Integer,Boolean> interface, and once as a class implementing the Predicate<Integer> interface.

But unfortunately, there is no way of storing the lambda expression with a type that is applicable to both functions, like in

GenericLambdaTypelambda = x -> true;

This "generic lambda type" would have to encode the type of the method that can be implemented by the given lambda expression. So in this case, it would be

(Ljava.lang.Integer)Ljava.lang.Booleanlambda = x -> true;

(based on the standard type signatures, for illustration). (This is not completely unreasonable: The C++ lambda expressions are basically doing exactly that...)


So is there any way to prevent a lambda expression being resolved to one particular type?

Particularly, is there any trick or workaround that allows the useF and useP methods sketched above to be called with the same object, as in

useF(theObject);
useP(theObject);

This is unlikely, so I assume the answer will plainly be: "No", but: Could there be any way to write a generic, magic adaption method like

useF(convertToRequiredTargetType(theObject));
useP(convertToRequiredTargetType(theObject));

?


Note that this question is more out of curiosity. So I'm literally looking for any way to achieve this (except for custom precompilers or bytecode manipulation).

There seem to be no simple workarounds. A naive attempt to defer the type inference, by wrapping the expression into a generic helper method, as in

static <T> T provide()
{
    return x -> true;
}

of course fails, stating that "The target type of this expression must be a functional interface" (the type can simply not be inferred here). But I also considered other options, like MethodHandles, brutal unchecked casts or nasty reflection hacks. Everything seems to be lost immediately after the compilation, where the lambda is hidden in an anonymous object of an anonymous class, whose only method is called via InvokeVirtual...

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Answer

0 votes
by (71.8m points)

I don't see any way of allowing a lambda expression that resolves to one particular functional interface type to be interpreted directly as an equivalent functional interface type. There is no superinterface or "generic lambda type" that both functional interfaces extend or could extend, i.e. that enforces that it takes exactly one parameter of exactly one specific type and returns a specific type.

But you can write a utility class with methods to convert from one type of functional interface to another.

This utility class converts predicates to functions that return booleans and vice versa. It includes identity conversions so that you don't have to worry about whether to call the conversion method.

public class Convert
{
    static <T> Predicate<T> toPred(Function<? super T, Boolean> func)
    {
        return func::apply;
    }

    static <T> Predicate<T> toPred(Predicate<? super T> pred)
    {
        return pred::test;
    }

    static <T> Function<T, Boolean> toFunc(Predicate<? super T> pred)
    {
        return pred::test;
    }

    static <T> Function<T, Boolean> toFunc(Function<? super T, Boolean> func)
    {
        return func::apply;
    }
}

The input functions and predicates are consumers of T, so PECS dictates ? super. You could also add other overloads that could take BooleanSuppliers, Supplier<Boolean>, or any other functional interface type that returns a boolean or Boolean.

This test code compiles. It allows you to pass a variable of a functional interface type and convert it to the desired functional interface type. If you already have the exact functional interface type, you don't have to call the conversion method, but you can if you want.

public class Main
{
    public static void main(String[] args)
    {
        Function<Integer, Boolean> func = x -> true;
        useF(func);
        useF(Convert.toFunc(func));
        useP(Convert.toPred(func));

        Predicate<Integer> pred = x -> true;
        useP(pred);
        useP(Convert.toPred(pred));
        useF(Convert.toFunc(pred));
    }

    static void useF(Function<Integer, Boolean> f) {
        System.out.println("function: " + f.apply(1));
    }
    static void useP(Predicate<Integer> p) {
        System.out.println("predicate: " + p.test(1));
    }
}

The output is:

function: true
function: true
predicate: true
predicate: true
predicate: true
function: true

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome to OStack Knowledge Sharing Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...