1.什么是Lambda表达式2.为什么使用Lambda表达式3.Lambda表达式语法4.函数式接口
4.1 什么是函数式接口4.2 自定义函数式接口4.3 Java内置函数式接口 5.方法引用6.构造器引用7.数组引用8.Lambda表达式的作用域
8.1 访问局部变量8.2 访问局部引用,静态变量,实例变量8.3 Lambda表达式访问局部变量作限制的原因 9.Lambda表达式的优缺点 1.什么是Lambda表达式
Lambda表达式,也可称为闭包。类似于Javascript中的闭包,它是推动Java8发布的最重要的新特性。
2.为什么使用Lambda表达式我们可以把Lambda表达式理解为一段可以传递的代码(将代码像数据一样进行传递)。Lambda允许把函数作为一个方法的参数,使用Lambda表达式可以写出更简洁、更灵活的代码,而其作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升。
一个简单示例:
分别使用成员内部类、局部内部类、静态内部类、匿名内部类方式实现Runnable的run()方法并创建和启动线程,如下所示:
public class LambdaDemo {class MyThread01 implements Runnable{ @Override public void run() { System.out.println("成员内部类:用Lambda语法创建线程吧!"); }}static class MyThread02 implements Runnable{ @Override public void run() { System.out.println("静态内部类:对啊,用Lambda语法创建线程吧!"); }}public static void main(String[] args) { class MyThread03 implements Runnable{ @Override public void run() { System.out.println("局部内部类:用Lambda语法创建线程吧!"); } } Runnable runnable = new Runnable(){ @Override public void run() { System.out.println("匿名内部类:求求你,用Lambda语法创建线程吧!"); } }; //成员内部类方式 LambdaDemo lambdaDemo = new LambdaDemo(); MyThread01 myThread01 =lambdaDemo.new MyThread01(); new Thread(myThread01).start(); //静态内部类方式 MyThread02 myThread02 = new MyThread02(); new Thread(myThread02).start(); //局部内部类 MyThread03 myThread03 = new MyThread03(); new Thread(myThread03).start(); //匿名内部类的方式 new Thread(runnable).start();}}
可以看到上面创建方式,代码量都不少,使用Lambda表达式实现,如下所示:
//Lambda方式new Thread(() -> System.out.println("使用Lambda就对了")).start();12
可以看到代码明显简洁了许多。
3.Lambda表达式语法Lambda表达式在Java语言中引入了一个操作符**“->”**,该操作符被称为Lambda操作符或箭头操作符。它将Lambda分为两个部分:
左侧:指定了Lambda表达式需要的所有参数
右侧:制定了Lambda体,即Lambda表达式要执行的功能。
像这样:
(parameters) -> expression或(parameters) ->{ statements; }
以下是lambda表达式的重要特征:
可选类型声明:不需要声明参数类型,编译器可以统一识别参数值。
可选的参数圆括号:一个参数无需定义圆括号,但多个参数需要定义圆括号。
可选的大括号:如果主体包含了一个语句,就不需要使用大括号。
可选的返回关键字:如果主体只有一个表达式返回值则编译器会自动返回值,大括号需要指定明表达式返回了一个数值。
下面对每个语法格式的特征进行举例说明:
(1)语法格式一:无参,无返回值,Lambda体只需一条语句。如下:
@Test public void test01(){ Runnable runnable=()-> System.out.println("Runnable 运行"); runnable.run();//结果:Runnable 运行 }
(2)语法格式二:Lambda需要一个参数,无返回值。如下:
@Test public void test02(){ Consumer
(3)语法格式三:Lambda只需要一个参数时,参数的小括号可以省略,如下:
public void test02(){ Consumer
(4)语法格式四:Lambda需要两个参数,并且Lambda体中有多条语句。
@Test public void test04(){ Comparator
(5)语法格式五:有两个以上参数,有返回值,若Lambda体中只有一条语句,return和大括号都可以省略不写
@Test public void test05(){ Comparator
(6)Lambda表达式的参数列表的数据类型可以省略不写,因为JVM可以通过上下文推断出数据类型,即“类型推断”
@Test public void test06(){ Comparator
类型推断:在执行javac编译程序时,JVM根据程序的上下文推断出了参数的类型。Lambda表达式依赖于上下文环境。
语法背诵口诀:左右遇一括号省,左侧推断类型省,能省则省。
4.函数式接口 4.1 什么是函数式接口 ==只包含一个抽象方法的接口,就称为函数式接口。==我们可以通过Lambda表达式来创建该接口的实现对象。
我们可以在任意函数式接口上使用@FunctionalInterface注解,这样做可以用于检测它是否是一个函数式接口,同时javadoc也会包含一条声明,说明这个接口是一个函数式接口。
按照函数式接口的定义,自定义一个函数式接口,如下:
@FunctionalInterfacepublic interface MyFuncInterf
定义一个方法将函数式接口作为方法参数。
public String toLowerString(MyFuncInterf
将Lambda表达式实现的接口作为参数传递。
@Test public void test07() { String value = toLowerString((str) -> { return str.toLowerCase(); }, "ABC"); System.out.println(value);//结果ABC }
4.3 Java内置函数式接口四大核心函数式接口的介绍,如图所示:
使用示例:
1.Consumer:消费型接口 void accept(T t)
public void makeMoney(Integer money, Consumer
2.Supplier:供给型接口 T get()
@Test public void test02() { List list = addNumInList(10, () -> (int) (Math.random() * 100)); list.forEach(t -> System.out.println(t)); } public List addNumInList(int size, Supplier
这时候小白肯定就要问了为什么(int) (Math.random() * 100)加return不行,因为他生产的东西不是我们所需要的List最最最主要的原因是因为去掉大括号的同时要去掉return
3.Function
public String handleStr(String s, Function
4.Predicate:断言型接口 boolean test(T t)
@Test public void test04() { List
我们来解析一下这个predicate.test()即把这个括号里的参数传入然后进行判断
我们来扩展一下在一个函数当中我们可以设置多个Predicate predicate
public static boolean checkString(String string, Predicate
当要传递给Lambda体的操作,已经有实现的方法了,就可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用的参数列表一致,方法的返回值也必须一致,即方法的签名一致)。方法引用可以理解为方法引用是Lambda表达式的另外一种表现形式。
方法引用的语法:使用操作符“::”将对象或类和方法名分隔开。
方法引用的使用情况共分为以下三种:
对象::实例方法名
类::静态方法名
类::实例方法名
使用示例:
1.对象::实例方法名
//对象::实例方法名 @Test public void test1() { PrintStream out = System.out; Consumer
2.类::静态方法名
@Test public void test2(){ Comparator
3.类::实例方法名
@Test public void test3() { BiPredicate
4.综合练习
class Dog { private String name; public Dog(String name) { this.name = name; } public static void bark(Dog dog) { System.out.println(dog + "叫了"); } public void ect(Dog dog) { System.out.println(dog + "吃狗粮"); } @Override public String toString() { return this.name; }}public class Demo { public static void main(String[] args) { //消费者函数 Consumer
最后我们打印的是原因吃狗粮
于是我们又添加的一个
Dog dog = new Dog("小");Consumer
证明dog::ect这个只是得到了一个方法
6.构造器引用 格式:类名::new
与函数式接口相结合,自动与函数式接口中方法兼容,可以把构造器引用赋值给定义的方法。需要注意构造器参数列表要与接口中抽象方法的参数列表一致。使用示例:
创建一个实体类Employee:
public class Employee { private Integer id; private String name; private Integer age; @Override public String toString() { return "Employee{" + "id=" + id + ", name='" + name + ''' + ", age=" + age + '}'; } public Employee(){ } public Employee(Integer id) { this.id = id; } public Employee(Integer id, Integer age) { this.id = id; this.age = age; } public Employee(int id, String name, int age) { this.id = id; this.name = name; this.age = age; }}
使用构造器引用与函数式接口相结合
@Test public void test01(){ //引用无参构造器 Supplier
输出结果:
Employee{id=null, name='null', age=null} Employee{id=21, name='null', age=null} Employee{id=8, name='null', age=24}
7.数组引用 数组引用的格式:type[]:new
使用示例:
@Testpublic void test02(){ Function
Lambda表达式可以看作是匿名内部类实例化的对象,Lambda表达式对变量的访问限制和匿名内部类一样,因此Lambda表达式可以访问局部变量、局部引用,静态变量,实例变量。
8.1 访问局部变量在Lambda表达式中规定只能引用标记了final的外层局部变量。我们不能在lambda 内部修改定义在域外的局部变量,否则会编译错误。
public class TestFinalVariable {interface VarTestInterface{ Integer change(String str);}public static void main(String[] args) { //局部变量不使用final修饰 Integer tempInt = 1; VarTestInterface var = (str -> Integer.valueOf(str+tempInt)); //再次修改,不符合隐式final定义 tempInt = 2; Integer str =var.change("111") ; System.out.println(str);}}
上面代码会出现编译错误,出现如下提示:
表达式变量是最终变量
特殊情况下,局部变量也可以不用声明为 final,但是必须不可被后面的代码修改(即隐性的具有 final 的语义)
例如上面的代码确保Lambda表达式后局部变量后面不做修改,就可以成功啦!
public class TestFinalVariable {interface VarTestInterface{ Integer change(String str);}public static void main(String[] args) { //局部变量不使用final修饰 Integer tempInt = 1; VarTestInterface var = (str -> Integer.valueOf(str+tempInt)); Integer str =var.change("111") ; System.out.println(str);}}
8.2 访问局部引用,静态变量,实例变量Lambda表达式不限制访问局部引用变量,静态变量,实例变量。代码测试都可正常执行,代码:
public class LambdaScopeTest {private static String staticVar;private static String instanceVar;@FunctionalInterfaceinterface VarChangeInterface{ Integer change(String str);}private void testReferenceVar(){ ArrayList
Lambda表达式里不允许声明一个与局部变量同名的参数或者局部变量。
//编程报错 Integer tempInt = 1; VarTestInterface varTest01 = (tempInt -> Integer.valueOf(tempInt)); VarTestInterface varTest02 = (str -> { Integer tempInt = 1; Integer.valueOf(str); });
8.3 Lambda表达式访问局部变量作限制的原因Lambda表达式不能访问非final修饰的局部变量的原因是,局部变量是保存在栈帧中的。而在Java的线程模型中,栈帧中的局部变量是线程私有的,如果允许Lambda表达式访问到栈帧中的变量地址(可改变的局部变量),则会可能导致线程私有的数据被并发访问,造成线程不安全问题。
基于上述,对于引用类型的局部变量,因为Java是值传递,又因为引用类型的指向内容是保存在堆中,是线程共享的,因此Lambda表达式中可以修改引用类型的局部变量的内容,而不能修改该变量的引用。
对于基本数据类型的变量,在 Lambda表达式中只是获取到该变量的副本,且局部变量是线程私有的,因此无法知道其他线程对该变量的修改,如果该变量不做final修饰,会造成数据不同步的问题。
但是实例变量,静态变量不作限制,因为实例变量,静态变量是保存在堆中(Java8之后),而堆是线程共享的。在Lambda表]达式内部是可以知道实例变量,静态变量的变化。
9.Lambda表达式的优缺点 优点:
使代码更简洁,紧凑
可以使用并行流来并行处理,充分利用多核CPU的优势
有利于JIT编译器对代码进行优化
缺点:
非并行计算情况下,其计算速度没有比传统的 for 循环快
不容易调试
若其他程序员没有学过 Lambda 表达式,代码不容易看懂
Integer tempInt = 1;
Integer.valueOf(str);
});
#### 8.3 Lambda表达式访问局部变量作限制的原因Lambda表达式不能访问非final修饰的局部变量的原因是,局部变量是保存在栈帧中的。而在Java的线程模型中,栈帧中的局部变量是线程私有的,如果允许Lambda表达式访问到栈帧中的变量地址(可改变的局部变量),则会可能导致线程私有的数据被并发访问,造成线程不安全问题。基于上述,对于引用类型的局部变量,因为Java是值传递,又因为引用类型的指向内容是保存在堆中,是线程共享的,因此Lambda表达式中可以修改引用类型的局部变量的内容,而不能修改该变量的引用。对于基本数据类型的变量,在 Lambda表达式中只是获取到该变量的副本,且局部变量是线程私有的,因此无法知道其他线程对该变量的修改,如果该变量不做final修饰,会造成数据不同步的问题。但是实例变量,静态变量不作限制,因为实例变量,静态变量是保存在堆中(Java8之后),而堆是线程共享的。在Lambda表]达式内部是可以知道实例变量,静态变量的变化。## 9.Lambda表达式的优缺点优点:使代码更简洁,紧凑可以使用并行流来并行处理,充分利用多核CPU的优势有利于JIT编译器对代码进行优化缺点:非并行计算情况下,其计算速度没有比传统的 for 循环快不容易调试若其他程序员没有学过 Lambda 表达式,代码不容易看懂