使用Java 8实现recursionlambda函数
Java 8引入了lambda函数,我想实现像factorial:
IntToDoubleFunction fact = x -> x == 0 ? 1 : x * fact.applyAsDouble(x-1);
编译返回
error: variable fact might not have been initialized
我怎样才能引用函数本身。 类是匿名的,但实例存在:它被称为fact
。
我通常使用(定义了所有function的接口)generics辅助类,它封装了函数接口types的variables。 这种方法解决了局部variables初始化的问题,并使代码看起来更清晰。
在这个问题的情况下,代码将如下所示:
// Recursive.java // @param <I> - Functional Interface Type public class Recursive<I> { public I func; } // Test.java public double factorial(int n) { Recursive<IntToDoubleFunction> recursive = new Recursive<>(); recursive.func = x -> (x == 0) ? 1 : x * recursive.func.applyAsDouble(x - 1); return recursive.func.applyAsDouble(n); }
一种方法是编写一个辅助函数helper
,它接受一个函数和一个数字作为参数,然后编写你真正想要的函数fact = helper(helper,x)
。
像这样:
BiFunction<BiFunction, Double, Double> factHelper = (f, x) -> (x == 0) ? 1.0 : x*(double)f.apply(f,x-1); Function<Double, Double> fact = x -> factHelper.apply(factHelper, x);
这在我看来似乎比依靠像closures这样的angular落案例语义稍微更优雅,它捕获对可变结构的引用,或者允许自引用“可能未被初始化”的警告。
不过,由于Java的types系统,这不是一个完美的解决scheme – generics不能保证factHelper
的参数f
与factHelper
(即相同的inputtypes和输出types)具有相同的types,因为这将是无限嵌套的通用的。
因此,一个更安全的解决scheme可能是:
Function<Double, Double> fact = x -> { BiFunction<BiFunction, Double, Double> factHelper = (f, d) -> (d == 0) ? 1.0 : d*(double)f.apply(f,d-1); return factHelper.apply(factHelper, x); };
factHelper
的不完美generics所带来的代码气味现在已经包含在lambda中(或者我敢说,封装)在lambda中,确保factHelper
永远不会被无意识地调用。
本地和匿名类以及lambdaexpression式在创build时通过值捕获局部variables。 因此,他们不可能通过捕获一个局部variables来引用自己,因为指向自己的值在创build的时候还不存在。
本地和匿名类中的代码仍然可以使用this
引用自己。 但是, this
在一个lambda不指lambda; 它是从外部的范围来看这个。
你可以捕获一个可变数据结构,就像数组一样:
IntToDoubleFunction[] foo = { null }; foo[0] = x -> { return ( x == 0)?1:x* foo[0].applyAsDouble(x-1);};
尽pipe几乎没有一个优雅的解
你可以定义一个recursionlambda作为一个实例或类variables:
static DoubleUnaryOperator factorial = x -> x == 0 ? 1 : x * factorial.applyAsDouble(x - 1);
例如:
class Test { static DoubleUnaryOperator factorial = x -> x == 0 ? 1 : x * factorial.applyAsDouble(x - 1)); public static void main(String[] args) { System.out.println(factorial.applyAsDouble(5)); } }
打印120.0
。
一种解决方法是将此函数定义为INSTANCE属性。
import java.util.function.*; public class Test{ IntToDoubleFunction fact = x -> { return ( x == 0)?1:x* fact.applyAsDouble(x-1);}; public static void main(String[] args) { Test test = new Test(); test.doIt(); } public void doIt(){ System.out.println("fact(3)=" + fact.applyAsDouble(3)); } }
另一个版本使用累加器,以便可以优化recursion。 移到通用接口定义。
Function<Integer,Double> facts = x -> { return ( x == 0)?1:x* facts.apply(x-1);}; BiFunction<Integer,Double,Double> factAcc= (x,acc) -> { return (x == 0)?acc:factAcc.apply(x- 1,acc*x);}; Function<Integer,Double> fact = x -> factAcc.apply(x,1.0) ; public static void main(String[] args) { Test test = new Test(); test.doIt(); } public void doIt(){ int val=70; System.out.println("fact(" + val + ")=" + fact.apply(val)); } }
public class Main { static class Wrapper { Function<Integer, Integer> f; } public static void main(String[] args) { final Wrapper w = new Wrapper(); wf = x -> x == 0 ? 1 : x * wfapply(x - 1); System.out.println(wfapply(10)); } }
以下的作品,但似乎很神秘。
import java.util.function.Function; class recursion{ Function<Integer,Integer> factorial_lambda; // The positions of the lambda declaration and initialization must be as is. public static void main(String[] args) { new recursion();} public recursion() { factorial_lambda=(i)->{ if(i==1) return 1; else return i*(factorial_lambda.apply(i-1)); }; System.out.println(factorial_lambda.apply(5)); } } // Output 120
有点像第一个回复…
public static Function<Integer,Double> factorial; static { factorial = n -> { assert n >= 0; return (n == 0) ? 1.0 : n * factorial.apply(n - 1); }; }
如果你发现自己需要经常做这样的事情,另一个select是创build一个帮助器接口和方法:
public static interface Recursable<T, U> { U apply(T t, Recursable<T, U> r); } public static <T, U> Function<T, U> recurse(Recursable<T, U> f) { return t -> f.apply(t, f); }
然后写:
Function<Integer, Double> fact = recurse( (i, f) -> 0 == i ? 1 : i * f.apply(i - 1, f));
(虽然我一般用引用types来做这个,但是你也可以制作原生的版本)。
这借用了Little Lisper中一个用来制作无名函数的旧技巧。
我不确定我是否会在生产代码中这样做,但这很有趣。
我今年在JAX听到,“lambda不支持recursion”。 这个陈述的含义是,lambda中的“this”总是指向周围的类。
但我设法定义 – 至less我怎么理解术语“recursion” – recursion拉姆达,它是这样的:
interface FacInterface { int fac(int i); } public class Recursion { static FacInterface f; public static void main(String[] args) { int j = (args.length == 1) ? new Integer(args[0]) : 10; f = (i) -> { if ( i == 1) return 1; else return i*f.fac( i-1 ); }; System.out.println( j+ "! = " + f.fac(j)); } }
将其保存在“Recursion.java”文件中,并使用“javac Recursion.java”和“javarecursion”两个命令为我工作。
clou保持lambda必须实现的接口作为周围类中的字段variables。 lambda可以引用该字段,并且该字段不会隐式地结束。
你也可以通过创build一个大小为1的最终数组来定义它作为局部variables,然后将该函数赋值给元素0.让我知道如果你需要确切的语法
鉴于lambda中的“this”引用了包含类,下面的代码编译时没有错误(当然是添加了依赖关系):
public class MyClass { Function<Map, CustomStruct> sourceToStruct = source -> { CustomStruct result; Object value; for (String key : source.keySet()) { value = source.get(key); if (value instanceof Map) { value = this.sourceToStruct.apply((Map) value); } result.setValue(key, value); } return result; }; }
问题是,lambda函数想要对final
variables进行操作,而我们需要一个可以用lambdareplace的可变Function
参数。
最简单的伎俩,似乎是将variables定义为成员variables,编译器不会抱怨。
我改变了我的例子,使用IntUnaryOperator
而不是IntToDoubleFunction
,因为我们只是在这里操作Integers
。
import org.junit.Test; import java.util.function.IntUnaryOperator; import static org.junit.Assert.assertEquals; public class RecursiveTest { private IntUnaryOperator operator; @Test public void factorialOfFive(){ IntUnaryOperator factorial = factorial(); assertEquals(factorial.applyAsInt(5), 120); // passes } public IntUnaryOperator factorial() { return operator = x -> (x == 0) ? 1 : x * operator.applyAsInt(x - 1); } }
这是一个不依赖于副作用的解决scheme。 为了使目的有趣,让我们说,你想抽象的recursion(否则实例字段的解决scheme是完全有效的)。 诀窍是使用匿名类来获得“this”引用:
public static IntToLongFunction reduce(int zeroCase, LongBinaryOperator reduce) { return new Object() { IntToLongFunction f = x -> x == 0 ? zeroCase : reduce.applyAsLong(x, this.f.applyAsLong(x - 1)); }.f; } public static void main(String[] args) { IntToLongFunction fact = reduce(1, (a, b) -> a * b); IntToLongFunction sum = reduce(0, (a, b) -> a + b); System.out.println(fact.applyAsLong(5)); // 120 System.out.println(sum.applyAsLong(5)); // 15 }
public class LambdaExperiments { @FunctionalInterface public interface RFunction<T, R> extends Function<T, R> { R recursiveCall(Function<? super T, ? extends R> func, T in); default R apply(T in) { return recursiveCall(this, in); } } @FunctionalInterface public interface RConsumer<T> extends Consumer<T> { void recursiveCall(Consumer<? super T> func, T in); default void accept(T in) { recursiveCall(this, in); } } @FunctionalInterface public interface RBiConsumer<T, U> extends BiConsumer<T, U> { void recursiveCall(BiConsumer<T, U> func, T t, U u); default void accept(T t, U u) { recursiveCall(this, t, u); } } public static void main(String[] args) { RFunction<Integer, Integer> fibo = (f, x) -> x > 1 ? f.apply(x - 1) + f.apply(x - 2) : x; RConsumer<Integer> decreasingPrint = (f, x) -> { System.out.println(x); if (x > 0) f.accept(x - 1); }; System.out.println("Fibonnaci(15):" + fibo.apply(15)); decreasingPrint.accept(5); } }
在我的testing中,这是我可以实现的最好的本地recursionlambdaexpression式。 它们也可以在stream中使用,但是我们放弃了目标打字的简单性。
你可以使用这个类创build一个recursion函数:
public class Recursive<I> { private Recursive() { } private I i; public static <I> I of(Function<RecursiveSupplier<I>, I> f) { Recursive<I> rec = new Recursive<>(); RecursiveSupplier<I> sup = new RecursiveSupplier<>(); rec.i = f.apply(sup); sup.i = rec.i; return rec.i; } public static class RecursiveSupplier<I> { private I i; public I call() { return i; } } }
然后你可以在1行中使用任何函数接口,使用lambdaexpression式,你的函数接口定义如下:
Function<Integer, Integer> factorial = Recursive.of(recursive -> x -> x == 0 ? 1 : x * recursive.call().apply(x - 1)); System.out.println(factorial.apply(5));
我发现它非常直观,易于使用。
Java 8的另一个recursion阶乘
public static int factorial(int i) { final UnaryOperator<Integer> func = x -> x == 0 ? 1 : x * factorial(x - 1); return func.apply(i); }
在一个关于Lambdas的演讲中,把这个问题当作一个可能的用例来使用斐波那契。
你可以像这样做一个recursionlambda:
import java.util.function.Function; public class Fib { static Function<Integer, Integer> fib; public static void main(String[] args) { fib = (n) -> { return n > 1 ? fib.apply(n-1) + fib.apply(n-2) : n; }; for(int i = 0; i < 10; i++){ System.out.println("fib(" + i + ") = " + fib.apply(i)); } } }
你有什么要记住的?
-
Lambdas在执行时被评估 – >它们可能是recursion的
-
在另一个lambda中使用lambdavariables需要variables被初始化 – >在定义一个recursionlambda之前,您必须用一个foo值来定义它
-
在lambda中使用局部lambdavariables需要variables是final的,因此不能被重新定义 – > 为lambda使用类/对象variables,因为它使用默认值进行初始化
我没有方便的Java8编译器,所以不能testing我的答案。 但是,如果将“事实”variables定义为最终的,它会起作用吗?
final IntToDoubleFunction fact = x -> { return ( x == 0)?1:x* fact.applyAsDouble(x-1); };