-Lambda表达式&方法引用
今日学习目标
- 能够知道Lambda表达式的标准格式
- 能够使用Lambda表达式实现无参无返回值的操作
- 能够使用Lambda表达式实现带参无返回值的操作
- 能够使用Lambda表达式实现带参带返回值的操作
- 能够知道Lambda表达式和匿名内部类的区别
- 能够知道引用类方法的使用
- 能够知道引用对象的实例方法的使用
- 能够知道引用类的实例方法的使用
- 能够知道引用构造器的使用
A.Lambda表达式
-
函数式编程思想概述
- f(x) = y在数学中的应用就是我传递一个x参数进去,经过计算返回一个y数据,也就是拿数据去操作.
- 在Java中我们使用的是面向对象思想,强调的东西是必须通过对象的形式来做事情,想要做什么操作之前先要创建对象再去根据对象的相关属性和方法进行操作.
- 函数式思想尽量忽略面向对象的复杂语法,强调做什么,而不是以什么形式去做,这也是Lambda表达式的函数式体现.
-
体验Lamda表达式
-
需求
- 启动一个线程,在控制台输出一句话:多线程程序启动了.
-
代码演示-01-重写run方法
-
package com.itheima.lambda.first; /* 需求:启动一个线程,在控制台输出一句话:多线程程序启动了. 方法01-自定义类实现Runnable接口重写run方法,然后创建Thread类,将自定义类对象传入. */ public class LambdaDemo01 { public static void main(String[] args) { /* 方法01-自定义类实现Runnable接口重写run方法,然后创建Thread类,将自定义类对象传入. */ MyRunnable myRunnable = new MyRunnable(); Thread thread = new Thread(myRunnable); thread.start(); } }
-
-
代码演示-02-使用匿名内部类的方式
-
package com.itheima.lambda.first; /* 需求:启动一个线程,在控制台输出一句话:多线程程序启动了. 方法02-使用匿名内部类的方式直接传入一个Runnable匿名对象并且实现run方法. */ public class LambdaDemo02 { public static void main(String[] args) { //初始化Thread类,使用匿名内部类的方式直接传入一个Runnable匿名对象并且实现run方法. new Thread(new Runnable() { @Override public void run() { System.out.println("多线程程序启动了."); } }).start(); } }
-
-
代码演示-03-Lambda表达式方式
-
package com.itheima.lambda.first; /* 需求:启动一个线程,在控制台输出一句话:多线程程序启动了. 方法03-使用Lambda表达式的方式直接传入一个Runnable匿名对象并且实现run方法. */ public class LambdaDemo03 { public static void main(String[] args) { //初始化Thread类,使用Lambda表达式的方式直接传入一个Runnable匿名对象并且实现run方法. new Thread(() -> System.out.println("多线程程序启动了.")).start(); } }
-
-
-
Lambda表达式的标准形式
-
Runnable匿名内部类的重写run方法的代码分析
-
代码演示
-
public class LambdaDemo02 { public static void main(String[] args) { //初始化Thread类,使用匿名内部类的方式直接传入一个Runnable匿名对象并且实现run方法. new Thread(new Runnable() { @Override public void run() { System.out.println("多线程程序启动了."); } }).start(); } }
-
-
分析
- run方法的形式参数为空,说明调用方法的时候不需要传入参数.
- run方法的返回值是void,说明方法执行之后没有结果返回.
- run方法的方法体中的内容是这个run方法具体做了什么事情.
-
-
Lambda表达式的代码分析
-
代码演示
-
public class LambdaDemo03 { public static void main(String[] args) { //初始化Thread类,使用Lambda表达式的方式直接传入一个Runnable匿名对象并且实现run方法. new Thread(() -> System.out.println("多线程程序启动了.")).start(); } }
-
-
分析 () -> System.out.println(“多线程程序启动了.”)
- ()里面没有内容,可以看成方法的形式参数为空.
- ->用箭头标示之后要做的操作是什么.
- {}里面包含了代码,我们称之为代码块,可以看做是run方法的方法体里面的内容.
-
-
-
Lambda的组成要素
- 形式参数
- 箭头
- 代码块
-
Lambda表达式的格式
-
(形式参数) -> {代码块}
-
如果有多个参数,参数之间用逗号,隔开,如果没有参数,则直接写()即可.
-
-> 有-和大于符号>组成,-符号是字母p右上角的键.
-
代码块就是我们具体要做的事情,相当于之前方法体内容.
-
-
Lambda表达式使用的前提
- 有一个接口,而且接口中有且仅有一个抽象方法才可以使用Lambda表达式.
-
Lambda的表达式练习1 -> 抽象方法无参无返回值.
-
需求
- 定义一个接口(Eatable),里面定义一个抽象方法:void eat().
- 定义一个测试类(EatableDemo),在测试类中提供两个方法,一个方法是:useEatable(Eatable e),一个方法是主方法,在主方法中调用useEatable方法.
-
代码演示
-
接口类
-
package com.itheima.lambda.test01; //定义接口Eatable,接口中有且仅有一个抽象方法. public interface Eatable { /* 定义抽象方法eat. */ void eat(); }
-
-
接口实现类
-
package com.itheima.lambda.test01; public class EatableImpl implements Eatable{ @Override public void eat() { System.out.println("实现了Eatable接口的eat方法."); } }
-
-
测试类
-
package com.itheima.lambda.test01; public class EatableTest { /* 定义主方法main,在主方法中调用useEatable方法. */ public static void main(String[] args) { //创建Eatable的接口实现类EatableImpl. EatableImpl eatable = new EatableImpl(); //调用useEatable方法,将实现类对象作为参数传递. useEatable(eatable); //使用匿名内部类的方式实现Eatable接口的eat方法并且作为参数传递. useEatable(new Eatable() { @Override public void eat() { System.out.println("匿名内部类实现了Eatable接口的eat方法."); } }); //使用Lambda表达式的方式实现Eatable接口的eat方法并且作为参数传递. useEatable(() -> { System.out.println("实现Lambda表达式的方式实现了Eatable接口的eat方法并且作为参数传递"); }); } /* 定义方法useEatable(Eatable e),需要传递一个Eatable接口的实现类参数,这里使用的多态. */ public static void useEatable(Eatable eatable){ eatable.eat(); } }
-
-
-
-
Lambda的表达式练习2 -> 抽象方法有参无返回值.
-
需求
- 定义一个接口(Flyable),里面定义一个抽象方法:void fly(String s).
- 定义一个测试类(FlyableDemo),在测试类中提供两个方法,一个方法是:useFlyable(Flyable f),一个方法是主方法,在主方法中调用useFlyable方法
-
代码演示
-
接口类
-
package com.itheima.lambda.test02; //Flyable,接口中有且仅有一个抽象方法. public interface Flyable { /* 定义抽象方法fly(String s) */ void fly(String s); }
-
-
接口实现类
-
package com.itheima.lambda.test02; public class FlyableImpl implements Flyable{ @Override public void fly(String s) { System.out.println("飞行的方式是:"+s); } }
-
-
测试类
-
package com.itheima.lambda.test02; public class FlyableTest { /* 定义主方法main,在主方法中调用useFlyable(Flyable flyable)方法. */ public static void main(String[] args) { //创建Flyable的实现类对象FlyableImpl. FlyableImpl flyable = new FlyableImpl(); //调用useFlyable方法,并且将实现类对象作为参数传递. useFlyable(flyable); //使用匿名内部类的方式实现Flyable接口的fly方法并且作为参数传递. useFlyable(new Flyable() { @Override public void fly(String s) { System.out.println(s); System.out.println("风和日丽,晴空万里."); } }); //使用Lambda表达式的方式实现Flyable接口的fly方法并且作为参数传递. useFlyable((String s) -> { System.out.println(s); System.out.println("风和日丽,晴空万里."); }); } /* 定义方法useFlyable(Flyable flyable),需要传递一个Flyable接口的实现类参数,这里使用的多态. */ public static void useFlyable(Flyable flyable){ flyable.fly("阿帕奇直升机."); } }
-
-
-
-
Lambda的表达式练习3 -> 抽象方法有参有返回值.
-
需求
- 定义一个接口(Addable),里面定义一个抽象方法:int add(int x,int y);
- 定义一个测试类(AddableDemo),在测试类中提供两个方法,一个方法是:useAddable(Addable a),一个方法是主方法,在主方法中调用useAddable方法.
-
代码演示
-
接口类
-
package com.itheima.lambda.test03; /* 定义Addable接口,有且仅有一个抽象方法. */ public interface Addable { /* 定义抽象方法int add(int x,int y) */ int add(int x,int y); }
-
-
接口实现类
-
package com.itheima.lambda.test03; public class AddableImpl implements Addable{ @Override public int add(int x, int y) { return x + y; } }
-
-
测试类
-
package com.itheima.lambda.test03; public class AddableTest { /* 定义main方法,在main方法中调用useAddable方法. */ public static void main(String[] args) { //创建Addable接口的实现类AddableImpl对象. AddableImpl addable = new AddableImpl(); //调用useAddable方法,将实现类对象作为参数传递. useAddable(addable); //使用匿名内部类的方式实现Addable接口的add方法并且作为参数传递. useAddable(new Addable() { @Override public int add(int x, int y) { return x/y; } }); //使用Lambda表达式实现Addable的add方法并且作为参数传递. useAddable((int x, int y) -> {return x * y;}); } /* 定义useAddable方法,需要传递一个Addable接口的实现类作为参数,这里使用多态. */ public static void useAddable(Addable addable){ int sum = addable.add(2, 3); System.out.println(sum); } }
-
-
-
-
Lambda表达式的省略模式
- 参数类型可以省略,但是如果有多个参数的情况下,不能只省略一个,要么全部不省略,要么全部省略.
- 如果参数只有一个,例如(x) -> {},那么包裹x的()可以省略,可以写成x -> {}
- 如果代码块的语句只有一条,那么可以省略大括号/分号/return关键字.
-
Lambda表达式使用的注意事项
- 要使用Lambda表达式必须要有接口,而且接口中有且只有一个抽象方法.
- 必须有上下文环境才能推导出Lambda对应的接口.
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s9AoASNi-1576848536660)(assets/1561218298790.png)]
- 根据局部变量的赋值得知Lambda对应的接口
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hX915jf8-1576848536662)(assets/1561218397471.png)]
- f根据调用方法的参数得知Lambda对应的接口
- [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GdUvD4b4-1576848536664)(assets/1561218479801.png)]
-
Lambda表达式与匿名内部类的区别
-
所需类型不同
-
匿名内部类可以是接口,也可以是抽象类,也可以是具体类.
-
代码演示
-
package com.itheima.lambda.test05; public class LambdaDemo05 { public static void main(String[] args) { //调用useAnimal方法,使用匿名内部类作为参数传递. useAniaml(new Animal() { @Override public void play() { super.play(); } }); //调用useStudent方法,使用匿名内部类作为参数传递. useStudent(new Student(){ @Override public void study() { super.study(); } }); //调用useInter方法,使用匿名内部类作为参数传递. useInter(new Inter() { @Override public void show() { System.out.println("Inter的show方法执行了."); } }); } public static void useAniaml(Animal animal){ animal.play(); } public static void useStudent(Student student){ student.study(); } public static void useInter(Inter inter){ inter.show(); } }
-
-
-
Lambda表达式只能是接口.
-
-
使用限制不同
- 如果接口中有且仅有一个抽象方法,那么可以使用Lambda表达式,也可以使用匿名内部类.
- 如果接口中有多个抽象方法,那么只能使用匿名内部类实现所有方法,不可以使用Lambda表达式.
-
实现原理不同
- 匿名内编译之后会产生一个单独的.class文件.
- Lambda表达式编译之后不会产生单独的.class文件,对应的字节码会在运行的时候动态生成.
-
B.接口组成更新
-
接口的组成更新概述
- 常量
- public static final
- 抽象方法
- public abstract
- 默认方法 -> Java8提供
- default 返回值类型 方法名(){}
- 私有方法 -> Java9提供
- private 返回值类型 方法名(){}
- 静态方法 -> Java8提供
- static 返回值类型 方法名(){}
- 常量
-
接口中的默认方法
-
格式
-
public default 返回值类型 方法名(参数列表){ 方法体; }
-
-
代码演示
-
package com.itheima.inter; public interface DefaultInter { /* 在接口中定义默认方法. */ default void show(){ System.out.println("DefaultInter中的默认方法执行了."); } }
-
-
注意事项
- 默认方法不是抽象方法,所以Java不强制实现类去实现这个方法,但是这个方法可以被重写,重写的时候去掉default关键字.
- public可以省略,default不可以省略.
- 如果实现的多个接口中出现同名的默认方法,那么必须重写这个默认方法,否则会报错.
-
-
接口中的静态方法
-
格式
-
public static 返回值类型 方法名(参数列表){ 方法体; }
-
-
代码演示
-
package com.itheima.inter02; public interface StaticInter { /* 接口中定义静态方法. */ static void show(){ System.out.println("StaticInter中的静态方法执行了."); } }
-
-
注意事项
- 接口中的静态方法只可以通过接口调用,实现类不可以调用,因为Java是可以多实现的,当两个接口出现同名静态方法的时候,那么Java无法判断具体要调用哪个接口的静态方法.
- public可以省略,static不可以省略.
-
-
接口中的私有方法
-
格式
-
private 返回值类型 方法名(参数列表){ 方法体; }
-
private static 返回值类型 方法名(参数列表){ 方法体; }
-
-
代码演示
-
package com.itheima.inter03; public interface PrivateInter { //定义默认方法show01 default void show01(){ System.out.println("默认方法show01执行了"); method01(); System.out.println("默认方法show01结束了"); } //定义默认方法show02 default void show02(){ System.out.println("默认方法show02执行了"); method02(); System.out.println("默认方法show02结束了"); } //定义静态方法show01 static void show1(){ System.out.println("静态方法show01执行了"); method02(); System.out.println("静态方法show01结束了"); } //定义私有方法method01 private void method01(){ System.out.println("私有方法method01执行了."); } //定义静态私有方法method02 private static void method02(){ System.out.println("私有静态方法method02执行了."); } }
-
-
注意事项
- 接口中的私有方法只可以被接口中的其他方法调用.
- 接口中的默认方法可以调用私有的静态方法和非静态方法.
- 接口中的静态方法只可以调用私有的静态方法.
-
C.方法引用
-
体验方法引用
-
方法引用出现的原因
- 在使用Lambda表达式的时候,我们实际上没有操作对象,而是直接拿参数做操作,直接书写解决方案,那么我们在Lambda中所指定的解决方案,已经有地方存在相同答案,就没有必要写重复逻辑,那么我们如何直接使用已经有的解决方案呢,可以通过方法引用来实现.
-
方法引用初体验
-
需求
- 定义一个接口,PrintAble,里面定义一个抽象方法,void printString(String s).
- 定义一个测试类,测试类中提供usePrintable(Printable printable)方法和主方法,在主方法中调用usePrintable()方法.
-
代码演示
-
接口类
-
package com.itheima.inter.inter04; public interface Printable { void printString(String s); }
-
-
测试类
-
package com.itheima.inter.inter04; public class PrintableTest { public static void main(String[] args) { //使用Lambda表达式实现Printable接口的printString方法并且作为参数传递. usePrintable(s -> System.out.println(s)); //使用方法引用 usePrintable(System.out::println ); } public static void usePrintable(Printable printable){ printable.printString("黑马程序员."); } }
-
-
-
-
-
方法引用符
-
方法引用符
- ::
-
代码分析
-
Lambda表达式写法
-
//使用Lambda表达式实现Printable接口的printString方法并且作为参数传递. usePrintable(s -> System.out.println(s));
获取到参数s之后传递到Lambda表达式,传递给System.out.println进行处理.
-
-
方法引用写法
-
//使用方法引用 usePrintable(System.out::println);
直接使用System.out中的println方法来取代Lambda,代码更加简洁.
实际上参数s传递到方法引用中,就无须写明s了,我们对参数进行操作,那么进行什么操作?打印操作,Java已经给我们提供了System.out.println方法,所以我们直接引用方法即可,Java会根据上下文自动推导要使用哪个方法进行使用.
-
-
-
推导与省略
- 如果使用Lambda,那么根据"可推导就是可省略"的原则,无须指定参数类型,也无须指定重载形式,都将被自动推导.
- 如果使用方法引用,也可以根据上下文进行推导.
- 方法引用是Lambda的孪生兄弟.
-
-
引用类方法
-
格式
- 类名::静态方法
- Interger::parseInt
-
需求
- 定义一个接口(Converter),里面定义一个抽象方法 int convert(String s);
- 定义一个测试类(ConverterDemo),在测试类中提供两个方法,一个方法是useConverter(Converter c),一个方法是主方法,在主方法中调用useConverter方法.
-
代码演示
-
接口类
-
package com.itheima.reference; public interface Converter { int convert(String s); }
-
-
测试类
-
package com.itheima.reference; public class ConverterTest { public static void main(String[] args) { //使用Lambda表达式方法调用useConverter方法. useConverter(s -> Integer.parseInt(s)); //使用类方法引用调用useConverter方法. useConverter(Integer::parseInt); } public static void useConverter(Converter converter){ int num = converter.convert("123"); System.out.println(num); } }
-
-
-
注意事项
- Lambda表达式被类方法替代的时候,它的形式参数全部传递给静态方法作为参数.
-
-
引用对象的实例方法 -> 就是引用类中的成员方法
-
格式
- 对象::成员方法
-
需求
- 定义一个类(PrintString),里面定义一个方法public void printUpper(String s):把字符串参数变成大写的数据,然后在控制台输出.
- 定义一个接口(Printer),里面定义一个抽象方法void printUpperCase(String s).
- 定义一个测试类(PrinterDemo),在测试类中提供两个方法,一个方法是:usePrinter(Printer p)一个方法是主方法,在主方法中调用usePrinter方法.
-
代码演示
-
自定义类
-
package com.itheima.reference; public class PrintString { public void printUpper(String s){ System.out.println(s.toUpperCase()); } }
-
-
接口类
-
package com.itheima.reference; public interface Printer { /* 定义抽象方法 */ void printUpperCase(String s); }
-
-
测试类
-
package com.itheima.reference; public class PrinterTest { public static void main(String[] args) { //创建PrintString对象. PrintString printString = new PrintString(); //使用Lambda表达式实现Printer接口的printUpperCase方法并且作为参数传递. usePrinter(s -> System.out.println(s.toUpperCase())); //我们已经有了直接变化为大写打印的解决方案,所以直接使用方法引用即可,引用printString对象的printUpper方法. usePrinter(printString::printUpper); } public static void usePrinter(Printer printer){ printer.printUpperCase("hello world"); } }
-
-
-
注意事项
- Lambda被对象的实例方法替代的的时候,它的形式参数会全部传递给该方法作为参数.
-
-
引用类的实例方法
-
格式
- 类名::成员方法
-
需求
- 定义一个接口(MyString),里面定义一个抽象方法:String mySubString(String s,int x,int y).
- 定义一个测试类(MyStringDemo),在测试类中提供两个方法一个方法是:useMyString(MyString my),一个方法是主方法,在主方法中调用useMyString方法.
-
代码演示
-
接口类
-
package com.itheima.reference.demo04; public interface MyString { /* 定义抽象方法 */ String mySubString(String s,int beginIndex,int endIndex); }
-
-
测试类
-
package com.itheima.reference.demo04; public class MyStringTest { public static void main(String[] args) { //使用Lambda表达式的实现MyString的mySubString方法并且作为参数传递. useMyString((s, beginIndex, endIndex) -> s.substring(beginIndex,endIndex)); //直接引用String类提供好的方法SubString方法. useMyString(String::substring); } public static void useMyString(MyString myString){ String s = myString.mySubString("HelloWorld", 2, 5); System.out.println(s); } }
-
-
-
注意事项
- Lambda被类的实体方法替代的时候,第一个参数作为调用者,后面的全部参数传递给该方法作为参数.
-
-
引用构造器
-
格式
- 类名::new
-
需求
- 定义一个类(Student),里面有两个成员变量(name,age),并提供无参构造方法和带参构造方法,以及成员变量对应的get和set方法.
- 定义一个接口(StudentBuilder),里面定义一个抽象方法,Student build(String name,int age);
- 定义一个测试类(StudentDemo),在测试类中提供两个方法一个方法是:useStudentBuilder(StudentBuilder s)一个方法是主方法,在主方法中调用useStudentBuilder方法.
-
代码演示
-
学生类
-
package com.itheima.reference.demo05; public class Student { private String name; private int age; //无参构造方法 public Student() { } //有参构造方法 public Student(String name, int age) { this.name = name; this.age = age; } //Get/Set方法 public String getName() { return name; } public void setName(String name) { this.name = name; } public int getAge() { return age; } public void setAge(int age) { this.age = age; } }
-
-
接口类
-
package com.itheima.reference.demo05; public interface StudentBuilder { /* 定义抽象方法,返回Student对象. */ Student buildStudent(String name,int age); }
-
-
测试类
-
package com.itheima.reference.demo05; public class StudentBuilderTest { public static void main(String[] args) { //使用Lambda表达式实现StringBuilder的buildStudent方法并且作为参数传递. useStringBuilder((name, age) -> new Student("路飞",19)); //使用构造器引用. useStringBuilder(Student::new); } public static void useStringBuilder(StudentBuilder studentBuilder){ Student student = studentBuilder.buildStudent("路飞", 19); System.out.println(student.getName()+"--"+student.getAge()); } }
-
-
-
注意事项
public interface StudentBuilder { /* 定义抽象方法,返回Student对象. */ Student buildStudent(String name,int age); } ```
-
测试类
-
package com.itheima.reference.demo05; public class StudentBuilderTest { public static void main(String[] args) { //使用Lambda表达式实现StringBuilder的buildStudent方法并且作为参数传递. useStringBuilder((name, age) -> new Student("路飞",19)); //使用构造器引用. useStringBuilder(Student::new); } public static void useStringBuilder(StudentBuilder studentBuilder){ Student student = studentBuilder.buildStudent("路飞", 19); System.out.println(student.getName()+"--"+student.getAge()); } }
-
-
-
注意事项
- Lambda表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数.
-