title: Lambda表达式
date: 2017-01-16 02:39:56
tags:
- Java
- 读书笔记
- Functional
category: Java
Lambda表达式
Lambda表达式:一段可以传递的代码
Java8之前不能单纯的传递代码,必须先创建对象,以对象为载体,通过传递对象达到传递代码的目的.
为了简化传递代码,在Java8中引入了对lambda表达式的支持。
Lambda表达式中代码的传递是以函数为载体的,一个函数最基本的构成为:
- 函数声明:
- 返回值
- 函数名
- 参数列表
- 函数体
返回值可以根据函数体的执行结果确定,如果函数只调用一次,那么函数名也可以省略(匿名)。
一个函数就可以概括成参数和函数体(即就是表达式)。
所以Lambda表达式的语法为:(参数列表声明)->{表达式}
函数式接口
函数式接口:只包含一个抽象方法的接口
Java8中lambda表达式能做的唯一一件事:转换函数式接口
如果一个方法的参数为函数式接口的对象,可以使用Lamdba表达式来替代创建函数式接口的对象。
例如:
Comparator
接口只有int compare(T o1, T o2);
这一个抽象方法,所以是一个函数式接口。
Arrays.sort(T[] a, Comparator<? super T> c)
接收Comparator
的对象,而c.compare()
函数体内包含实际执行的代码.
Java8之前:
调用Arrays.sort(T[] a, Comparator<? super T> c)
之前必须先创建Comparator
接口实例对象。
Java8中lambda表达式对开发人员可以简单理解为:
传入一个lambda表达式,系统会自动创建Comparator
对象,调用该对象的compare()
方法时会执行lambda表达式中的代码。
实际的编译,运行过程并不是上面描述的这样,但我们可以简单的这么理解。
实际上lambda表达式依赖了JDK7引入的invokedynamic
指令,在多数情况下,lambda都拥有比内部类更好的性能。
方法引用
Lambda表达式的本质是传递函数,如果现有的函数完全能达到我们的需求,我们就可以直接传递函数,这就是方法引用。
方法引用的基本语法:类/对象::方法
,可能存在的对应关系有:
- 对象::实例方法
- 类::静态方法
- 类::实例方法
第三种情况最为特殊,表达的意思是第一个参数作为方法调用的对象。
关于重载方法的问题,根据参数类型自动匹配。
可以作为对象的不仅有普通对象,还有this
和super
两个隐含对象。
对构造器则使用类::new
的形式调用,根据上下文类型信息会比配构造器。
变量作用域
lambda表达式不同于函数的地方:
函数是独立的,而lambda包含上下文,lambda可以引用定义它的上下文变量(闭包)。
Java8之前为了实现对上下文的引用,使用的是内部类访问外部的final局部变量,Java8中放宽了这条规则,不要求变量必须使用final声明了,但是仍然要求不改变变量,编译器会对做部分此检查。
如果变量是一个实例变量,编译器无法检查不变性,但是不建议这么使用。
lambda表达式的作用域与嵌套它的上下文环境一样。
在lambda表达式中使用this
指向的是创建该lambda表达式的方法的对象,这也间接可以说明lambda表达式不是由内部类实现的。
默认方法
Java8中允许接口中定义并实现方法,这样的方法称为默认方法。
-
优点:方便的扩展接口方法
例如为
Collectoin
接口添加一个forEach
方法,如果没有默认方法的支持,我们需要为每个子类增加对应的实现,这是灾难式的,但默认方法只需要在一个接口中添加就可以了。 -
缺点:方法冲突
在继承体系中,继承的接口与接口,或接口与类可能实现了相同的方法,造成冲突。
Java8的规则:
- 接口与接口的方法冲突:不管有没有接口实现了方法,子类中一律必须重新实现
- 接口与类的方法冲突:子类默认继承类的实现方法(类优先)
Java8允许在接口中定义方法可以将原本工具类中的静态方法定义到接口中,避免了接口和工具类的分离。
同时将原本高层的工具类中的方法分散到各个子接口中,可以更清楚的使用类型。
例如:原本定在Collections
中的nCopies
方法现在定义在List
中,写法更加清晰:
之前:Collections.nCopies(10, "Hello");
现在:List.nCopies(10, "Hello");