有没有办法绕过lambda表达式的类型?

时间:2016-03-31 14:51:39

标签: java generics lambda casting

这是一个我想知道的问题,因为lambdas是用Java引入的,受到a related question的启发,我想我可以把它带到这里,看看是否有任何想法。

(附注:有一个similar question for C#,但我没有找到一个用于Java。关于“将lambda存储在变量中”的Java问题总是指的情况变量的类型是固定的 - 这正是我试图规避的事情)

Lambda表达式通过目标类型推断接收所需的类型。这全都由编译器处理。例如,函数

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

可以使用相同的 lambda表达式调用:

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

表达式将表现为一个实现Function<Integer,Boolean>接口的类,一次作为实现Predicate<Integer>接口的类。

但遗憾的是,没有办法将lambda表达式存储为适用于这两种函数的类型,例如

GenericLambdaType lambda = x -> true;

这种“通用lambda类型”必须编码由给定的lambda表达式可以实现的方法的类型。所以在这种情况下,它将是

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

(基于standard type signatures,为了说明)。 (这不是完全不合理的:C++ lambda expressions基本上正是这样......)

是否有任何方法来阻止lambda表达式被解析为一种特定类型?

特别是,是否有任何技巧或解决方法允许使用相同的对象调用上面概述的useFuseP方法,如

useF(theObject);
useP(theObject);

这不太可能,所以我认为答案显然是:“不”,但是:是否有任何方式来编写泛型,魔术适应方法就像

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

请注意,这个问题更多是出于好奇。所以我真的在寻找任何方法来实现这一点(除了自定义预编译器或字节码操作)。

似乎没有简单的解决方法。通过将表达式包装到通用帮助器方法中来推迟类型推断的天真尝试,如

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

当然失败了,说“此表达式的目标类型必须是一个功能接口”(这里不能推断出这种类型)。但我也考虑过其他选择,比如MethodHandles,残酷的未经检查的演员表或讨厌的反思黑客。在编译之后,所有内容似乎都会立即丢失,其中lambda隐藏在匿名类的匿名对象中,其唯一方法是通过InvokeVirtual调用...

2 个答案:

答案 0 :(得分:2)

我没有看到任何允许将解析为一种特定功能接口类型的lambda表达式直接解释为等效功能接口类型的方法。没有超级接口或&#34;通用lambda类型&#34;两个功能接口都扩展或可以扩展,即强制它只接受一个特定类型的一个参数并返回特定类型。

但您可以编写一个实用程序类,其中包含从一种类型的功能接口转换为另一种功能接口的方法。

此实用程序类将谓词转换为返回布尔值的函数,反之亦然。它包含身份转换,因此您不必担心是否要调用转换方法。

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;
    }
}

输入函数和谓词是T的使用者,因此PECS指示? super。您还可以添加其他可能需要BooleanSupplierSupplier<Boolean>或任何其他返回booleanBoolean的功能接口类型的重载。

此测试代码编译。它允许您传递功能接口类型的变量并将其转换为所需的功能接口类型。如果您已经拥有确切的功能界面类型,则无需调用转换方法,但如果需要,可以使用。

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));
    }
}

输出结果为:

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

答案 1 :(得分:2)

假设在一个光明的未来(比如Java 10或11),我们有真正的函数类型,它允许指定一个函数而不强迫它是一个特定的传统Java类型(并且是某种值而不是一个对象,等等)。那么我们仍然存在现有方法的问题

[HttpPost]
Public ActionResult ActionName(string value)
{
   //suppose you have student class then you can set name property like below
   Student obj = new Student();
   obj.Nmae = value;
   //To Do your code
}

期望Java对象实现传统的Java static void useF(Function<Integer, Boolean> f) { ... } static void useP(Predicate<Integer> p) { ... } 并且表现得像Java对象那样,即不会突然改变interfacetheObject instanceof Function的结果。这意味着它不会是传递给这些方法中的任何一个时突然开始实现所需接口的泛型函数,而是应用某种捕获转换,产生一个实现所需目标接口的对象,就像今天一样,当你将lambda表达式传递给这些方法中的任何一个,或者使用theObject instanceof PredicatePredicate转换为Function(或使用p::test反之亦然)。

所以不会发生的是你将同一个对象传递给两个方法。您只有一个隐式转换,它将在编译时确定,并且可能在字节代码中显式,就像今天的lambda表达式一样。

f::apply这样的通用方法无法工作,因为它不了解目标类型。使这样的事情有效的唯一解决方案是你已经排除的那些,预编译器和字节码操作。您可以创建一个接受附加参数的方法,一个描述require convertToRequiredTargetType的{​​{1}}对象,该对象委托给Class,但该方法必须重做编译器所做的一切,确定功能签名,要实现的方法的名称等。

没有任何好处,因为调用像interface(或实际LambdaMetaFactory)这样的实用工具方法绝不比简单,例如convertToRequiredTargetType(theObject))。你创建这样一个方法的愿望引起了你的奇怪声明“在编译之后,一切似乎都丢失了,其中lambda隐藏在一个匿名类的匿名对象中”实际上,你有一个对象实现了一个已知的功能接口签名,因此可以像convertToRequiredTargetType(theObject, Function.class)一样简单地转换(如果你忘了,IDE可以为你完成方法名称)...