Lambda表达式
体验lambda表达式
需求:启动一个线程,在控制台输出:多线程程序启动了
方式1:
- 定义一个类MyRunnable实现Runnable接口,重写run方法
- 创建MyRunnable类的对象
- 创建Thread类对象,把MyRunnable对象作为构造参数传递
- 启动线程
/**
* @author: 小码农
* @create: 2021-08-06 10:38
**/
public class RunnableDemo1 implements Runnable {
@Override
public void run() {
System.out.println("多线程程序启动了");
}
}
RunnableDemo1 r = new RunnableDemo1();
new Thread(r).start();
方式2:
- 匿名内部类的方式改进
//匿名内部类改进
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("启动多线程");
}
}).start();
方式3:
- Lambda表达式的方式改进
//Lambda表达式改进
new Thread(() ->{
System.out.println("启动多线程");
}).start();
可以看出,对比前两种方法,Lambda表达式更为简单快捷
格式
方式3代码分析
- ():里面没有内容,可以看成是方法形式参数为空
- ->:用箭头指向后面要做的事情
- {}:包含一段代码,我们称之为代码块,可以看成是方法体中的内容
组成Lambda表达式的三要素:形式参数、箭头、代码块
- 格式:(形参)->{代码块}
- 形参:如果有多个参数,参数之间用逗号分隔;如果没有参数,留空
- ->:由英文中画线和大于符号组成,固定写法,代表指向动作
- 代码块:使我们具体要做的事情,也就是以前我们写的方法体内容
使用前提
- 有一个接口
- 接口中有且仅有一个抽象方法
练习1:
- 定义一个接口Eatable,里面定义一个抽象方法:void eat();
- 定义一个测试类EatableDemo,在测试类中提供两个方法
- 一个方法时:useEatable(Eatable e)
- 一个方法是主方法,在主方法中调用useEatable方法
/**
* @author: 小码农
* @create: 2021-08-06 10:54
**/
public class Demo2 {
public static void main(String[] args) {
DemoEatable e= new DemoEatableImp();
useEatable(e);
//匿名内部类
useEatable(new DemoEatable() {
@Override
public void eat() {
System.out.println("Hello Java");
}
});
//使用Lambda表达式
useEatable(()->{
System.out.println("Hello JavaSE");
});
}
public static void useEatable(DemoEatable e){
e.eat();
}
}
练习2:
- 定义一个接口Flyable,里面定义一个抽象方法void fly(String s)
- 定义一个测试类FlyableDemo,在测试类中提供两个方法
- 一个方法是:useFlyable(Flyable f)
- 一个方法是主方法,在主方法中调用useFlyable方法
/**
* @author: 小码农
* @create: 2021-08-06 11:01
**/
public class Demo3 {
public static void main(String[] args) {
//匿名内部类
useFlyable(new Flyable() {
@Override
public void fly(String s) {
System.out.println(s);
System.out.println("向着蓝天飞翔");
}
});
//Lambda表达式
useFlyable((String s)->{
System.out.println(s);
System.out.println("向着蓝天飞翔");
});
}
public static void useFlyable(Flyable f){
f.fly("向着大地奔跑");
}
}
练习3:
- 定义一个接口Addable,里面定义一个抽象方法:int add(int x, int y);
- 定义一个测试类(AddableDemo),在测试类中提供两个方法
- 一个方法是:useAddable(Addable a)
- 一个方法是主方法,在主方法中调用useAddable方法
/**
* @author: 小码农
* @create: 2021-08-06 11:10
**/
public interface Addable {
int add(int x,int y);
}
/**
* @author: 小码农
* @create: 2021-08-06 11:10
**/
public class Demo4 {
public static void main(String[] args) {
useAddable((int x,int y)->{
return x+y;
});
}
public static void useAddable(Addable a){
int sum = a.add(10,20);
System.out.println(sum);
}
}
省略模式
- 带参的Lambda表达式,参数类型可以省略,但是有多个参数的情况下不能只省略一个
useAddable(( x , y )->{
return x + y;
});
- 如果参数有且只有一个,包裹参数的小括号也可以省略
useFlyable(s->{
System.out.println(s);
});
- 如果代码块语句只有一句,大括号和分号也可以省略
useFlyable(s -> System.out.println(s));
- 如果代码块只有一句,且包含return,省略大括号和分号的同时,return也要省略。否则将会报编译错误
useAddable((x,y) -> x + y);
注意事项
-
使用Lambda必须要有接口,并且要求接口中有且仅有一个抽象方法
-
必须有上下文环境,才能推导出Lambda对应的接口
-
根据局部变量的赋值得知Lambda对应的接口:
Runnable r = ()->System.out.println("Lambda");
-
根据调用方法的参数得知Lambda对应的接口:
new Thread(()->System.out.println("Lambda")).start();
-
Lambda与匿名内部类的区别
匿名内部类是使用范围包括:抽象类、具体类、接口。抽象方法数量没有要求,只要重写即可。编译后会产生新的字节码文件。Lambda表达式的使用范围:仅限于接口interface,且接口只能有一个抽象方法。编译后无字节码文件
总结:
所需类型不同
-
匿名内部类:可以是接口,也可以是抽象类,还可以是具体类
-
Lambda表达式:只能是接口
使用限制不同
- 如果接口中有且仅有一个抽象方法,可以使用Lambda表达式,也可以使用匿名内部类
- 如果接口中多于一个抽象方法,只能使用匿名内部类,而不能使用Lambda表达式
实现原理不同
- 匿名内部类:编译之后,产生一个单独的.class字节码文件
- Lambda表达式:编译之后,没有一个单独的.class字节码文件,对应的字节码会在运行时动态生成
接口组成更新
接口的组成
- 常量:public static final
- 抽象方法:public abstract
- 默认方法default(Java8)
- 静态方法static(Java8)
- 私有方法(Java9)
接口默认方法
接口中默认方法的定义格式:
- 格式:public default返回值类型方法名(参数列表){ }
- 范例:public default void show3(){ }
接口中默认方法的注意事项:
- 默认方法不是抽象方法,所以不强制被重写。但是可以被重写,重写的时候去掉default关键字
- public可以省略,default不能省略
public interface Inter2 {
void show1();
void show2();
public default void show3(){
System.out.println("show3");
}
}
/**
* @author: 小码农
* @create: 2021-08-06 16:19
**/
public class Demo8_1 implements Inter2{
@Override
public void show1() {
System.out.println("one show1");
}
@Override
public void show2() {
System.out.println("one show2");
}
//重写默认方法
@Override
public void show3() {
System.out.println("one show3");
}
}
接口静态方法
接口静态方法的定义格式:
- 格式:public static 返回值类型 方法名(参数列表){ }
- 范例:public static void show(){ }
接口中静态方法的注意事项:
- 静态方法只能通过接口名调用,不能通过实现类名或对象名调用
- 接口中声明静态方法的public可以省略,static不能省略
/**
* @author: 小码农
* @create: 2021-08-06 16:46
**/
public interface Inter4 {
//抽象方法
void show();
//默认方法
default void show2(){
System.out.println("默认方法");
}
//静态方法
static void show3(){
System.out.println("静态方法");
}
}
public class Demo9 {
public static void main(String[] args) {
Inter4 i = new Demo9_Inter4();
i.show();
i.show2();
Inter4.show3(); //使用接口调用静态方法
}
}
接口私有方法
接口中私有方法的定义格式(jdk1.9后新增):
- 格式1:private 返回值类型 方法名(参数列表){ }
- 范例1:private void show(){ }
- 格式2:private static 返回值类型 方法名(参数列表){ }
- 范例2:private static void method(){ }
接口私有方法注意事项:
- 默认方法可以调用私有的静态方法和非静态方法
- 静态方法只能调用私有的静态方法
//私有方法使用
public interface Inter5 {
default void show1(){show();show3();}
default void show2(){show();show3();}
static void method1(){show();}
static void method2(){show();}
private static void show(){System.out.println("托儿所")}
private void show3(){System.out.println("小学僧")}
}
//调用接口实现类创建对象实现方法输出
/*
托儿所
小学僧
托儿所
小学僧
托儿所
托儿所
*/
方法引用
在使用Lambda表达式的时候,我们实际上传递进去的代码就是一种解决方案:拿参数做操作
那么考虑一种情况:如果我们在Lambda中所指定的操作方案,已经有其它地方存在相同方案,那是否还有必要再写重复逻辑呢?答案是没有必要
方法引用就是在某些地方直接使用已经存在的方案
/**
* @author: 小码农
* @create: 2021-08-06 17:29
**/
public class Demo11 {
public static void main(String[] args) {
usePrint(s -> System.out.println(s));
//方法引用符 ::,此处方法引用后直接将字符串s传入println方法中
usePrint(System.out::println);
//可推导的就是可省略的
}
private static void usePrint(Inter6 i){
i.printString("Hello world");
}
}
方法引用符
- :: 该符号为引用运算符,而它所在的表达式被称为方法引用
推导与省略
- 如果使用Lambda,根据“可推导就是可省略”原则,无需指定参数类型,也无需指定重载形式,它们都将被自动推导
- 如果使用方法引用,同样可以根据上下文进行推导
- 方法引用是Lambda的孪生兄弟
常见引用方式:
- 引用类方法
- 引用对象的实例方法
引用类方法
引用类方法,其实就是引用类的静态方法
- 格式:类名::静态方法
- 范例:Integer::parseInt
- Integer类的方法:public static int parseInt(String s) 将此String转换为Int类型数据
练习:
-
定义一个接口(Converter),里面定义一个抽象方法 int convert(String s)
-
定义一个测试类(ConverterDemo),在测试类中提供两个方法,
- useConverter(Converter c)
- 主方法main
引用类方法,Lambda表达式被类方法替代时,它的形参全部传递给静态方法作参数
/**
* @author: 小码农
* @create: 2021-08-06 19:04
**/
public class Demo_Converter {
public static void main(String[] args) {
//Lambda表达式
useConverter(s -> Integer.parseInt(s));
//引用类方法,Lambda表达式被类方法替代时,它的形参全部传递给静态方法作参数
useConverter(Integer::parseInt);
}
public static void useConverter(Converter c){
int number = c.convert("666");
System.out.println(number);
}
}
引用对象实例方法
引用对象的实例方法,就是引用类中的成员方法
- 格式:对象::成员方法
- 范例:“Hello World”::toUpperCase
- String类中的方法:public String toUpperCase() 将String所有字符转为大写
练习
- 定义一个类PrintString,里面定义一个方法
- public void printUpper(String s) 把字符串变大写,然后输出
- 定义一个接口Printer,内有一个抽象方法
- void printUpperCase(String s)
- 定义一个测试类Demo_Printer,在测试类中提供主方法和usePrinter方法
Lambda表达式被对象的实例方法代替的时候,它的形参都要传递给该方法作为参数
/**
* @author: 小码农
* @create: 2021-08-06 19:14
**/
public class Demo_printer {
public static void main(String[] args) {
//使用Lambda表达式
usePrinter(s -> System.out.println(s.toUpperCase()));
//使用引用对象实例
PrintString ps = new PrintString();
usePrinter(ps::printUpper);
//Lambda表达式被对象的实例方法代替的时候,它的形参都要传递给该方法作为参数
}
public static void usePrinter(Printer p){
p.printUpperCase("Helloworld");
}
}
接口
/**
* @author: 小码农
* @create: 2021-08-06 19:14
**/
public interface Printer {
void printUpperCase(String s);
}
PrintString类
/**
* @author: 小码农
* @create: 2021-08-06 19:14
**/
public class PrintString {
public void printUpper(String s){
String str = s.toUpperCase();
System.out.println(str);
}
}
引用类的实例方法
引用类的实例方法,其实就是引用类中的成员方法
- 格式:类名::成员方法
- 范例:String::sbustring
- String类中的方法:public String substring(int beginIndex,int endIndex)
- 从beginIndex开始到enIndex结束,截取字符串,返回一个字符串,长度为end-begin
练习:
- 定义一个接口MyString,里面定义一个抽象方法
- String mySubString(String s,int x,int y)
- 定义一个测试类Demo_MyString,在测试类中提供两个方法
- useMyString(MyString m)
- main
Lambda表达式被类的实例方法替代的时候,第一个参数作为调用者,后面的参数全部传递该方法作为参数
/**
* @author: 小码农
* @create: 2021-08-06 19:30
**/
public class Demo13_MyString {
public static void main(String[] args) {
//在主方法中调用useMyString方法
useMyString((s,x,y)-> s.substring(x,y));
//引用类的实例方法
useMyString(String::substring);
//Lambda表达式被类的实例方法替代的时候,第一个参数作为调用者,后面的参数全部传递该方法作为参数
}
public static void useMyString(MyString m){
String string = m.mySubString("hello world", 2, 5);
System.out.println(string);
}
}
引用构造器
引用构造器,其实就是引用构造方法
-
格式:类名::new
-
范例:Student::new
练习
- 定义一个类Student,里面有两个成员变量name,age
- 并提供无参构造方法和带参构造方法,以及成员变量对应的get和set方法
- 定义一个接口StudentBuilder,里面定义一个抽象方法
- Student build(String name,int age);
- 定义一个测试类Demo_Student,在测试类中提供两个方法
- useStudentBuilder
- main
Lambda表达式被构造器替代的时候,它的形式参数全部传递给构造器作为参数
测试类
/**
* @author: 小码农
* @create: 2021-08-06 19:39
**/
public class Demo14_Student {
public static void main(String[] args) {
//使用Lambda表达式
useStudent((s,i)-> new StudentClass(s,i));
//使用引用构造器
useStudent(StudentClass::new);
}
public static void useStudent(StudentBuilder sb){
StudentClass sc = sb.builder("小李",18);
System.out.println(sc.getName()+" "+sc.getAge());
}
}
接口
/**
* @author: 小码农
* @create: 2021-08-06 19:38
**/
public interface StudentBuilder {
StudentClass builder (String name,int age);
}
Student类
/**
* @author: 小码农
* @create: 2021-08-06 19:39
**/
public class StudentClass {
private String name;
private int age;
public StudentClass(String name, int age) {
this.name = name;
this.age = age;
}
public StudentClass() {
}
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;
}
}