67

Java 8 引入了 lambda 函数,我想实现类似阶乘的东西:

 IntToDoubleFunction fact = x -> x == 0 ? 1 : x * fact.applyAsDouble(x-1);

编译返回

  error: variable fact might not have been initialized

我如何引用函数本身。类是匿名的,但实例存在:它被称为fact.

4

25 回答 25

51

我通常使用(once-for-all-functional-interfaces defined)通用帮助类,它包装了功能接口类型的变量。这种方法解决了局部变量初始化的问题,让代码看起来更清晰。

如果出现此问题,代码将如下所示:

// 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);
}
于 2014-08-24T16:35:10.150 回答
21

一种方法是编写一个辅助函数 ,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);

在我看来,这似乎比依赖极端情况语义(如捕获对可变结构的引用的闭包)或允许自我引用并警告“可能未初始化”的可能性稍微优雅一些​​。

尽管如此,由于 Java 的类型系统,这并不是一个完美的解决方案——泛型不能保证f的参数 与factHelperfactHelper即相同的输入类型和输出类型)的类型相同,因为这将是一个无限嵌套的泛型。

因此,相反,更安全的解决方案可能是:

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永远不会在不知情的情况下调用它。

于 2014-04-09T05:11:25.683 回答
14

本地和匿名类以及 lambda在创建时按值捕获局部变量。因此,它们不可能通过捕获局部变量来引用自己,因为指向它们自己的值在它们被创建时还不存在。

本地和匿名类中的代码仍然可以使用this. 但是,this在 lambda 中并不指代 lambda;它指的是this来自外部的范围。

您可以捕获一个可变数据结构,如数组,而不是:

IntToDoubleFunction[] foo = { null };
foo[0] = x -> { return  ( x == 0)?1:x* foo[0].applyAsDouble(x-1);};

虽然不是一个优雅的解决方案。

于 2013-10-18T09:18:26.827 回答
10

如果你发现自己经常需要做这种事情,另一种选择是创建一个辅助接口和方法:

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 中用于制作未命名函数的旧技巧。

我不确定我是否会在生产代码中这样做,但这很有趣......

于 2016-03-14T20:14:37.350 回答
5
于 2019-03-26T09:05:50.413 回答
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));
}
}
于 2013-10-17T14:58:49.283 回答
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
于 2014-04-22T14:47:41.833 回答
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));
    }
}
于 2014-04-03T21:09:12.063 回答
2

有点像第一个回复...

public static Function<Integer,Double> factorial;

static {
    factorial = n -> {
        assert n >= 0;
        return (n == 0) ? 1.0 : n * factorial.apply(n - 1);
    };
}
于 2014-06-01T12:54:33.130 回答
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

于 2014-02-08T12:41:17.447 回答
2

一种解决方案是将此函数定义为一个实例属性。

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));
    }
}
于 2013-10-17T14:38:31.993 回答
2
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);
  }
}

在我的测试中,这是我可以为本地递归 lambda 实现的最好的结果。它们也可以在流中使用,但是我们失去了目标类型的简单性。

于 2016-06-10T09:02:02.313 回答
1

我在今年的 JAX 上听说“lambds 不支持递归”。这个语句的意思是 lambda 中的“this”总是指周围的类。

但我设法定义了——至少我如何理解“递归”这个术语——一个递归 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));
  }
}

将其保存在文件“Recursion.java”中,并使用两个命令“javac Recursion.java”和“java Recursion”它对我有用。

clou 是将 lambda 必须实现的接口保留为周围类中的字段变量。lambda 可以引用该字段,并且该字段不会是隐式最终的。

于 2014-05-16T20:19:48.590 回答
1

您还可以通过创建大小为 1 的最终数组(例如 Function[])将其定义为局部变量,然后将该函数分配给元素 0。如果您需要确切的语法,请告诉我

于 2014-07-29T14:08:04.877 回答
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( )

于 2019-03-25T20:43:58.610 回答
1

鉴于 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;
    };
}
于 2016-01-15T19:35:10.117 回答
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);
}
于 2017-08-22T09:34:53.567 回答
1

您可以像这样定义通用定点组合器

public static <T, R> Function<T, R> fixedPointCombinator(Function<Function<T, R>, Function<T, R>> f) {
    return new Function<T, R>() {
        @Override
        public R apply(T n) {
            return f.apply(this).apply(n);
        }
    };
}

Function<Function<Integer, Double>, Function<Integer, Double>> fact =
    self -> n -> n == 0 ? 1 : n * self.apply(n - 1);

System.out.println(fixedPointCombinator(fact).apply(10));

输出:

3628800.0
于 2020-08-04T00:27:01.130 回答
0

在这里了解答案的共同主题是 lambda 可以递归,只要它们具有固定的参考点(因此基于类/接口的答案,例如@assylias@Andrey Morozov@Ian Robertson等)。

我真的很喜欢@000000000000000000000的成员变量解决方法的答案,但我担心预期的 lambda 函数是否想要引用包含函数范围内的其他变量。当然,它会在赋值时评估这些局部引用,并将结果函数放入一个成员变量中,类中的其他方法可以访问它。这听起来……不对(如果包含方法本身被递归调用,可能会变得非常有趣)。

以下是基于类的解决方案的变体,其形式接近于 OP 的原始单行 lambda,但 Eclipse 并没有抱怨。

IntToDoubleFunction fact = new IntToDoubleFunction() {
    @Override
    public double applyAsDouble(int x) {
        return x == 0 ? 1 : x * this.applyAsDouble(x-1);
    }
};

{ } 当然创建了一个匿名类,因此创建了一个新的范围,其中包含用于 lambda 评估的参考点,其额外的好处是仍然在包含函数自己的范围内,因此是“兄弟”变量。

于 2019-06-22T20:54:05.713 回答
0

您也可以自己定义接口,在调用期间将其本身作为参数传递。例如

interface MyOwnFunction<T,R>{
    R apply(MyOwnFunction<T,R> self,T arg);
}
于 2020-01-11T20:43:22.067 回答
0

在关于 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 在执行时被评估 -> 它们可能是递归的

  • 在另一个 lambda 中使用 lambda 变量需要初始化变量 -> 在定义递归 lambda 之前,您必须使用 foo 值定义它

  • 在 lambda 中使用局部 lambda 变量需要变量是最终的,因此不能重新定义它 ->使用 lambda 的类/对象变量,因为它是用默认值初始化的

于 2017-10-18T11:17:18.283 回答
0

问题是 lambda 函数想要对变量进行操作,而我们需要一个可以用我们的 lambda 替换final的可变引用。Function

最简单的技巧似乎是将变量定义为成员变量,编译器不会抱怨。

我将示例更改为使用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);
    }
}
于 2015-09-08T09:54:39.543 回答
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
}
于 2015-12-13T17:44:16.870 回答
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 和函数接口的定义在 1 行中使用任何函数接口,如下所示:

Function<Integer, Integer> factorial = Recursive.of(recursive ->
        x -> x == 0 ? 1 : x * recursive.call().apply(x - 1));
System.out.println(factorial.apply(5));

我发现它非常直观且易于使用。

于 2017-08-02T17:49:47.057 回答
-2

我手边没有 Java8 编译器,所以无法测试我的答案。但是,如果您将“事实”变量定义为最终变量,它会起作用吗?

final IntToDoubleFunction fact = x -> {
    return  ( x == 0)?1:x* fact.applyAsDouble(x-1);
};
于 2014-04-15T14:52:18.267 回答