Lambda类型(高级)

文章来自java期刊:Quiz Yourself: Lambda Types (Advanced) | Java Magazine
翻译采用软件,适当调整和总结

如果你以前做过我们的小测验问题,你知道这些问题不容易。他们模拟了认证考试中的难题。“中级”和“高级”的名称指的是考试而不是题目的难度,尽管通常情况下,“高级”问题都会更难。我们为认证考试写问题,我们也采用这种方式:把形容词放在前面,设计这些问题不是为了忽悠你,而是直接地测试你对编程语言的来龙去脉。
这里的目标是测试java.util.function包中接口的知识。

public void doIt() {
    /* insert here */ v1 = () -> a -> {}; 
}

下列哪一项可以正确替换注释的代码/* insert here */?选一个。

var
Supplier<Consumer<?>>
Function<Consumer<?>,?>
Function<Predicate<?>,?>
Consumer<Function<?,?>>

这个问题研究lambda表达式的各个方面,嵌套lambda的复杂性,用lambda表达式声明和初始化函数式接口,以及java.util.function包中接口的特性。

初次接触时,使用lambda表达式的代码可能很难理解,而且在某些情况下,即使您有相当的经验,也可能很难理解。其中一个很好的技巧是把事情分解成更小的块。每个lambda表达式由一个参数列表和一个方法体两部分组成。有时方法体只是一个简单表达式,有时它是一个带大括号的完整的方法体,但不管怎样,它总是存在的。

因此,让我们先不看返回类型,这是一个给v1赋值的表达式。即:

() -> a -> {}

如果您对Haskell感兴趣,这个语法可能会非常舒服,但是因为这是在讨论java,对许多人来说,它根本就不舒服。
那么,你是怎么分解这个的呢?首先,每个lambda有三个部分:参数列表、箭头和方法体。让我们看看这里是否可以这样处理:

()	            ->	          a -> {}

也许有人会问,你怎么知道第二个箭头不是这个lamda表达式的箭头?有两个原因。首先,在参数列表中不能有箭头。第二,也是更抽象的,就像大多数Java操作符一样,箭头从左到右分组。
现在,你知道这整个混乱的一块是一个没有参数的lambda表达式。这本身就告诉了你很多关于它的事情,但是可以先不管,来看看表达式的主体部分。

可以发现,主体是另一个lambda表达式。实际上,这是lamda表达式主体的短形式。lambda表达式有两种形式:一种形式在右边有花括号,另一种形式没有。没有花括号的只是有花括号的一种特例。看看下面这个lambda,它使用了一个块:

(a, b) -> { return a + b; }

实际上,lambda中的这样一个块是一个完整的方法体(它必须在各个方面都符合这些要求)。但是,这个非常简单,因为它所做的只是计算一个表达式并返回结果值。如果,当方法主体只执行计算表达式并返回结果–您可以移除大括号,return关键字,以及分号。右边的只有表达式。因此,前面的示例与这个较短的形式相同:

(a, b) -> a + b

然而,lambda表达式,至少在Java中,表示的是对象的实例(它们可能以不同的实现方式,但从源代码的角度来看,它们的行为方式跟对象是一样的)。因此,不管第一个lambda是什么,它都返回一个对象。但这个对象并不是简单对象,而是一个实现某些接口的对象。
因此,让我们详细分析返回的值:

a	       ->	       {}

这个表达式中,只使用一个参数,并且该主体以“块lambda”的形式存在,其花括号围绕着一个完整的方法体–尽管该方法体是空的。由于是一个空的方法体,所以这个lambda的返回类型是void。
如果外部lambda也使用了块形式,可能会有一些帮助:

() -> { return a -> {}; }

它告诉您,表达式是一个lambda,它接受零参数并返回“某样东西”。

让我们回到原来的问题。对于这个复杂的嵌套lambda表达式,什么样的声明是可以接受的?
让我们先考虑选项A。使用var伪类型具有这样的变量声明,必须能够从用于初始化该变量的表达式(在本例中是lambda)中确定变量的类型。但是,lambda表达式的类型是依靠声明变量类型确定的。这a是b,b是a。循环依赖关系在逻辑上是不可解析的,因此不允许使用var使用选项A中所示形式的lambda表达式进行伪类型,因此可以确定选项A是不正确的。

a lambda expression determines the functional interface type that it implements from the context to which it is assigned.这句话,究竟是什么意思?被分配的上下文是指什么?参数类型?还是返回结果的类型?其实个人感觉他这里说得不是很好,lambda 的类型是可以根据上下文的参数类型确定的,只是说这不是不能选择A选项的理由,而应该这样解释:
var不能接收lambda 表达式。注意,这里不是说不能接收lambda 的返回结果,只是说当返回结果是一个新的lambda 表达式的时候,不能用var来接收。
当然,包括lambda 的返回,以及普通函数的返回,如果返回的是一个新的lambda 表达式的话,就不能用var来接收了。

对于其余的选项,需要判定这些功能接口的组合是否可以正确地描述lambda。四个选项有Supplier, Consumer, Function,和Predicate。查看API文档,您可以看到它们对于声明的抽象方法如下:

public interface Supplier<T> { T get(); }
public interface Consumer<T> { void accept(T t); }
public interface Function<T, R> { R apply(T t); }
public interface Predicate<T> { boolean test(T t); }

要使lambda有效,lambda的参数列表必须与接口的参数列表一致。此外,lambda表达式的返回类型也必须与接口顺应。(这个词intended(顺应)是为了允许可能发生某些自动转换,比如继承类型等的自动转换。)

作为提醒,您正在寻找一种表示一个lambda,它接受零参数,并返回一个新的lambda表达式,这个新的lambda表达式接收一个参数并返回void。因此,在不考虑返回类型的情况下,这四种类型中的哪一种与采用零参数的lambda兼容?只有一个是,那就是Supplier。这立即表示选项C、D和E必须不正确。当然,这只剩下选项B,但是您还应该判定B是否完全正确。

选项B是Supplier<Consumer<?>>。已经确定,lambda必须并且确实采用零参数。到目前一切尚好。现在让我们考虑返回类型。选项B说明返回类型必须是Consumer(严格地说,Consumer<?>,但这仅仅意味着Consumer可以分配给Object,这意味着您对Consumer的参数完全不清楚。中的返回值。Supplier是(a) -> {}。这个lambda接受一个参数并返回void。这与Consumer,由此可以得出结论,选项B中的代码是完全正确的,选项B确实是正确的答案。

虽然翻译完了,好像也有一些理解了,但离真正搞懂lambda、运用lambda还有很远的距离。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值