25_Java中的Lambda表达式
Java中的Lambda表达式
()代表此接口中的唯一方法,"->" 指向一个语句块{},{}语句块中表示这个方法的重写
一、函数式编程思想概述
在数学中,函数就是有输入量,输出量的一套计算方案,也就是“拿数据做操作”。
面向对象思想强调的 “必须通过对象的形式来做事情”
函数式思想则尽量忽略面向对象的复杂语法:“强调做什么,而不是以什么形式去做”
而这里说的Lambda表达式就是函数式思想的体现
二、体验Lambda表达式
需求:启动一个线程,在控制台输出一句话:多线程程序启动了
步骤:
1、
定义一个类MyRunnable实现Runnable接口,重写run()方法
创建MyRunnable类对象
创建Thread的类对象,把MyRunnable的对象作为构造方法的参数传递
启动线程
2、
使用匿名内部类
3、
使用Lambda表达式
参考代码:
package com.itheima_01; //多线程接口实现类 public class MyRunnable implements Runnable{ @Override public void run() { System.out.println("多线程程序启动了"); } }
package com.itheima_01; /* 需求:启动一个线程,在控制台输出一句话:多线程程序启动了 */ public class LambdaDemo { public static void main(String[] args){ //一、通过一个实现Runnable接口的实现类,来完成需求 /*MyRunnable my = new MyRunnable(); Thread t = new Thread(my); //通过实现Runnable的自定义类作为构造参数传递 t.start(); //通过start方法启动多线程*/ //可以看到,对这样一个简单需求,使用实现类未免有些麻烦(若是多个线程对象都需要这个实现类下的run那这样就可以) //使用匿名内存类的方式进行改进 /*new Thread(new Runnable(){ @Override public void run(){ System.out.println("多线程程序启动了"); } }).start();*/ //像这样的一个情况下,接口实现中就只有一个方法需要重写 //使用Lambda表达式的方式改进 ()->{} new Thread(()->{ System.out.println("多线程程序启动了"); }).start(); } }
三、Lambda表达式的标准格式
1、匿名内部类的写法:
new Thread(new Runnable(){ @Override public void run(){ System.out.println("多线程程序启动了"); } }).start();
向匿名对象Thread中的有参构造函数传递了一个匿名内部类
方法的参数为空,无返回值,里面的内容表示我们需要完成的事情
2、Lambda表达式写法(对于其而言,重点关注我们需要做什么,而不是创建对象,这也是函数式编程思想):
可见的是对于匿名内部类而言,Lambda表达式省略了创建子类/实现类的过程,只关注完成哪件事。而对于接口而言接口本身的关注点也是在其中的方法,而并非是成员属性即类。
new Thread(()->{ System.out.println("多线程程序启动了"); }).start();
():表示匿名内部类中重写的函数,参数为空
->:用箭头指向函数的具体实现即{}
{}:包含一段代码块,这里是指的函数的实现体
组成Lambda表达式的三要素:形式参数、箭头、代码块
3、Lambda表达式的格式:
(形式参数)->{代码块}
形式参数:与函数(方法)中的差不多,可以使用函数的写法
->:表示一个指向的动作
代码块:是我们具体要做的事情,也就是函数的实现语句
四、Lambda表达式的练习
Lambda表达式的使用前提:
有一个接口
接口中有且仅有一个抽象方法
练习1:
定义一个接口(Eatable),里面定义一个抽象方法:void eat();
定义一个测试类(EatableDemo),在测试类中提供两个方法
一个方法是:useEatable(Eatable e)
一个方法是主方法,在主方法中调用useEatable方法
参考代码:
package com.itheima_02; //定义一个接口,其中只有一个抽象方法 public interface Eatable { //创建一个接口interface public abstract void eat(); //在接口中只有一个抽象方法 }
package com.itheima_02; //定义一个接口的实现类 public class EatableImpl implements Eatable{ @Override public void eat(){ System.out.println("吃肉"); } }
package com.itheima_02; //主类 public class EatableDemo { public static void main(String[] args){ //调用方法 //1、传入实现类对象 EatableDemo.useEatable(new EatableImpl()); //使用了匿名对象 //2、传入匿名内部类 EatableDemo.useEatable(new Eatable() { @Override public void eat() { System.out.println("吃水果"); } }); //3、传入对应的lambda表达式 EatableDemo.useEatable(()->{ System.out.println("吃菜"); }); } //定义一个方法,使用接口作为参数,需要传入一个对应的实现类,进行多态的实现 public static void useEatable(Eatable eatable){ eatable.eat(); } }
练习2:
定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s);
定义一个测试类(FlyableDemo),在测试类中提供两个方法
一个方法是:useFlyable(Flyable f)
一个方法是主方法,在主方法中调用useFlyable方法
参考代码:
package com.itheima_03; //接口 public interface Flyable { public abstract void fly(String s); }
package com.itheima_03; //实现类 public class FlyableImpl implements Flyable{ @Override public void fly(String s){ System.out.println(s); } }
package com.itheima_03; //主类 public class FlyableDemo { public static void main(String[] args){ //调用方法 //使用实现类做参数进行传递 FlyableDemo.useFlyable(new FlyableImpl()); //使用匿名内部类作为参数进行传递 FlyableDemo.useFlyable(new Flyable(){ @Override public void fly(String s){ System.out.println(s); } }); //使用lambda表达式作为参数进行传递 FlyableDemo.useFlyable((String s)->{ //此处括号中就有参数了 System.out.println(s); }); FlyableDemo.useFlyable((s)->{ //还可以这样写(省略了数据类型) System.out.println(s); }); } public static void useFlyable(Flyable flyable){ flyable.fly("测试输出语句"); } }
练习3:
定义一个接口(Addable),里面定义一个抽象方法:int add(int x, int y);
定义一个测试类(AddableDemo),在测试类中提供两个方法
一个方法是:useAddable(Addable a)
一个方法是主方法,在主方法中调用useAddable方法
参考代码:
package com.itheima_04; public interface Addable { public abstract int add(int x, int y); }
package com.itheima_04; public class AddableDemo { public static void main(String[] args){ //调用方法 //使用匿名内部内作为参数传递 AddableDemo.useAddable(new Addable(){ @Override public int add(int x, int y){ return x + y; } }); //使用lambda表达式进行传递 AddableDemo.useAddable((int x, int y)->{ //可以看到无需添加一个返回类型在()前,只需要在实现体中return return x + y; }); AddableDemo.useAddable((x, y)->{ //可以看到参数类型可以省略 return x + y; }); } public static void useAddable(Addable addable){ int sum = addable.add(10, 30); System.out.println(sum); } }
五、Lambda表达式的省略模式
省略规则:
参数类型可以省略,但是又多个参数的情况下,不能只省略一个
如果参数有且只有一个,那么小括号可以省略
如果代码块语句有且只有一条,可以省略 大阔号 和 分号 ,同时含return时需要省略return
参考代码:
package com.itheima_05; public interface Addable { public abstract int add(int x, int y); }
package com.itheima_05; public interface Flyable { public abstract void fly(String s); }
package com.itheima_05; public class LambdaDemo { public static void main(String[] args) { //常规写法: /*useAddable((int x, int y)->{ return x + y; });*/ //1、参数类型可以省略 useAddable((x, y)->{ //且要省略,就所有参数都要省略数据类型 return x + y; }); //常规写法 /*useFlyable((String s)->{ System.out.println(s); });*/ /*useFlyable((s)->{ System.out.println(s); });*/ //2、如果参数“有且只有一个时”,小括号可以省略 /*useFlyable(s->{ System.out.println(s); });*/ //3、如果代码块的语句只有一条,可以省略大括号和分号 useFlyable(s-> System.out.println(s)); //只有一条语句 且 这一条语句中含 return 还能吧return省略了 useAddable((x, y) -> (x + y)); } public static void useFlyable(Flyable flyable){ flyable.fly("测试输出语句"); } public static void useAddable(Addable addable){ int sum = addable.add(10, 20); System.out.println(sum); } }
六、Lambda表达式的注意事项
注意事项:
使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
必须要有上下文环境,才能推导出Lambda对应的接口
例如:将其传递给一个对应接口对象,或者作为参数向接口形参传递(可以参照一下多态)
参考代码:
package com.itheima_06; public interface Inter { public abstract void show(); //添加一个抽象方法,发现Lambda表达式便不能使用了 // public abstract void method(); }
package com.itheima_06; /* lambda表达式的注意事项 */ public class LambdaDemo { public static void main(String[] args){ //方法定义只有一条语句,进行简写 //使用Lambda必须要使用接口,而且接口中有且只有一个抽象方法 useInter(()-> System.out.println("好好学习,天天向上!")); //必须有上下文环境,此能够推导出Lambda对应的接口 /*new Thread(new Runnable(){ @Override public void run(){ System.out.println("匿名内部类"); } }).start(); //对象调用start方法开始启动多线程*/ //只有单个Lambda语句时无法推导出其含义(这样的接口很多,天知道你要干嘛) // ()-> System.out.println("Lambda表达式"); //指明赋值给对应接口时(或者作为参数传递等),可以推断出 //同时可以看出其本质也是一个实现类对象 Runnable r = ()-> System.out.println("Lambda表达式"); new Thread(r).start(); } public static void useInter(Inter i){ i.show(); } }
七、Lambda表达式和匿名内部类的区别
所需类型不同:
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
Lambda表达式:只能是接口
使用限制不同:
Lambda表达式只能针对只有一个抽象方法的接口
匿名内部类可以针对有多个抽象方法的接口
实现原理不同:
匿名内部类编译后会生成class字节码文件
Lambda表达式编译后不会生成一个class字节码文件,对应的字节码文件会在运行的时候动态生成
参考代码:
package com.itheima_07; /* 具体类 */ public class Student { public void study(){ System.out.println("爱生活,爱健康"); } }
package com.itheima_07; /* 抽象类 */ public abstract class Animal { public abstract void method(); }
package com.itheima_07; /* 接口 */ public interface Inter { public abstract void show(); //运行Lambda时注释 public abstract void method(); }
package com.itheima_07; /* 测试类: 通过测试类、抽象类、接口来体现 */ public class LambdaDemo { public static void main(String[] args){ //一、使用匿名内部类 /*//1、此处匿名内部类,相当于这个具体类的子类,并重写了类中的一个方法 useStudent(new Student(){ @Override public void study(){ System.out.println("具体类"); } }); //2、此处匿名内部类,相当于这个抽象类的子类,并重写了抽象类中的一个方法 useAnimal(new Animal() { @Override public void method() { System.out.println("抽象类"); } }); //3、此处匿名内部类,相当于这个接口的实现类,并重写了接口中的一个方法 useInter(new Inter() { @Override public void show() { System.out.println("接口"); } });*/ //接口中有两个抽象方法时 useInter(new Inter() { @Override public void show() { System.out.println("方法一重写"); } @Override public void method() { System.out.println("方法二重写"); } }); //二、使用Lambda表达式 //当接口中在添加一个方法后,无法通过编译 // useInter(() -> System.out.println("接口")); // useAnimal(() -> System.out.println("抽象类")); //无法通过编译 // useStudent(() -> System.out.println("具体类")); //无法通过编译 //三、匿名内部类编译后会生成一个字节码文件,而Lambda表达式不会 //LambdaDemo$1.class (通过模块目录下的out文件运行后比对查看) } private static void useStudent(Student s){ s.study(); } private static void useAnimal(Animal a){ a.method(); } private static void useInter(Inter i){ //此处值调用了show()方法 i.show(); } }
本文作者:编程初学者求大佬指点
本文链接:https://www.cnblogs.com/fragmentary/p/16988210.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。