【问题标题】:How to directly use a function as a Functional Type in Java 8如何在 Java 8 中直接使用函数作为函数类型
【发布时间】:2015-12-12 15:37:39
【问题描述】:

如果我创建一个函数式接口:

@FunctionalInterface
public class Consumer2<T1, T2> {
    void accept(T1 t1, T2 t2);

    default Consumer1<T2> curry(T1 t1) {
        return (t2) -> accept(t1, t2);
    }
}

现在,如果我有课:

public class MyClass {
    public void printStrings(String a, String b) {
        System.out.println(a + ": " + b);
    }
}

MyClass myClass = new MyClass();

现在,如果我想使用我的功能界面,我可以:

Consumer2<String, String> printString = myClass::printStrings;
printString.curry("hello").accept("world");

但我不能这样做:

myClass::printStrings.curry("hello").accept("world");

这是有道理的,因为Java 无法知道myClass::printStrings 可以应用于功能接口Consumer2。为此,我创建了一个实用程序类:

public class F {
    public static <T1, T2> Consumer2<T1, T2> c2(Consumer2<T1, T2> fn) {
        return fn;
    }
}

那么我可以:

F.c2(myClass::printStrings).curry("hello").accept("world");

即使这样,也可以:

((Consumer2<String, String>)myClass::printStrings).curry("hello").accept("world");

在这种情况下,只要 Java 8 有某种方法可以理解该函数类型。那么,问题是,最好的方法是什么,同时又可以避免使用样板文件?

【问题讨论】:

  • 我猜class Consumer2 应该是interface Consumer2...

标签: java functional-programming java-8 method-reference


【解决方案1】:

你不是在currying,而是在执行部分函数应用程序。这些操作是相关的,但并不完全相同。柯里化意味着将您的Consumer2&lt;T1, T2&gt; 转换为Function&lt;T1,Consumer1&lt;T2&gt;&gt;。将该 curried 函数应用于 T1 值时,您将得到您的方法有效执行的操作。

使用既定名称 bind 更容易,因为将值绑定到函数的参数是每个开发人员都可以理解的,无需深入研究函数式编程世界。

也就是说,最好记住现在interfaces 可以有static 方法,所以不需要这样的实用程序类。此外,仅返回其参数的static 方法本身并没有什么用处,因此您可以将它与它应该服务的后续方法融合在一起。然后,它实现了与实例方法相同的目的,并且可以作为简单的重载提供:

@FunctionalInterface
public interface Consumer2<T1, T2> {
    void accept(T1 t1, T2 t2);

    default Consumer1<T2> bind(T1 t1) {
        return bind(this, t1);
    }
    static <T,U> Consumer1<U> bind(Consumer2<? super T, ? super U> c, T t) {
        return u -> c.accept(t, u);
    }
}
public interface Consumer1<T1> extends Consumer<T1> {}

public class MyClass {
    public static void printStrings(String a, String b) {
        System.out.println(a + ": " + b);
    }

    public static void main(String[] args) {
        Consumer2.bind(MyClass::printStrings, "hello").accept("world");
    }
}

另一方面,当您使用现有标准interfaces ConsumerBiConsumer 时,您别无选择,只能在与这些interfaces 不同的类中提供实用方法。但好消息是,很容易使解决方案保持一致,因为除了static 方法之外,您无法提供任何其他内容:

class FunctionUtil {
    static <T,U> Consumer<U> bind(BiConsumer<? super T, ? super U> c, T t) {
        return u -> c.accept(t, u);
    }
}
public class MyClass {
    public static void printStrings(String a, String b) {
        System.out.println(a + ": " + b);
    }

    public static void main(String[] args) {
        FunctionUtil.bind(MyClass::printStrings, "hello").accept("world");
    }
}

【讨论】:

    【解决方案2】:

    您涉及F.c2 方法的解决方案很有趣,但是您的示例太人为了。如果你问,如何更好地编写这段代码

    F.c2(myClass::printStrings).curry("hello").accept("world");
    

    那么我肯定会建议你这样写:

    myClass.printStrings("hello", "world");
    

    如果您想问如何将预定义的参数绑定到方法引用,我建议您改用 lambda 函数:

    Consumer1<String> fn = str -> myClass.printStrings("hello", str);
    fn.accept("world");
    

    您可能想考虑在编译时您的函数未知的情况。在这种情况下,它要么从另一个方法返回,要么作为方法参数传递给当前方法,或者存储在变量/字段中。在所有这些情况下,它已经是功能接口,您可以直接使用curry

    Consumer2<String, String> getConsumer2() { return myClass::printStrings; }
    
    getConsumer2().curry("hello").accept("world");
    

    因此,总的来说,我在这里看不到问题。如果你仍然认为对方法引用应用柯里化很有用,我会在 Consumer1 接口中创建一个静态的 curry 方法(虽然我猜它实际上是一个“绑定”,而不是“柯里”):

    static <T1, T2> Consumer1<T2> curry(Consumer2<T1, T2> c2, T1 t1) {
        return c2.curry(t1);
    }
    

    并像这样使用它:

    Consumer1.curry(myClass::printStrings, "hello").accept("world");
    

    【讨论】:

      猜你喜欢
      • 2017-12-27
      • 2022-01-06
      • 1970-01-01
      • 1970-01-01
      • 2020-10-27
      • 1970-01-01
      • 2019-08-09
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多