每日学习五分钟—Java8中的Lambda表达式详解(附全套java教程)
一.什么是函数式编程
函数式编程是种编程方式,它将电脑运算视为函数的计算。函数编程语言最重要的基础是λ演算(lambda calculus),而且λ演算的函数可以接受函数当作输入(参数)和输出(返回值)。和指令式编程相比,函数式编程强调函数的计算比指令的执行重要。和过程化编程相比,函数式编程里函数的计算可随时调用。
1.函数式编程的特点
函数是"第一等公民":指的是函数与其他数据类型一样,处于平等地位,可以赋值给其他变量,也可以作为参数,传入另一个函数,或者作为别的函数的返回值。举例来说,下面代码中的print变量就是一个函数,可以作为另一个函数的参数。var print = function(i){ console.log(i);}; [1,2,3].forEach(print);
只用"表达式",不用"语句":"表达式"(expression)是一个单纯的运算过程,总是有返回值;"语句"(statement)是执行某种操作,没有返回值。函数式编程要求,只使用表达式,不使用语句。也就是说,每一步都是单纯的运算,而且都有返回值。
没有"副作用":,指的是函数内部与外部互动(最典型的情况,就是修改全局变量的值),产生运算以外的其他结果。函数式编程强调没有"副作用",意味着函数要保持独立,所有功能就是返回一个新的值,没有其他行为,尤其是不得修改外部变量的值。
不修改状态:函数式编程只是返回新的值,不修改系统变量。因此,不修改变量,也是它的一个重要特点。
引用透明性:函数程序通常还加强引用透明性,即如果提供同样的输入,那么函数总是返回同样的结果。
副作用:副作用是修改系统状态的语言结构。
2.函数式编程的优缺点
优点:
代码简洁,开发快速:函数式编程大量使用函数,减少了代码的重复,因此程序比较短,开发速度较快。
接近自然语言,易于理解:函数式编程的自由度很高,可以写出很接近自然语言的代码。将表达式(1 + 2) * 3 - 4,写成函数式语言:subtract(multiply(add(1,2), 3), 4)
更方便的代码管理:函数式编程不依赖、也不会改变外界的状态,只要给定输入参数,返回的结果必定相同。因此,每一个函数都可以被看做独立单元,很有利于进行单元测试(unit testing)和除错(debugging),以及模块化组合。
易于"并发编程":函数式编程不需要考虑"死锁"(deadlock),因为它不修改变量,所以根本不存在"锁"线程的问题。不必担心一个线程的数据,被另一个线程修改,所以可以很放心地把工作分摊到多个线程,部署"并发编程"(concurrency)。
代码的热升级:函数式编程没有副作用,只要保证接口不变,内部实现是外部无关的。所以,可以在运行状态下直接升级代码,不需要重启,也不需要停机。
缺点:
函数式编程常被认为严重耗费在CPU和存储器资源。早期的函数式编程语言实现时并无考虑过效率问题;有些非函数式编程语言为求提升速度,不提供自动边界检查或自动垃圾回收等功能;惰性求值亦为语言增加了额外的管理工作。
因为其灵活的语法控制不好程序的结构,带来了语言学习难度高,代码维护性差等缺点。
二.什么是Lambda
Lambda 允许把函数作为一个方法的参数(函数作为参数传递进方法中)。
1.Lambda表达式的优缺点
优点:
少量的代码就能替代以前的一大堆循环判断过滤等,代码简洁。
缺点:
用Lambda充当匿名内部类、方法引用等场合效率低。
Lambda的特点还在于开发成本高,并且异常难以排查。它的异常堆栈比匿名内部类还要难懂。如果你把stream的操作都写在同一行,则问题更甚。
代码维护性差。
2.Lambda表达式的语法
Java8中Lambda 表达式由三个部分组成:
第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;
第二部分为一个箭头符号:->;
第三部分为方法体,可以是表达式和代码块。
1. 方法体为表达式,该表达式的值作为返回值返回。
(parameters) -> expression
//求和
(int a,int b) -> return a + b;
2. 方法体为代码块,必须用 {} 来包裹起来,且需要一个return返回值,但函数式接口里面方法返回值是 void,无需返回值。
(parameters) -> { statements; }
//求平方
(int a) -> {return a * a;}
//打印,无返回值
(int a) -> {System.out.println("a = " + a);}
3.Lambda表达式的重要特征
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值
4.Lambda表达式的底层实现
java8内部Lambda表达式的实现方式在本质是以匿名内部类的形式的实现的。
package Lambda;
import java.util.function.IntBinaryOperator;
/**
* test
* @author lc
*/
public class Test {
public static void main(String[] s) {
/**
* 定义了一个叫binaryOperator的Lambda表达式,返回值是一个IntBinaryOperator实例。
*/
IntBinaryOperator binaryOperator = (int a, int b) -> {
return a + b;
};
/**
* 利用binaryOperator计算
*/
int result = binaryOperator.applyAsInt(1,2);
System.out.println(result);
}
}
查看IntBinaryOperator这个接口,发现这是一个被@FunctionalInterface注解的接口,@FunctionalInterface标注了这是一个函数式接口,所以(int a, int b) -> {return a + b;}返回的一个IntBinaryOperator的匿名实现类。
@FunctionalInterface
public interface IntBinaryOperator {
/**
* Applies this operator to the given operands.
*
* @param left the first operand
* @param right the second operand
* @return the operator result
*/
int applyAsInt(int left, int right);
}
5. Lambda表达式的函数式接口
函数式接口(Functional Interface)是Java 8对一类特殊类型的接口的称呼。这类接口只定义了唯一的抽象方法的接口(除了隐含的Object对象的公共方法,因此最开始也就做SAM类型的接口(Single Abstract Method)。
定义函数式接口的原因是在Java Lambda的实现中,开发组不想再为Lambda表达式单独定义一种特殊的Structural函数类型,称之为箭头类型(arrow type,依然想采用Java既有的类型(class, interface, method等)。
增加一个结构化的函数类型会增加函数类型的复杂性,破坏既有的Java类型,并对成千上万的Java类库造成严重的影响。权衡利弊,因此最终还是利用SAM 接口作为 Lambda表达式的目标类型。
对于函数式接口来说@FunctionalInterface并不是必须的,只要接口中只定义了唯一的抽象方法的接口那它就是一个实质上的函数式接口,就可以用来实现Lambda表达式。
Java8中已经定义了很多常用的函数式接口,它们都放在java.util.function包下面,一般有以下常用的四大核心接口:
三.Lambda具体应用场景
1.使用() -> {} 替代匿名类
package Lambda;
/**
* @author lc
*/
public class ThreadDemo {
public static void main(String[] s) {
/**
* 不使用Lambda
*/
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("no use lambda");
}
});
/**
* 使用Lambda,代码要简洁一些
*/
Thread t2 = new Thread(() -> System.out.println("use lambda"));
t1.run();
t2.run();
}
}
2.以流水线的方式处理数据
/**
* Stream
* @author lc
*/
public class StreamDemo {
public static void main(String[] s) {
List<Integer> integers = Arrays.asList(4, 5, 6,1, 2, 3,7, 8,8,9,10);
//过滤出偶数列表 [4,6,8,8,10]
List<Integer> evens = integers.stream().filter(i -> i % 2 == 0).collect(Collectors.toList());
//转成平方列表
List<Integer> squareList = integers.stream().map(i -> i * i).collect(Collectors.toList());
}
}
3.数据并行处理
数据并行处理,只需要在原来的基础上加一个parallel()就可以开启,这里parallel()开启的底层并行框架是fork/join,默认的并行数是Ncpu个。
//转成平方列表,并行处理
List<Integer> squareList1 = integers.stream().parallel().map(i -> i * i).collect(Collectors.toList());
4. 用内部迭代取代外部迭代
package Lambda;
import java.util.Arrays;
import java.util.List;
/**
* forEach
* @author lc
*/
public class FechEachDemo {
public static void main(String[] s) {
//外部迭代
List<String> features1 = Arrays.asList("Lambdas", "Default Method", "StreamAPI");
for (String feature : features1) {
System.out.println(feature);
}
//内部迭代
List<String> features2 = Arrays.asList("Lambdas", "Default Method", "StreamAPI");
features2.stream().forEach(n -> System.out.println(n));
}
}
写在最后:柠檬为大家准备了一些适合于1-5年以上开发经验的java程序员面试涉及到的绝大部分面试题及答案做成了文档和学习笔记文件以及架构视频资料免费分享给大家(包括Dubbo、Redis、Netty、zookeeper、Spring cloud、分布式、高并发等架构技术资料),希望可以帮助到大家。
获取方式:请大家关注并私信小编关键词:“资料”即可获取你需要的各类资料。
相关推荐
f = lambda x, y, z: x + y + z # returns a function that can optionally be assigned a name. def func: