体验Lambda表达式
函数式编程思想,简称λ,全称是Lambda
函数:有输出量、输出量的一套计算方案,也就是"拿数据做操作"
因为Java是面向对象的语言,所以对于Java来说,强调的是"必须通过对象的形式来做事情"
而函数式思想则尽量忽略面向对象的复杂语法,强调的是"强调做什么,而不是以什么形式去做"
对于我们将要学习的Lambda表达式就是函数式思想的体现
需求:启动一个线程,在控制台输出一句话:多线程程序启动了 提示:采用实现类的方式
方式1如下 1、定义一个类MyRunnable实现Runnable接口,重写run()方法 2、创建MyRunnable类的对象 3、创建Thread类的对象,把MyRunnable的对象作为构造参数传递 4、启动线程
方式2如下 1、匿名内部类的方式改进
方式3如下 1、Lambda表达式的方式改进
体验Lambda表达式的练习
xxxxxxxxxx
package ch23;
public class a_1_1MyRunnable implements Runnable{
public void run() {
System.out.println("多线程程序启动了");
}
}
xxxxxxxxxx
package ch23;
public class a_1_2测试 {
public static void main(String[] args) {
//方式1:采用实现类的方式
//创建MyRunnable类的对象
a_1_1MyRunnable my = new a_1_1MyRunnable();
//创建Thread类的对象,把MyRunnable的对象作为构造参数传递
Thread t = new Thread(my);
//启动线程
t.start();
//'实现类的方式'的弊端是:需要去创建一个类,比如我们的MyRunnable类,还要在里面MyRunnable类实现Runnable接口和重写run方法
//-------------------------------------------------------------------------------------------------------
//方式2:匿名内部类的方式
//优点是:不需要创建类,就测试类就行了
new Thread(new Runnable() {
public void run() {
System.out.println("------------------------");
System.out.println("多线程程序启动了");
}
}).start();//Thread括号里面需要的是Runnable接口的实现类对象,我们写上new Runnable(),再回车,就能生成现在的代码样子
//'匿名内部类'的弊端是:代码结构复杂,都堆积成一堆
//--------------------------------------------------------------------------------------------------------
//方式3:使用Lambda表达式的方式
//优点是:不需要创建类,就测试类就行,且代码精简
new Thread(()->{
System.out.println("------------------------");
System.out.println("多线程程序启动了");
}).start();
}
}
Lambda表达式的标准格式
格式如下:
xxxxxxxxxx
(形式参数)->{代码块}
格式范例:
xxxxxxxxxx
new xxx(()->{
xxxxxxxxxxxxxx
}).xxx();
参数解释:
():里面没有内容,可以看成是方法形式参数为空。如果有多个参数,参数之间用逗号隔开;如果没有参数,留空即可
->:用箭头指向后面要做的事情
{}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容
组成Lambda表达式的三要素:形式参数、箭头、代码块
Lambda表达式_抽象方法无参无返回值
Lambda表达式的使用前提: 1、有一个接口 2、接口中有且仅有一个抽象方法
练习: 1、定义一个接口(Eatable),里面定义了一个抽象方法:void eat() 2、定义一个测试类,在测试类中提供两个方法,一个方法是useEatable(a_3_1Eatable e),另一个方法是主方法且在主方法中调用useEatable方法
我们首先要有一个接口a_3_1Eatable,接口里面有且仅有一个抽象方法eat()。 并在测试类中定义一个useEatable方法,且useEatable方法要使用该接口a_3_1Eatable 其中,如果是'使用实现类的方式来调用useEatable方法',则还需要新建一个'a_3_3EatableImpl'类,'a_3_3EatableImpl‘就是a_3_1Eatable接口的实现类 其中,如果是'使用匿名内部类的方式’或'使用Lambda表达式的方式',就不需要使用到那个新建的a_3_3EatableImpl'类
Lambda表达式_抽象方法无参无返回值的练习
xxxxxxxxxx
package ch23;
public interface a_3_1Eatable {
//定义一个抽象方法:void eat()
void eat();
}
xxxxxxxxxx
package ch23;
public class a_3_2测试 {
public static void main(String[] args) {
//使用实现类的方式来调用下面的useEatable方法
//在主方法中调用useEatable方法。由于下面的useEatable方法的参数是a_3_1Eatable接口,即方法调用的实际上是接口的实现类对象
//所以我们需要新建一个接口的实现类,例如a_3_3EatableImpl,建好了之后再进行下面那行的代码
a_3_1Eatable e = new a_3_3EatableImpl();//通过多态的形式创建对象
useEatable(e);
//--------------------------------------------------------------------------------------------------------
//使用匿名内部类的方式来调用下面的方法
useEatable(new a_3_1Eatable() {//useEatable()里面输入接口参数,即new a_3_1Eatable(),然后回车,就能看到这样的代码样子了
public void eat() {
System.out.println("--------------------");
System.out.println("匿名内部类的方式");
}
});
//优点是不用新建'a_3_3EatableImpl'类,这种方式比上面那种匿名内部类的方式要简洁一些
//-------------------------------------------------------------------------------------------------------
//使用Lambda表达式的方式来调用下面的方法。由于'a_3_1Eatable'接口的方法没有参数,所以下面那行的箭头前面的小括号就不需要写参数
useEatable(()->{
System.out.println("--------------------");
System.out.println("Lambda表达式的方式");
});
//优点是不用新建'a_3_3EatableImpl'类,这种方式是最简洁的
}
//调用'a_3_1Eatable'接口中的方法
private static void useEatable(a_3_1Eatable e){
e.eat();
}
}
xxxxxxxxxx
package ch23;
//该类是a_3_1Eatable接口的实现类对象
public class a_3_3EatableImpl implements a_3_1Eatable{
public void eat() {
System.out.println("我是'a_3_1Eatable接口'的实现类");
}
}
Lambda表达式_抽象方法带参无返回值
注意:其实就是前面的内容,即"a_3_0"~"a_3_3"的内容,为了能有熟悉感,我注释都基本写的一样
练习: 1、定义一个接口(Flyable),里面定义了一个抽象方法:void fly(String s) 2、定义一个测试类,在测试类中提供两个方法,一个方法是useFlyable(a_4_1Flyable f),另一个方法是主方法且在主方法中调用useFlyable方法
其实就是"a_3_0"~"a_3_3"的进阶版,同样也是演示三种方式是如何调用'接口的实现类'的方法,'接口的实现类'这里对应的就是'a_4_3FlyableImpl' 区别就是,这节课的'a_4_1Flyable'接口是带参的抽象方法,当使用'Lambda表达式的方式'的时候,注意在箭头前面的小括号里面写参数就行
我们首先要有一个接口a_4_1Flyable,接口里面有且仅有一个带参抽象方法fly(String s)。 并在测试类中定义一个useFlyable方法,且useFlyable方法要使用该接口a_4_1Flyable 其中,如果是'使用实现类的方式来调用useFlyable方法',则还需要新建一个'a_4_3FlyableImpl'类,'a_4_3FlyableImpl‘就是a_4_1Flyable接口的实现类 其中,如果是'使用匿名内部类的方式’或'使用Lambda表达式的方式',就不需要使用到那个新建的a_4_3FlyableImpl类,注意箭头前面的小括号里面写参数就行
Lambda表达式_抽象方法带参无返回值的练习
package ch23;
public interface a_4_1Flyable {
//定义一个带参的抽象方法
void fly(String s);
}
xxxxxxxxxx
package ch23;
public class a_4_2测试 {
public static void main(String[] args) {
//使用实现类的方式来调用下面的useFlyable方法
//在主方法中调用useFlyable方法。由于下面的useFlyable方法的参数是a_4_1Flyable接口,即方法调用的实际上是接口的实现类对象
//所以我们需要新建一个接口的实现类,例如a_4_3FlyableImpl,建好了之后再进行下面那行的代码
a_4_1Flyable e = new a_4_3FlyableImpl();//通过多态的形式创建对象
useFlyable(e);
//--------------------------------------------------------------------------------------------------------
//使用匿名内部类的方式来调用下面的方法
useFlyable(new a_4_1Flyable() {//useFlyable()里面输入接口参数,即new a_4_1Flyable(),然后回车,就能看到这样的代码样子了
public void fly(String s) {
System.out.println("-----------------------");
System.out.println(s);
System.out.println("匿名内部类的方式");
}
});
//优点是不用新建'a_4_3FlyableImpl'类,这种方式比上面那种匿名内部类的方式要简洁一些
//-------------------------------------------------------------------------------------------------------
//使用Lambda表达式的方式来调用下面的方法。由于'a_4_1Flyable'接口的方法有参数,所以下面那行的箭头前面的小括号需要写参数
useFlyable((String s)->{//这个s任意的,不一定要写s
System.out.println("-----------------------");
System.out.println(s);
System.out.println("Lambda表达式的方式");
});
//优点是不用新建'a_4_3FlyableImpl'类,这种方式是最简洁的
}
private static void useFlyable(a_4_1Flyable f){
//调用接口中的方法
f.fly("带参参数");
}
}
xxxxxxxxxx
package ch23;
public class a_4_3FlyableImpl implements a_4_1Flyable{
public void fly(String s) {
System.out.println("我是'a_4_1Flyable'的实现类");
}
}
Lambda表达式_抽象方法带参带返回值
其实就是前面的内容,即"a_4_0"~"a_4_3"的内容,为了能有熟悉感,我注释都基本写的一样
练习: 1、定义一个接口(Addable),里面定义了一个抽象方法:int add(int x,int y) 2、定义一个测试类,在测试类中提供两个方法,一个方法是useAddable(Addable a),另一个方法是主方法且在主方法中调用useAddable方法 注意:这次的抽象方法add是需要两个int类型的参数,并返回一个int类型的结果
其实就是"a_4_0"~"a_4_3"的进阶版,同样也是演示三种方式是如何调用'接口的实现类'的方法,'接口的实现类'这里对应的就是'a_5_3AddableImpl' 区别就是,这节课的'a_5_1Addable'接口是带参带返回值的抽象方法,当使用'Lambda表达式的方式'的时候,注意在箭头前面的小括号里面写参数,方法体中注意返回值
我们首先要有一个接口a_5_1Addable,接口里面有且仅有一个带参带返回值抽象方法add(int x,int y)。 并在测试类中定义一个useAddable方法,且useAddable方法要使用该接口a_5_1Addable 其中,如果是'使用实现类的方式来调用useAddable方法',则还需要新建一个'a_5_3AddableImpl'类,'a_5_3AddableImpl‘就是a_5_1Addable接口的实现类 其中,如果是'使用匿名内部类的方式’或'使用Lambda表达式的方式',就不需要使用到那个新建的a_5_3AddableImpl类,注意箭头前面的小括号里面写参数,方法体中注意返回值
Lambda表达式_抽象方法带参带返回值的练习
xxxxxxxxxx
package ch23;
public interface a_5_1Addable {
//定义一个带参带返回值的抽象方法
int add(int x,int y);
}
xxxxxxxxxx
package ch23;
public class a_5_2测试 {
public static void main(String[] args) {
//使用实现类的方式来调用下面的useAddable方法
//在主方法中调用useAddable方法。由于下面的useAddable方法的参数是a_5_1Addable接口,即方法调用的实际上是接口的实现类对象
//所以我们需要新建一个接口的实现类,例如a_5_3AddableImpl,建好了之后再进行下面那行的代码
a_5_1Addable e = new a_5_3AddableImpl();//通过多态的形式创建对象
useAddable(e);
//--------------------------------------------------------------------------------------------------------
//使用匿名内部类的方式来调用下面的方法
useAddable(new a_5_1Addable() {//useAddable()里面输入接口参数,即new a_5_1Addable(),然后回车,就能看到这样的代码样子了
public int add(int x, int y) {
System.out.println("----------------------");
return x + y;//上面那行的字母是什么,这里就是什么
}
});
//优点是不用新建'a_4_3FlyableImpl'类,这种方式比上面那种匿名内部类的方式要简洁一些
//-------------------------------------------------------------------------------------------------------
//使用Lambda表达式的方式来调用useAddable方法
useAddable((int x,int y)->{ //注意x和y可以写成其他字母,但是必须是两个int参数就行。因为我们在'a_5_1Addable'接口里面写的是两个int参数
System.out.println("----------------------");
return x + y;//上面那行的字母是什么,这里就是什么
});
}
private static void useAddable(a_5_1Addable a){
int sum = a.add(10, 20);
System.out.println(sum);
}
}
xxxxxxxxxx
package ch23;
public class a_5_3AddableImpl implements a_5_1Addable{
public int add(int x, int y) {
return x + y;
}
}
Lambda表达式的省略模式
省略规则: 1、参数类型可以省略。但是有多个参数的情况下,不能只省略一个 2、如果参数有且仅有一个,那么小括号可以省略 3、如果代码块的语句只有一条,可以省略大括号和分号。且如果还有return,省略大括号和分号之后还一定要省略return
Lambda表达式省略模式的练习
xxxxxxxxxx
package ch23;
public interface a_6_1Addable {
//定义一个带参带返回值的抽象方法
int add(int x,int y);
}
xxxxxxxxxx
package ch23;
public interface a_6_2Flyable {
//定义一个带参的抽象方法
void fly(String s);
}
xxxxxxxxxx
package ch23;
//有多少个参数和参数类型,是看对应接口的,例如a_6_2Flyable接口是一个参数和一个参数类型,例如a_6_1Addable是两个参数和两个参数类型
public class a_6_3测试 {
public static void main(String[] args) {
//调用下面写好的useFlyable和useAddable方法。注意,不调用就不会被执行
//Lambda表达式的标准格式
useAddable((int x, int y) -> {
return x + y;
});
//---------------------------------------------------------------------------------------------------
//Lambda表达式的省略模式:参数的类型可以省略
useAddable((x, y) -> {
return x + y;
});
//注意,当有多个参数的情况下,不能只省略一个参数的类型,例如上面的x和y参数都有参数类型,所以,不能写成(int x, y)或(x, int y)
//----------------------------------------------------------------------------------------------------
//Lambda表达式的标准格式
useFlyable((String s)->{ //注意:这里的s是参数,String是参数类型
System.out.println(s);
});
//Lambda表达式的省略模式:参数的类型可以省略
useFlyable((s)->{
System.out.println(s);
});
//注意,如果参数只有一个,那么参数类型和小括号都可以省略不写,如下
useFlyable(s->{
System.out.println(s);
});
//----------------------------------------------------------------------------------------------------
//如果代码块的语句只有一条,可以省略大括号和分号,如下
useFlyable(s->
System.out.println(s)
);
//整理一下就是,如下
useFlyable(s->System.out.println(s));
//如果代码块的语句只有一条,且代码块出现return,则可以省略大括号和分号且必须还要省略return
//useAddable((x,y)->return x + y); //报错,没有省略return
useAddable((x,y)->x + y);
}
private static void useFlyable(a_6_2Flyable f) {
System.out.println("------------------------");
f.fly("a_6_2Flyable接口的带参抽象方法");
}
private static void useAddable(a_6_1Addable a) {
System.out.println("------------------------");
int sum = a.add(10, 20);
System.out.println(sum);
}
}
Lambda表达式的注意事项
package ch23;
public interface a_7_1Inter {
//无参无返回值的抽象方法
void show();
}
package ch23;
public class a_7_2测试 {
public static void main(String[] args) {
//Lambda表达式的标准格式
useInter(()->{
System.out.println("a_7_1Inter接口的show方法");
});
//Lambda表达式的省略模式
useInter(()->
System.out.println("a_7_1Inter接口的show方法")
);
//当在a_7_1Inter接口中多写一个抽象方法,那么上面的两部分就会报错,所以第一个注意事项如下
//使用Lambda表达式必须要有接口,并且要求接口中有且仅有一个抽象方法
//--------------------------------------------------------------------------------------------------
//匿名内部类的方式使用下面的useInter方法。匿名内部类的上下文环境是Thread方法需要的是接口类型的参数
new Thread(new Runnable() {//new Runnable()就是接口的实现类对象
public void run() {
System.out.println("-----------------------");
System.out.println("匿名内部类");
System.out.println("-----------------------");
}
}).start();
//如何用Lambda表达式来达到上面的效果。思考Lambda表达式的上下文环境是什么
//()-> System.out.println("Lambda表达式");//这种就是典型的缺乏上下文环境,报错。解决如下
Runnable r = ()-> System.out.println("Lambda表达式1");
new Thread(r).start();
//其实上面的Lambda表达式可以合并优化一下,如下
new Thread(()-> System.out.println("Lambda表达式2")).start();
}
private static void useInter(a_7_1Inter i){
System.out.println("-----------------------");
i.show();
}
}
Lambda表达式和匿名内部类的区别
第一个区别:方法形参的所需类型不同 1、匿名内部类:可以是接口类、抽象类、具体类 2、Lambda表达式:只能是接口
第二个区别:使用限制不同 1、如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类 2、如果接口中多于1个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
第三个区别:实现原理不同 1、匿名内部类:运行编译之后,产生一个单独的.class字节码文件 2、Lambda表达式:运行编译之后,不会产生一个单独的.class字节码文件。原因:对应的字节码会在运行的时候动态生成
Lambda表达式和匿名内部类区别的练习
xxxxxxxxxx
package ch23;
public interface a_8_1Inter {
//无参无返回值的抽象方法
void show();
//void show2();
}
xxxxxxxxxx
package ch23;
public abstract class a_8_2Animal {//该类是抽象类
//无参无返回值的抽象方法
public abstract void method();
}
xxxxxxxxxx
package ch23;
public class a_8_3Student {//该类是一个具体类
//非抽象方法
public void study(){
System.out.println("我是a_8_3Student的study方法");
}
}
xxxxxxxxxx
package ch23;
public class a_8_4测试 {
public static void main(String[] args) {
//使用匿名内部类调用下面写好的三个方法
useInter(new a_8_1Inter() {//new a_8_1Inter()是形参
public void show() {
System.out.println("匿名内部类-接口类-单方法show");
System.out.println("----------------------");
}
});
useAnimal(new a_8_2Animal() {//new a_8_2Animal()是形参
public void method() {
System.out.println("匿名内部类-抽象类");
System.out.println("----------------------");
}
});
useStudent(new a_8_3Student(){//new a_8_3Student是形参
public void study() {
System.out.println("匿名内部类-具体类");
System.out.println("----------------------");
}
});
//总结:说明匿名内部类调用方法的时候,方法里的形参可以是抽象类、具体类、接口类
//--------------------------------------------------------------------------------------------------------
//使用Lambda表达式来调用下面写好的三个方法。注意下面是Lambda表达式的省略模式的写法
useInter(()-> System.out.println("Lambda表达式-接口类"));
//useAnimal(()-> System.out.println("Lambda表达式-抽象类"));//报错
//useStudent(()-> System.out.println("Lambda表达式-具体类"));//报错
//总结:说明Lambda表达式来调用方法的时候,方法里的形参必须是接口
//上面演示的是Lambda表达式和匿名内部类的第一个区别:'a_8_1Inter'接口中,如果是单方法时,即只有一个方法时,Lambda表达式的形参可以
//是抽象类、具体类、接口类,Lambda表达式的形参只能是接口类
//---------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------
//下面演示的是Lambda表达式和匿名内部类的第二个区别:'a_8_1Inter'接口中,如果是多方法时,例如有一个show方法和一个show2方法,
//匿名内部类可以正常使用,Lambda表达式不能正常使用会报错
//当我们在接口类里面再写一个方法show2的话,上面的Lambda表达式就会报错,即不能使用Lambda表达式了,只能使用匿名内部类的方式
//当接口里有两个方法时,此时内部类写法如下。为了不跟双虚线上面的代码冲突,我先把'a_8_1Inter'接口里面的show2方法注释了
//如果想要看下面的代码运行,就去把'a_8_1Inter'接口里面的show2方法解开注释,把这里的第81行和50~60行解开注释,并且把这里的第7~36行注释掉
/*useInter(new a_8_1Inter() {
@Override
public void show() {
System.out.println("匿名内部类-接口类-多方法show");
}
@Override
public void show2() {
System.out.println("匿名内部类-接口类-多方法show2");
}
});*/
//---------------------------------------------------------------------------------------------------------
//---------------------------------------------------------------------------------------------------------
//Lambda表达式和匿名内部类的第三个区别:匿名内部类每次运行会在"D:\huanf\out\production\java"路径里面多生成一个字节码文件,
//字节码文件的后缀是.java,而Lambda表达式每次运行后,不会在模块路径下生成字节码文件
//提示:把匿名内部类注释掉,再运行,额外生成的字节码文件就没了
}
//具体类
private static void useStudent(a_8_3Student s){
s.study();
}
//抽象类
private static void useAnimal(a_8_2Animal a){
a.method();
}
//接口类
private static void useInter(a_8_1Inter i){
i.show();
//i.show2();//这里的调用是起绝对作用,如果不调用show2,上面的main方法就不会执行show2
}
}