Lambda表达式
1. 函数式编程思想和面向对象的思想
在数学中,函数就是有输入量,输出量的一套计算方案,也就是“拿什么东西做什么事情”。相对而言,面向对象过分强调“必须通过对象的形式来做事情”,而函数式思想尽量忽略面向对象的复杂语法——强调做什么,而不是以什么形式做。
-
面向对象的思想:
做一件事情,找一个能解决这个事情的对象,调用对象的方法,完成事情
-
函数式编程思想:
只要能获取到结果,谁去做的,怎么做的都不重要,重视的是结果,不重视过程
2. 冗余的Runnable代码
2.1 传统的写法
当需要启动一个线程去完成任务时,通常会通过java.lang.Runnable
接口来定义任务内容,并使用java.lang.Thread
类来启动该线程。代码如下
-
先写一个Runnable的实现类
//1. 创建一个Runnable接口的的实现类 public class Runnable_Impl implements Runnable{ @Override //2. 重写run方法,设置线程任务 public void run() { System.out.println(Thread.currentThread().getName()); } }
-
再写一个main
public class transferRun { public static void main(String[] args) { //3. 新建一个Runnable接口的实现类对象run Runnable_Impl run = new Runnable_Impl(); //4. 创建Thread类对象,构造方法中传递Runnable接口的实现类对象run Thread t = new Thread(run); //5. 调用Thread的start方法,启动线程 t.start(); } }
2.2 匿名内部类的写法
public class transferRun {
public static void main(String[] args) {
//匿名内部类写法1:
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
new Thread(r).start();
//匿名内部类写法2:
/*相当于把1写法中的r用代码块new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}; 打包传进new Thread(传进这里).start();*/
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
}
}
2.3 总结
- Thread类需要Runnable接口作为参数,其中重写run方法是核心,这时候,不得不创建一个Runnable接口的实现类
Runnable_Impl
- 就算用了匿名内部类的方法,省去了写一个Runnable实现类的功夫,但是还是要重写run方法,还是要把run方法名称、方法参数、方法返回值写一遍
- 其实全部代码,只有
System.out.println(Thread.currentThread().getName());
这句不是废话。因为我们到头来都是为了这句代码能运行起来。
3. 编程思想转变
3.1 关注做什么,而不是怎么做
像上面代码中,我们真正向做的是把run方法内部的代码运行起来,但是受限于面向对象的规则约束,不得不再创建一个Runnable的实现类等等,我们实际上不想做这些功夫。而Lambda表达式的出现帮助我们解决了代码冗余的问题。
3.2 体验Lambda的更优写法(简化匿名内部类中对接口的书写)
2014年3月Oracle发布的java8 (JDK 1.8)中,加入了Lambda表达式这一重量级新特性
public class tryLambda {
public static void main(String[] args) {
//对照匿名内部类的写法
new Thread(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
//Lambda表达式的写法,显然Lambda写法更简单
new Thread(()-> {
System.out.println(Thread.currentThread().getName());
}).start();
}
}
4. Lambda语法
Lambda表达式的标准格式:
() -> {System.out.println("powerful Lambda");}
-
由三部分组成:
- 一些参数
- 一个箭头
- 一段代码
-
格式:
(参数列表)-> {一些重写方法的代码};
-
解释说明格式:
- () : 接口中抽象方法的参数列表,没有参数,就空着;有参数就写出参数,多个参数使用逗号分隔
- -> : 传递的意思,把参数传递给方法
- {} : 重写接口的抽象方法的方法体
5. 练习:使用Lambda标准格式(无参数无返回值)
题目
给定一个厨子Cook
接口,内含唯一的抽象方法makeFood
,且无参数、无返回值。如下:
public interface Cook{
void makeFood();
}
在下面的代码中,请使用Lambda的标准格式调用invokeCook
方法,打印输出“吃饭啦!“字样
public class Demo01InvokeCook{
public static void main(String[] args){
//请在此使用Lambda【标准格式】调用InvokeCook方法
}
public static void invoeCook(Cook cook){
cook.maeFood();
}
}
答案
public class Demo01InvokeCook{
public static void main(String[] args){
//首先,我们看看用匿名内部类的写法是怎样的
//调用invokeCook方法,参数是Cook接口,传递Cook接口的匿名内部类对象
invokeCook(new Cook() {
@Override
public void makeFood() {
System.out.println("吃饭了");
}
});
//用Lambda表达式的写法,简化匿名内部类的书写
//依然是先写invokeCook();,然后在这括号里面重写方法
//由于Cook接口里面的makeFood () 方法是没有参数的,所以直接写(),然后写箭头->
//最后写方法体{System.out.println("吃饭了");}
invokeCook(()->{System.out.println("吃饭了");});
}
public static void invokeCook(Cook cook){
cook.makeFood();
}
}
6. Lambda有参数有返回值的写法
需求:
使用数组存储多个person对象
数组中的person对象使用Arrays的sort方法通过年龄进行升序排序
Arrays的sort方法有两个参数,一个是数组,一个是Comparator接口
分析:
-
创建一个person类
public class person { //名字 private String name; //年龄 private int age; //无参构造函数 public person() { } //有参构造函数 public person(String name, int age) { this.name = name; this.age = age; } //重写tostring方法 @Override public String toString() { return "person [name=" + name + ", age=" + age + "]"; } //获取名字 public String getName() { return name; } //获取年龄 public int getAge() { return age; } }
-
传统写法:用匿名内部类方式
import java.util.Arrays; import java.util.Comparator; /*Lambda表达式有参数返回值的练习 * 需求: * 使用数组存储多个person对象 * 对数组中的person对象使用Arrays的sort方法通过年龄进行升序排序 */ public class Demo02person { public static void main(String[] args) { //使用数组存储多个person对象 person[] beautys = { new person("柳岩", 21), new person("迪丽热巴", 19), new person("杨幂", 18) }; //对数组中的person对象使用Arrays的sort方法通过年龄进行升序(前边 - 后边)排序 //Comparator是一个接口,我们可以用匿名内部类的方式重写它里面的compare方法 Arrays.sort(beautys, new Comparator<person>() { @Override public int compare(person o1, person o2) { //升序(前边 - 后边) return o1.getAge() - o2.getAge(); } }); //打印结果 for (person person : beautys) { System.out.println(person.toString()); } } } /* person [name=杨幂, age=18] person [name=迪丽热巴, age=19] person [name=柳岩, age=21] */
-
Lambda写法
import java.util.Arrays; import java.util.Comparator; /*Lambda表达式有参数返回值的练习 * 需求: * 使用数组存储多个person对象 * 对数组中的person对象使用Arrays的sort方法通过年龄进行升序排序 */ public class Demo02person { public static void main(String[] args) { //使用数组存储多个person对象 person[] beautys = { new person("柳岩", 21), new person("迪丽热巴", 19), new person("杨幂", 18) }; //由于Comparator是一个接口,不用再new什么了,直接开始写Lambda三要素 //第一个要素 () : 这次compare方法有两个参数person o1和person o2 //第二个要素 -> : 直接写 //第三个要素 {} :写方法体,return o1.getAge() - o2.getAge(); Arrays.sort(beautys, (person o1, person o2) -> {return o1.getAge() - o2.getAge();}); //打印结果 for (person person : beautys) { System.out.println(person.toString()); } } }
7. 练习:使用Lambda(有参数有返回值)
题目
给定一个计算器Calculator
接口,内含抽象方法calc可以将两个int数字相加得到和值
public interface Calculator{
int calc(int a, int b);
}
在下面的代码中,请使用Lambda的标准格式调用invokeCalc
方法,完成120和130相加
public class Demo03InvokeCalc{
public static void main(String[] args){
//请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果
}
public static void invokeCalc(int a, int b, Calculator calculator){
int result = calculator.calc(a, b);
System.out.println("结果是:" + result);
}
}
答案
public class Demo03InvokeCalc{
public static void main(String[] args){
//请在此使用Lambda【标准格式】调用invokeCalc方法来计算120+130的结果
int a = 120, b = 130;
invokeCalc(a, b, (int c, int d) -> {return c + d;});
/*匿名内部类的写法
invokeCalc(a, b, new Calculator() {
@Override
public int calc(int a, int b) {
return a + b;
}
}); */
}
public static void invokeCalc(int a, int b, Calculator calculator){
int result = calculator.calc(a, b);
System.out.println("结果是:" + result);
}
}
8. Lambda的省略写法
可推导可省略
Lambda强调的是“做什么”而不是“怎么做”,所以凡是可以根据上下文推导得知的信息,都可以省略。例如上例还可以使用Lambda的省略写法
刚开始的写法
int a = 120, b = 130;
invokeCalc(a, b, (int c, int d) -> {return c + d;});
省略的写法
int a = 120, b = 130;
invokeCalc(a, b, (c, d) -> c + d);
区别
修饰c和d的类型可以省略,因为在方法在接口中声明时,已经知道c和d的类型
省略了大括号{},因为只有一句代码return c + d;,不用大括号
省略了return,因为方法在接口中声明时,就明确了有int返回,省略了return,就要把分号也省掉
9. Lambda使用前提
-
lambada的语法非常简洁,完全没有面向对象的束缚。但是使用时有几个问题需要特别注意:
-
使用Lambda必须具有接口,且要求接口中仅有一个抽象方法,无论是JDK内置的Runnable、Comparator接口还是自定义接口,只有当接口中的抽象方法存在且唯一时,才可以使用Lambda
-
使用Lambda必须具有上下文判断。
也就是方法的参数或局部变量类型必须为Lambda对应的接口类型,才能使用Lambda作为该接口的实例。
-
备注:有且仅有一个抽象方法的接口,称为“函数式接口”