文章目录

  • Pre
  • 类型检查
  • 同样的 Lambda,不同的函数式接口
    • 菱形运算符
    • 特殊的void兼容规则
  • 类型推断
  • 使用局部变量

Pre

当我们第一次提到Lambda表达式时,说它可以为函数式接口生成一个实例。然而,Lambda
表达式本身并不包含它在实现哪个函数式接口的信息。为了全面了解Lambda表达式,women 应该知道Lambda的实际类型是什么 .


类型检查

Lambda的类型是从使用Lambda的上下文推断出来的。 上下文(比如,接受它传递的方法的参数,或接受它的值的局部变量)中Lambda表达式需要的类型称为目标类型。

举个例子

List<Apple> heavierThan150g = filter(inventory, (Apple a) -> a.getWeight() > 150);

类型检查过程可以分解为如下所示。
 首先,我们要找出 filter 方法的声明。
 第二,要求它是 Predicate<Apple> (目标类型)对象的第二个正式参数。
 第三, Predicate<Apple> 是一个函数式接口,定义了一个叫作 test 的抽象方法。
 第四, test 方法描述了一个函数描述符,它可以接受一个 Apple ,并返回一个 boolean 。
 最后, filter 的任何实际参数都必须匹配这个要求

这段代码是有效的,因为我们所传递的Lambda表达式也同样接受 Apple 为参数,并返回一个boolean 。请注意,如果Lambda表达式抛出一个异常,那么抽象方法所声明的 throws 语句也必须与之匹配


同样的 Lambda,不同的函数式接口

有了目标类型的概念,同一个Lambda表达式就可以与不同的函数式接口联系起来,只要它们的抽象方法签名能够兼容.

我们来看下这两个函数式接口


这两个函数式接口 都是 什么也不接受且返回一个泛型 T 的函数, 所以 下面两个赋值是有效的

 Callable<Integer> integerCallable = () -> 18;PrivilegedAction<Integer> privilegedAction = () -> 18;

第一个赋值的目标类型是 Callable<Integer>
第二个赋值的目标类型是PrivilegedAction<Integer>

再举个栗子 : 同一个Lambda可用于多个不同的函数式接口

    Comparator<Enginner> enginnerComparator = (e1, e2) -> e1.getJob().compareTo(e2.getJob());ToIntBiFunction<Enginner, Enginner> toIntBiFunction = (e1, e2) -> e1.getJob().compareTo(e2.getJob());BiFunction<Enginner, Enginner, Integer> toIntFunction = (e1, e2) -> e1.getJob().compareTo(e2.getJob());

Comparator 、 ToIntBiFunction 、 BiFunction 都是返回一个int类型的的函数


菱形运算符

Java 7中已经引入了菱形运算符( <> ),利用泛型推断从上下文推断类型的思想。 一个类实例表达式可以出现在两个或更多不同的上下文中,并会像下面这样推断出适当的类型参数。

List<String> listOfStrings = new ArrayList<>();
List<Integer> listOfIntegers = new ArrayList<>();

特殊的void兼容规则

如果一个Lambda的主体是一个语句表达式, 它就和一个返回 void 的函数描述符兼容(当然需要参数列表也兼容)。

举个例子:

以下两行都是合法的,尽管 List 的 add 方法返回了一个boolean ,而不是 Consumer 上下文( T -> void )所要求的 void

   List<String> stringList = new ArrayList<>();// Predicate返回了一个booleanPredicate<String> predicate = s -> stringList.add(s);// Consumer返回了一个voidConsumer<String> consumer =    s -> stringList.add(s);

经过了这几个小demo ,是不是能够很好地理解在什么时候以及在哪里可以使用Lambda表达式了。Lambda表达式可以从赋值的上下文、方法调用的上下文(参数和返回值),以及类型转换的上下文中获得目标类型

来个小测验

类型检查——为什么下面的代码不能编译呢?
Object o = () -> {System.out.println("Tricky example"); };答案:
Lambda表达式的上下文是 Object (目标类型)。但 Object 不是一个函数式接口 。 为了解决这个问题,可以把目标类型改成 Runnable ,它的函数描述符是 () -> void :Runnable r = () -> {System.out.println("Tricky example"); };

类型推断

刚才已经讨论了如何利用目标类型来检查一个Lambda是否可以用于某个特定的上下文。其实,
它也可以用来做一些略有不同的事:推断Lambda参数的类型,我们来看下。

Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名,因为函数描述符可以通过目标类型来得到。这样做的好处在于,编译器可以了解Lambda表达式的参数类型,这样就可以在Lambda语法中省去标注参数类型.

举个例子

 List<Enginner> goEngineerList = filter(enginnerList,a-> a.getJob().equals("GO"));

参数 a 没有显式类型 .

再举个栗子 ,Lambda表达式有多个参数,代码可读性的好处就更为明显

 // 没有类型推断,因为给o1,o2指定了Enginner 类型Comparator<Enginner> comparator = (Enginner o1, Enginner o2) -> o1.getJob().compareTo(o2.getJob());//  有类型推断,因为没有给o1,o2指定了Enginner 类型Comparator<Enginner> comparator2 = ( o1,  o2) -> o1.getJob().compareTo(o2.getJob());

个人感觉,第二种写法更简单 。

当Lambda仅有一个类型需要推断的参数时,参数名称两边的括号也可以省略。


使用局部变量

上面所介绍的所有Lambda表达式都只用到了其主体里面的参数。但Lambda表达式也允许使用自由变量(不是参数,而是在外层作用域中定义的变量),就像匿名类一样。 它们被称作捕获Lambda。

举个例子

  int num = 1;Runnable runnable = ()->System.out.println(num);

这么做虽然有点啰嗦,我们这里想要讨论的是 使用外部的变量有什么限制吗?

