Java 8 lambdas,Function.identity()或t-> t

211 java lambda java-8

我对该Function.identity()方法的用法有疑问.

想象一下以下代码:

Arrays.asList("a", "b", "c")
          .stream()
          .map(Function.identity()) // <- This,
          .map(str -> str)          // <- is the same as this.
          .collect(Collectors.toMap(
                       Function.identity(), // <-- And this,
                       str -> str));        // <-- is the same as this.
Run Code Online (Sandbox Code Playgroud)

是否有任何理由你应该使用Function.identity()而不是str->str(反之亦然).我认为第二种选择更具可读性(当然是品味问题).但是,有没有"真正的"理由为什么应该首选?

Hol*_*ger 279

从当前的JRE实现开始,Function.identity()将始终返回相同的实例,而每次出现时identifier -> identifier不仅会创建自己的实例,甚至还会有一个不同的实现类.有关详细信息,请参阅此处.

原因是编译器生成一个合成方法,该方法保存该lambda表达式的普通主体(在x->x等效的情况下return identifier;)并告诉运行时创建调用此方法的功能接口的实现.因此,运行时只能看到不同的目标方法,并且当前实现不会分析方法以确定某些方法是否相同.

因此,使用Function.identity()而不是x -> x可能会节省一些内存,但如果您真的认为它x -> x比可读性更强,则不应该推动您的决定Function.identity().

您还可以考虑在启用调试信息进行编译时,合成方法将具有指向包含lambda表达式的源代码行的行调试属性,因此您可以Function在调试时查找特定实例的源代码..相反,当遇到Function.identity()调试操作期间返回的实例时,您将不知道谁调用了该方法并将实例传递给操作.

  • @Tagir Valeev:您可以调试接收任意函数的代码并进入该函数的apply方法.然后你可能会得到一个lambda表达式的源代码.在显式lambda表达式的情况下,您将知道函数的来源,并有机会识别通过身份函数传递决策的位置.当使用`Function.identity()`时,信息丢失了.然后,调用链可以在简单的情况下提供帮助,但可以考虑,例如多线程评估,其中原始启动器不在堆栈跟踪中... (12认同)
  • @Wim Deblauwe:有意思,但我总是会反过来看:如果工厂方法没有在其文档中明确声明它将在每次调用时返回一个新实例,那么你不能认为它会.所以如果没有,那就不足为奇了.毕竟,这是使用工厂方法而不是"新"的一个重要原因.`new Foo(...)`保证创建一个确切类型`Foo`的新实例,而`,Foo.getInstance(...)`可以返回一个现有的实例(子类型)`Foo` ... (11认同)
  • 很好的答案.我对调试有些怀疑.它有用吗?它不太可能获得涉及`x - > x`帧的异常堆栈跟踪.你建议把断点设置为这个lambda吗?通常将断点放入单表达式lambda(至少在Eclipse中)并不容易...... (3认同)
  • 在这种情况下有趣的是:http://blog.codefx.org/java/instances-non-capturing-lambdas/ (2认同)
  • @dfreis [你不是第一个](/sf/answers/1962320041/) (2认同)

Psh*_*emo 80

在你的例子中,两者之间没有太大的区别str -> str,Function.identity()因为内部简单t->t.

但有时我们不能使用,Function.identity因为我们不能使用Function.看看这里:

List<Integer> list = new ArrayList<>();
list.add(1);
list.add(2);
Run Code Online (Sandbox Code Playgroud)

这将编译好

int[] arrayOK = list.stream().mapToInt(i -> i).toArray();
Run Code Online (Sandbox Code Playgroud)

但如果你试图编译

int[] arrayProblem = list.stream().mapToInt(Function.identity()).toArray();
Run Code Online (Sandbox Code Playgroud)

因为mapToInt期望ToIntFunction,你将得到编译错误,这与之无关Function.也ToIntFunction没有identity()方法.

  • 我更喜欢`mapToInt(Integer :: intValue)`. (15认同)
  • @shmosel没关系,但值得一提的是,两个解决方案的工作方式类似,因为`mapToInt(i - > i)`是`mapToInt((Integer i) - > i.intValue())`的简化.使用你认为更清楚的版本,对我来说,mapToInt(i - > i)更好地展示了这段代码的意图. (4认同)
  • 有关使用`Function.identity()`替换`i - > i`将导致编译器错误的另一个示例,请参见http://stackoverflow.com/q/38034982/14731. (3认同)

Jas*_*onN 41

来自JDK来源:

static <T> Function<T, T> identity() {
    return t -> t;
}
Run Code Online (Sandbox Code Playgroud)

所以,不,只要它在语法上是正确的.

  • @orbfish:这完全符合要求.源代码中每次出现的`t-> t`都可能创建一个对象,而`Function.identity()`的实现是*one*occurrence.因此,调用`identity()`的所有调用站点将共享该一个对象,而所有明确使用lambda表达式`t-> t`的站点将创建自己的对象.方法`Function.identity()`在任何方面都不是特殊的,每当你创建一个封装常用lambda表达式的工厂方法并调用该方法而不是重复lambda表达式时,你可以节省一些内存,*给定当前的实现*. (23认同)
  • 我想知道这是否会使上述与创建对象的lambda相关的答案无效 - 或者这是否是一个特定的实现. (6认同)
  • @DanielGray 决定是在运行时做出的。编译器插入一条“invokedynamic”指令,该指令在第一次执行时通过执行所谓的引导方法进行链接,在 lambda 表达式的情况下,该指令位于 [`LambdaMetafactory`](https://docs.oracle.com /javase/8/docs/api/java/lang/invoke/LambdaMetafactory.html)。此实现决定返回构造函数、工厂方法或始终返回相同对象的代码的句柄。它还可能决定返回指向已存在句柄的链接(目前不会发生)。 (3认同)
  • @JasonN 该方法可能会被内联,但语义不会改变。但请记住,是否创建新实例都被视为实现细节。所以我们在这里讨论一个特定的实现。即使方法被内联,这个特定的实现也将重用创建的对象。但是,在内联并应用各种优化之后,生成的代码可能根本不使用该对象。 (2认同)
  • @JasonN 优化不允许更改代码的语义。因此该方法是否内联并不重要。这里有两种观点。低级视图看到一个“invokedynamic”指令,该指令不允许多次链接,并且不允许将其复制到多个调用站点来更改它,因此所有这些都必须链接到总是返回的相同代码在引导期间创建的单个实例。高级视图知道 lambda 表达式的对象标识未指定,但是,它不需要内联即可知道这一点。 (2认同)