Java 8引入了lambda函数,我想实现像factorial这样的东西:
IntToDoubleFunction fact = x -> x == 0 ? 1 : x * fact.applyAsDouble(x-1);
编译返回
error: variable fact might not have been initialized
如何参考功能本身。类是匿名的但实例存在:它被称为fact
。
答案 0 :(得分:39)
我通常使用(一次为所有功能接口定义)通用助手类,它包装了功能接口类型的变量。 这种方法解决了局部变量初始化的问题,并使代码看起来更清晰。
如果出现此问题,代码将如下所示:
// 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);
}
答案 1 :(得分:16)
一种方法是编写一个辅助函数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);
在我看来,这比依赖于角落案例语义更优雅,例如一个捕获对可变结构的引用的闭包,或者允许自引用并警告“可能没有初始化”。< / p>
但是,由于Java的类型系统,它不是一个完美的解决方案 - 泛型不能保证f
(factHelper
的参数与factHelper
的类型相同(即相同的输入类型和输出类型),因为那将是一个无限嵌套的泛型。
因此,相反,更安全的解决方案可能是:
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
不完美的泛型类型产生的代码气味现在包含在lambda中(或者,我敢说,封装),确保factHelper
永远不会在不知不觉中被调用
答案 2 :(得分:13)
本地和匿名类以及lambdas在创建时按值捕获局部变量。因此,他们不可能通过捕获局部变量来引用自己,因为在创建它们时,指向它们的值还不存在。
本地和匿名类中的代码仍然可以使用this
引用自己。但是,lambda中的this
不是指lambda;它指的是来自外部范围的this
。
您可以捕获可变数据结构,例如数组:
IntToDoubleFunction[] foo = { null };
foo[0] = x -> { return ( x == 0)?1:x* foo[0].applyAsDouble(x-1);};
虽然不是一个优雅的解决方案。
答案 3 :(得分:7)
如果你发现自己经常需要做这种事情,另一种选择是创建一个辅助接口和方法:
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));
(虽然我通常使用引用类型执行此操作,但您也可以创建基本特定版本。)
这借用了The Little Lisper的一个老技巧来制作未命名的功能。
我不确定我是否会在生产代码中执行此操作,但这很有趣......
答案 4 :(得分:2)
使用累加器的另一个版本,以便可以优化递归。 已移至通用接口定义。
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));
}
}
答案 5 :(得分:2)
您可以将递归lambda定义为实例或类变量:
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
。
答案 6 :(得分:2)
public class Main {
static class Wrapper {
Function<Integer, Integer> f;
}
public static void main(String[] args) {
final Wrapper w = new Wrapper();
w.f = x -> x == 0 ? 1 : x * w.f.apply(x - 1);
System.out.println(w.f.apply(10));
}
}
答案 7 :(得分:2)
一种解决方案是将此函数定义为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));
}
}
答案 8 :(得分:2)
以下作品但看起来确实很神秘。
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
答案 9 :(得分:2)
有点像第一个回复...
public static Function<Integer,Double> factorial;
static {
factorial = n -> {
assert n >= 0;
return (n == 0) ? 1.0 : n * factorial.apply(n - 1);
};
}
答案 10 :(得分:1)
在使用Fibonacci作为可能的用例的Lambdas讲座中遇到了这个问题。
你可以像这样制作一个递归的lambda:
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));
}
}
}
你有什么要记住的?
Lambda在执行时进行评估 - &gt;它们可能是递归的
在另一个lambda中使用lambda变量需要 要初始化的变量 - &gt;在定义递归lambda之前你 必须用foo值定义它
在lambda中使用本地lambda变量需要变量 是最终的,因此无法重新定义 - &gt; 使用类/对象 lambda 的变量,因为它是使用默认值
答案 11 :(得分:1)
Java 8的另一个递归因子
public static int factorial(int i) {
final UnaryOperator<Integer> func = x -> x == 0 ? 1 : x * factorial(x - 1);
return func.apply(i);
}
答案 12 :(得分:1)
@IanRobertson很好,事实上,您可以将静态“工厂”移到接口本身的主体中,从而将其完全封装:
public static interface Recursable<T, U> {
U apply(T t, Recursable<T, U> r);
public static <T, U> Function<T, U> recurseable(Recursable<T, U> f) {
return t -> f.apply(t, f);
}
}
这是到目前为止我所看到的最干净的解决方案/答案……尤其是因为“事实”的调用是“自然地”编写的:fac.apply(n)是一元函数所希望看到的就像fac()
答案 13 :(得分:1)
答案是:您必须在名称变量调用applyAsDouble函数之前使用 this :-
IntToDoubleFunction fact = x -> x == 0 ? 1 : x * this.fact.applyAsDouble(x-1);
如果您将事实定为最终事实,也将适用
final IntToDoubleFunction fact = x -> x == 0 ? 1 : x * this.fact.applyAsDouble(x-1);
我们可以在此处使用功能界面 UnaryOperator 。一元运算符,始终返回其输入参数。
1)只需添加此。函数名称之前,如:
UnaryOperator<Long> fact = x -> x == 0 ? 1 : x * this.fact.apply(x - 1 );
此操作将避免“在定义字段之前无法引用字段” 。
2)如果您希望使用静态字段,只需将其替换为类的名称:
static final UnaryOperator<Long> fact = x -> x== 0? 1: x * MyFactorial.fact.apply(x - 1 );
答案 14 :(得分:1)
考虑到&#34;这个&#34;在lambda中引用包含类,下面编译时没有错误(当然还有添加的依赖项):
data[0][0:1439,1]
答案 15 :(得分:1)
您也可以通过创建一个大小为1的最终数组(比如Function [])将其定义为局部变量,然后将该函数分配给元素0.如果您需要确切的语法,请告诉我
答案 16 :(得分:1)
我今年在JAX上听说过,&#34; lambads不支持递归&#34;。这句话的意思是&#34;这个&#34; lambda里面总是指周围的类。
但我设法定义 - 至少我如何理解术语&#34;递归&#34; - 一个递归的lambda,就像那样:
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));
}
}
将其保存在文件中&#34; Recursion.java&#34;并使用两个命令&#34; javac Recursion.java&#34;和#34; java递归&#34;它对我有用。
clou是保持lambda必须在周围类中作为字段变量实现的接口。 lambda可以引用该字段,该字段不会隐式最终。
答案 17 :(得分:0)
在这里选择答案的通用主题是,lambda可以递归,只要它们具有固定的参考点即可(因此,基于类/接口的答案,例如@assylias,@Andrey Morozov,{{ 3}}等。
我真的很喜欢@Ian Robertson的答案,它提供了成员变量解决方法,但是我担心要使用的lambda函数是否要引用包含函数范围内的其他变量。当然,它将在赋值时评估这些本地引用,并将结果函数放入成员变量中,该变量可以由类中的其他方法访问。听起来不... 正确(如果包含方法本身被递归调用,可能会很有趣)。
以下是基于类的解决方案的一种变体,其表达形式接近于OP的原始单行lambda,但Eclipse对此并不抱怨。
IntToDoubleFunction fact = new IntToDoubleFunction() {
@Override
public double applyAsDouble(int x) {
return x == 0 ? 1 : x * this.applyAsDouble(x-1);
}
};
{}当然会创建一个匿名类,并因此为lambda的评估创建一个带有参考点的新范围,其附加好处是仍在包含函数自己的范围之内,因此也包含“兄弟”变量。
答案 18 :(得分:0)
您也可以自己定义接口,以便在调用过程中将其本身作为参数传递。例如
interface MyOwnFunction<T,R>{
R apply(MyOwnFunction<T,R> self,T arg);
}
答案 19 :(得分:0)
尝试一下。
Function<Integer, Integer> factorial = n ->
new Object() {
Function<Integer, Integer> fact = n -> n <= 0 ? 1 : this.fact.apply(n - 1) * n;
}.fact.apply(n);
System.out.println(factorial.apply(10));
输出
3628800
答案 20 :(得分:0)
您可以使用此类创建递归函数:
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;
}
}
}
然后你可以使用lambda和你的功能界面的定义在一行中使用任何功能界面,如下所示:
Function<Integer, Integer> factorial = Recursive.of(recursive ->
x -> x == 0 ? 1 : x * recursive.call().apply(x - 1));
System.out.println(factorial.apply(5));
我发现它非常直观且易于使用。
答案 21 :(得分:0)
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);
}
}
在我的测试中,这是我可以为本地递归lambdas实现的最好的。 它们也可以在流中使用,但是我们忽略了目标类型的简单性。
答案 22 :(得分:0)
这是一种不依赖副作用的解决方案。为了使目的有趣,让我们说你想要在递归上进行抽象(否则实例字段解决方案是完全有效的)。 诀窍是使用匿名类来获取'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
}
答案 23 :(得分:0)
问题是,lambda函数想要对final
变量进行操作,而我们需要一个可变的Function
- 引用,可以用我们的lambda替换。
最简单的技巧似乎是将变量定义为成员变量,编译器不会抱怨。
我将我的示例更改为使用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);
}
}
答案 24 :(得分:-2)
我没有Java8编译器方便,所以无法测试我的答案。但如果你将'fact'变量定义为final,它会起作用吗?
final IntToDoubleFunction fact = x -> {
return ( x == 0)?1:x* fact.applyAsDouble(x-1);
};