如果你想要对这个变量进行操作,之前的lambda就报错了。所以说Lambda可以没有限制地捕获(也就是在其主体中引用)实例变量和静态变量,但是局部变量必须显式声明为 final.

换句话说,Lambda表达式只能捕获指派给它们的局部变量一次。(注:捕获实例变量可以被看作捕获最终局部变量 this 。) 如上图。

为什么会这样呢?

  • 第一: 实例变量都存储在堆中,而局部变量则保存在栈上。如果Lambda可以直接访问局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。因此,Java在访问自由局部变量时,实际上是在访问的副本,而不是访问原始变量。如果局部变量仅仅赋值一次那就没有什么区别了——因此就有了这个限制
  • 第二,这一限制不鼓励你使用改变外部变量的典型命令式编程模式,这种模式会阻碍很容易做到的并行处理.

Java 8 - 04 类型检查、类型推断以及限制相关推荐

  1. java中检查性异常类_Java异常处理、java语言推崇使用检查类型异常

    异常处理是java语言的重要特性之一,<Three Rules for effective Exception Handling>一文中是这么解释的:它主要帮助我们在debug的过程中解决 ...

  2. java判捕获e异常类型_Java SE7新特性之捕获多种类型的异常并且重新抛出使用改进的类型检查的异常...

    本文涵盖了以下主题: 处理多种类型的异常 重新抛出使用更宽泛的类型检查的异常 在Java SE 7 以及后续版本中, 一个简单的 catch 块可以处理多种类型的异常.这种特性可以减少重复代码以及对于 ...

  3. java 类型检查_Java开发笔记(五十二)对象的类型检查

    前面介绍了类的多态性,来自于鸡类的实例chicken,既能用来表达公鸡实例,也能用来表达母鸡实例.可是这导致了一个问题,假如在call方法内部需要手工判断输入参数属于公鸡实例还是母鸡实例,那该如何是好 ...

  4. java 类型检查_Java开发对象类型检查详细解析

    原标题:Java开发对象类型检查详细解析 前面介绍了类的多态性,来自于鸡类的实例chicken,既能用来表达公鸡实例,也能用来表达母鸡实例.可是这导致了一个问题,假如在call方法内部需要手工判断输入 ...

  5. python mypy类型检查_Python 类型检查指南

    Python 作为一种动态语言,在 PEP484(3.5) 才支持 Type Hints,且类型申明是 optional 的,对于从静态语言(比如:Java,国内大学专业cs or se的教学语言也是 ...

  6. 疯狂Kotlin讲义学习笔记07章:面向对象(上)对象,中缀,解构,幕后字段、属性,延迟初始化,访问控制符,构造器,继承,重写,super限定,重写,多态,is类型检查,as强制类型转换

    1.定义类的标准格式 修饰符 class 类名 [ constructor 主构造器]{零到多个次构造器定义零到多个属性....零到多个方法.... } 修饰符open是final的反义词,用于修饰一 ...

  7. python 学习指南_Python类型检查终极指南

    python 学习指南 In this guide, you will get a look into Python type checking. Traditionally, types have ...

  8. Java中的运行期类型鉴定

     对于作为程序一部分的每个类,它们都有一个class对象.换言之,每次写一个新类时,同时也会创建一个class对象,更恰当的说,是保存在一个完全同名的.class文件中.在运行期,一旦我们想生成那 ...

  9. Flow - JS静态类型检查工具

    本章的目标是提供一些Flow工具的介绍与使用建议.Flow本质上也只是个检查工具,它并不会自动修正代码中的错误,也不会强制说你没按照它的警告消息修正,就不会让你运行程序.当然,并没有要求什么时候一定要 ...

最新文章

  1. 深度学习核心技术精讲100篇(二十一)-深入理解Dirichlet分布及过程
  2. Disruptor-net
  3. 剑指offer-求二叉树深度
  4. oracle 容器切换,oracle12c 多租户管理四(容器连接切换)
  5. 虚拟化实验室推进计算机网络专业实践教学的解决方案(论文体)
  6. 【Django】文件上传以及celery的使用
  7. bootstraptable 列隐藏_bootstrap中table如何隐藏列?
  8. 过磅系统更换服务器,无人值守过磅系统改造方案
  9. c语言花朵源代码,C语言花朵代码.doc
  10. pythonmt4通讯swot矩阵_swot分析矩阵范例(各部门)
  11. Fall 2020 Berkeley cs61a hw04答案
  12. python虚拟变量回归_虚拟变量回归中的截距项问题
  13. java中重命名文件夹名,【Java】对文件或文件夹进行重命名
  14. 前端--开发“抽屉新热榜”
  15. juniper 设备查看光衰的命令
  16. 51单片机键盘、矩阵键盘松手检测
  17. 【转载】整车CAN网络介绍
  18. APP测试点总结(全面)
  19. android逆向分析腾讯微视研究无限循环视频嵌套滑动不中断的实现方式
  20. ASP.NET c# 实验日记(1)

热门文章

  1. 学生成绩等级评定python_马鞍山市初中学生综合素质评价工作政策解读来了!
  2. 正版七日杀服务器存档,七日杀网吧怎么存档 七日杀网吧存档读档方法介绍-游侠网...
  3. python的模块、包、库区别。导入包/模块的方式
  4. TensorFlow 最小二乘法拟合
  5. transepose 矩阵的转置
  6. BPMF论文辅助笔记: 固定U,更新θU 部分推导
  7. 机器学习笔记:CNN卷积神经网络
  8. Python应用实战-sql操作groupby常用技巧
  9. SENet双塔模型:在推荐领域召回粗排的应用及其它
  10. 产品路线图的三种模型(RICE/MoSCoW/Kano),教你如何对需求进行优先级排序