文章目录
1. Lambda表达式
1.1 函数式编程思想概述
在数学,函数就是有输入量、输出量的一套计算方案,也就是“拿数据总操作”
面向对象思想强调“必须通过对象的形式来做事情”
函数式思想尽量忽略面向对象的复杂语法:“强调作甚恶魔,而不是以什么形式去做”
1.2 体验Lambda表达式
需求:启动一个线程,在控制台输出:多线程程序已启动
//线程类
public class ThreadC implements Runnable {
@Override
public void run() {
System.out.println("多线程程序启动啦");
}
}
//测试类
public class Demo {
public static void main(String[] args) {
//实现类的方式实现需求
ThreadC t = new ThreadC();
Thread m = new Thread(t);
m.start();
}
}
用匿名内部类改进
public class Demo {
public static void main(String[] args) {
//匿名内部类方式改进
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程程序启动啦");
}
}).start();
}
}
Lambda表达式方式的改进
public class Demo {
public static void main(String[] args) {
new Thread(() -> {
System.out.println("多线程程序启动啦");
}).start();
}
}
1.3 Lambda表达式的标准格式
分析:
以下是匿名内部类重写run()方法的代码
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("多线程程序启动啦");
}
}).start();
- 方法形式参数为空,说明调用方法时不需要传递参数
- 方法返回值类型为void,说明方法执行没有结果返回
- 方法体中的内容,是我们具体要做的事情
Lambda表达式的代码
new Thread(() -> {
System.out.println("多线程程序启动啦");
}).start();
- (): 里面没有内容,可以看成是方法形式参数为空
- ->: 用箭头指向后面要做的事情
- { }: 包含一段代码,我们称之为代码块,可以看成是方法体中的内容
组成Lambda表达式的三要素:形式参数,箭头,代码块
1.3 Lambda表达式的标准格式
- 格式:(形式参数) -> {代码块}
- 形式参数:如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
- ->: 有英文中画线和大于符号组成,固定写法,代表指向动作
- 代码块: 是我们具体要做的事情,也就是以前我们写的方法体内容
1.4 Lambda表达式的练习
Lambda表达式的使用前提
- 有一个接口
- 接口中有且仅有一个抽象方法
练习1:
- 定义一个接口(Eatable),里面定义一个抽象方法:void eat()
- 定义一个测试类(EatableDemo),在测试类中提供两个方法
- 一个方法是:useEatable(Eatable e)
- 一个方法是主方法,在主方法中调用useEatable()方法
public interface Eatable {
void eat();
}
//
public class EatableImpl implements Eatable{
@Override
public void eat() {
System.out.println("吃");
}
}
//
public class EatableDemo {
private static void useEatable(Eatable e) {
e.eat();
}
public static void main(String[] args) {
Eatable e = new EatableImpl();
useEatable(e);
//匿名内部类
useEatable(new Eatable() {
@Override
public void eat() {
System.out.println("吃");
}
});
//lambda表达式
useEatable(() -> {
System.out.println("吃");
});
}
}
练习2:
- 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s)
- 定义一个测试类(FlyableDemo),在测试类中提供两个方法
- 一个方法是:useFlyable(Flyable e)
- 一个方法是主方法,在主方法中调用useFlyable()方法
public interface Flyable {
void fly(String s);
}
///
public class FlyableDemo {
public static void main(String[] args) {
//匿名内部类
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println(s+",fei");
}
});
//lambda表达式
useFlyable((String s)->{
System.out.println(s+",,fei");
});
}
private static void useFlyable(Flyable f){
f.fly("飞");
}
}
练习3:
-
- 定义一个接口(Addable),里面定义一个抽象方法:void add(int x,int y)
- 定义一个测试类(AddableDemo),在测试类中提供两个方法
- 一个方法是:useFlyable(Addable e)
- 一个方法是主方法,在主方法中调用useAddable()方法
public class Demo {
public static void main(String[] args) {
useAddable((int x,int y)->{ //可以写
return x+y;
});
}
private static void useAddable(Addable a) {
int sum = a.add(10,20);
System.out.println(sum);
}
}
///
public interface Addable {
int add(int x,int y);
}
1.5 Lambda表达式的省略模式
上面的代码写省略版
public interface Addable {
int add(int x,int y);
}
///
public interface Flyable {
void fly(String s);
}
public class Demo {
public static void main(String[] args) {
useAddable((int x,int y)->{ //可以写
return x+y;
});
//参数的类型可以省略
useAddable((x,y)->{ //可以写
return x+y;
});
//但是有多个参数的情况下,不能只省略一个
// useAddable((int x,y)->{ //可以写
// return x+y;
// });
useFlyable((String s) -> {
System.out.println(s+",飞");
});
//如果参数有且只有一个,那么小括号可以省略
useFlyable(s -> {
System.out.println(s+",飞");
});
//如果代码块的语句只有一条,可以省略大括号和分号
useFlyable(s -> System.out.println(s+",飞"));
//如果代码块的语句只有一条,而且有return,也得把return省略掉
useAddable((x,y)->x+y);
}
private static void useAddable(Addable a) {
int sum = a.add(10,20);
System.out.println(sum);
}
private static void useFlyable(Flyable f){
f.fly("飞");
}
}
1.6 Lambda表达式的注意事项
- 使用Lambda必须要有接口,并且要求接口有且仅有一个抽象方法
- 必须要有上下文环境,才能推导出Lambda对应的接口
- 根据局部变量的赋值得知Lambda对应的接口:Rnnable r = () -> System.out.println(“Lambda表达式”);
- 根据调用方法的参数得知Lambda对应的接口:new Thread(()-> System.out.println(“Lambda表达式”)).start();
1.7 Lambda表达式和匿名内部类的区别
所需类型不同:
- 匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
- Lambda表达式:只能是接口
使用限制不同:
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
实现原理不同:
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件。对应的字节码会在运行的时候动态生成
2.方法引用
2.1 体验方法引用
- 方法引用的出现原因
- 在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作
- 那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有地方存在相同方案,那是否还有必要再写重复逻辑呢?答案肯定是没有必要
- 那我们又是如何使用已经存在的方案的呢?
- 这就是我们要讲解的方法引用,我们是通过方法引用来使用已经存在的方案
public interface Printable {
void printSring(String s);
}
/
public class PrintableDemo {
public static void main(String[] args) {
userPrintable(s-> System.out.println(s));
System.out.println("ai");
//方法引用符: ::
userPrintable(System.out::println);//把参数给了println方法
//可推到的就是可省略的
}
private static void userPrintable(Printable p){
p.printSring("ai");
}
}
2.2 方法引用符
- 方法引用符
:: 该符号为引用运算符,而它所在的表达式被称为方法引用 - 推导与省略
· 如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式,它们都将被自动推导
· 如果使用方法引用,也是同样可以根据上下文进行推导
· 方法引用是Lambda的孪生兄弟
2.3 Lambda表达式支持的方法引用
常见的引用方式:
- 引用类方法
- 引用对象的实例方法
- 引用类的实例方法
- 引用构造器
2.4 引用类方法
其实就是引用类的静态方法
- 格式: 类名::静态方法
- 范例:Integer::parseInt
Integer类的方法:public static int parseInt(String s)将此String转换为int类型数据
//Lambda表达式
useConverter(s->Integer.parseInt(s));
//引用类方法
useConverter(Integer::parseInt);
//Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数(也就是说s的位置有多少的参数,就有多少个参数给了parseInt方法)
2.5 引用对象的实例方法
其实就是引用类中的成员方法
- 格式: 对象::成员方法
- 范例:“HelloWorld”::toUpperCase
注意::后面只写方法名,没有()
//Lambda表达式
useConverter(s->Integer.parseInt(s));
//引用类方法
Integer i;
useConverter(i::parseInt);
//Lambda表达式被对象的实例方法替代的时候,它的形式参数全部传递给静态方法作为参数
2.6 引用类的实例方法
其实就是引用类的成员方法
- 格式: 类名::成员方法
- 范例:String::substring
String类中的方法:public String substring(int beginIndex,int endIndex)
从beginIndex开始到endIndex结束,截取字符串。
//Lambda表达式
useMyString((s,x,y)->s.substring(x,y));
//引用类的实例方法
useMyString(String::substring);
//Lambda表达式被类的实例方法替代的时候,第一个参数作为调用者,后面的参数全部传递给该方法作为参数
2.7 引用构造器
其实就是引用构造方法
- 格式: 类名::new
- 范例:Student::new
//Lambda表达式
useStudentBuilder((name,age)->new Student(name,age));
//引用构造器
useStudentBuilder(Student::new);
//Lambda表达式构造器替代的时候,他的形式参数全部传递给构造作为参数