1. Lambda表达式的简介
1.1. Lambda表达式的概念
Lambda表达式,是Java8的一个新特性,也是Java8中最值得学习的新特性之一。
Lambda表达式,从本质来讲,是一个匿名函数。可以使用这个匿名函数,实现接口中的方法。对接口进行非常简洁的实现,从而简化代码。
1.2. Lambda表达式的使用场景
通常来讲,使用Lambda表达式,是为了简化接口实现的。
关于接口的实现,可以有很多方式来实现。例如:设计接口的实现类、使用匿名内部类。但是lambda表达式 ,比这两种方式都简单,
public class Program {
public static void main(String[] args) {
// 无参、无返加值的函数式接口
interfaceImpl();
}
public static void interfaceImpl(){
// 1、使用显式的实现类对象
SingleReturnSingleParam param1 = new Impl();
// 2、使用匿名内部类实现
SingleReturnSingleParam param2 = new SingleReturnSingleParam() {
@Override
public int test(int a) {
return a * a;
}
};
// 3、使用lambda表达式实现
SingleReturnSingleParam param3 = a -> a * a;
System.out.println(param1.test(10));
System.out.println(param2.test(10));
System.out.println(param3.test(10));
}
private static class Impl implements SingleReturnSingleParam{
@Override
public int test(int a) {
return a * a;
}
}
// 定义匿名内部类
private interface SingleReturnSingleParam{
int test(int a);
}
}
1.3. Lambda表达式对接口的要求
虽然说,lambda表达式可以在一定程度上简化接口的实现。但是,并不是所有的接口都可以使用lambda表达式来简化实现的。
lambda表达式毕竟只是一个匿名方法。当实现的接口中的方法过多或者过少的时候,lambda是不适用的。
1.4. 函数式接口
1.4.1. 基础概念
如果说,一个接口中,要求实现类必须实现的抽象方法,有且只有一个!这样的接口,说是函数式接口。
// 这个接口中,有且只有一个方法,是必须实现的,因此是一个函数式接口
interface Test1{
void test();
}
// 这个接口中,实现类必须要实现的方法,有两个,因此不是一个函数式接口
interface Test2{
void test1();
void test2();
}
// 这个接口中,实现类必须要实现的方法,有零个!因此不是一个函数式接口
interface Test3{
}
// 这个接口中,虽然没用定义任何的方法,但是可以从父接口中继承到一个抽象方法,是一个函数式接口
interface Test4 extends Test1{
}
// 这个接口中,虽然定义了两个方法,但是defualt方法子类不是必须实现的
// 因此,实现类实现这个接口的时候,必须实现的方法只有一个!是一个函数式接口
interface Test5{
void test5();
default void test(){};
}
// 这个接口中的toString方法,是Object类中定义的方法。
// 此时,实现类在实现接口的时候,toString可以不重写!因为可以从以从父类Object中继承到!
// 此时,实现类在实现接口的时候,有且只有一个方法是必须要重写的。是一个函数式接口!
interface Test6{
void test6();
String toString();
}
思考一下:下面的两个接口是不是函数式接口?
interface Test7{
String toString();
}
interface Test8{
void test();
default void test1(){};
static void test2(){};
String toString();
}
1.4.2. @FunctionalInterface
是一个注解,用在接口之前,判断这个接口是否是一个函数式接口。如果是函数式接口,没有任何问题,如果不是一个函数式接口,则会报错。功能类似于@Override。
@FunctionalInterface
interface FunctionalInterfaceTest{
void test();
}
1.4.3. 系统内置的函数式接口
接口名字 | 参数 | 返回值 | 特殊接口 |
---|---|---|---|
Predicate< T > | T | boolean | IntPredicate:参数 int,返回值 boolean LongPredicate:参数 long,返回值 boolean DoublePredicate:参数 double,返回值 boolean |
Consumer< T > | T | void | IntConsumer:参数 int,返回值 void LongConsumer:参数 long,返回值 void DoubleConsumer:参数 double,返回值 void |
Function<T , R> | T | R | IntFunction< R >:参数 int,返回值 R IntToDoubleFunction:参数 int,返回值 double IntToLongFunction:参数 int,返回值 long LongFunction< R >:参数 long,返回值 R LongToIntFunction:参数 long,返回值 int LongToDoubleFunction:参数 long,返回值double DoubleFunction< R >:参数 double,返回值 R DoubleToIntFunction:参数 double,返回值 int DoubleToLongFunction:参数 double,返回值 long |
Supplier< T > | 无 | T | BooleanSupplier:参数 无,返回值 boolean IntSupplier:参数 无,返回值 int LongSupplier:参数 无,返回值 long DoubleSupplier:参数 无,返回值 double |
UnaryOperator< T > | T | T | IntUnaryOperator :参数 int,返回值 int LongUnaryOperator :参数 long,返回值 long DoubleUnaryOperator :参数 double,返回值 int |
BinaryOperator< T > | T,T | T | IntBinaryOperator:参数 int,int,返回值 int LongBinaryOperator:参数 long,long,返回值 long DoubleBinaryOperator:参数 double,double,返回值 double |
BiPredicate<L,R> | L,R | boolean | |
BiConsumer<T,U> | T,U | void | |
BIFunction<T,U,R> | T,U | R |
2. Lambda表达式的语法
准备interface!
// 无参无返回值
@FunctionalInterface
public interface NoneReturnNoneParameter {
void test();
}
// 一个参数无返回值
@FunctionalInterface
public interface NoneReturnSingleParameter {
void test(int a);
}
// 多个参数无返回值
@FunctionalInterface
public interface NoneReturnMutipleParameter {
void test(int a,int b);
}
// 无参有返回值
@FunctionalInterface
public interface SingleReturnNoneParameter {
int test();
}
// 一个参数有返回值
@FunctionalInterface
public interface SingleReturnSingleParameter {
int test(int a);
}
// 多个参数有返回值
@FunctionalInterface
public interface SingleReturnMutipleParameter {
int test(int a,int b);
}
2.1. Lambda表达式的基础语法
lambda表达式,其实本质来讲,就是一个匿名函数。因此在写lambda的时候,不需要关心方法名是什么。
实际上,我们在写lambda表达式的时候,也不需要关心返回值类型。
我们在写lambda表达式的时候,只需要关注两部分内容即可:参数列表 和 方法体。
lambda表达式的基础语法:
(参数) -> {
方法体
};
参数部分: 方法的参数列表,要求和实现的接口中的方法参娄部分一致,包括参数的数量和类型。
方法体的部分: 方法和实现部分,如果接口中定义的方法有返回值,则在实现的时候,注意返回值的返回。
->: 分隔参数部分和方法体部分。
public class BasicSyntax {
public static void main(String[] args) {
// 1、实现无参,无返回值的函数式接口
NoneReturnNoneParameter lambda1 = () -> {
System.out.println("这是一个无参数无返回值的接口");
};
lambda1.test();
// 2、实现一个参数,无返回值的函数式接口
NoneReturnSingleParameter lambda2 = (int a) -> {
System.out.println("这是一个参数,无返回值的方法,参数a = " + a);
};
lambda2.test(10);
// 3、实现多个参数,无返回值的函数式接口
NoneReturnMutipleParameter lambda3 = (int a,int b) -> {
System.out.println("这是多个参数,无返回值的方法,参数a = " + a +" 参数b = " + b);
};
lambda3.test(100,111);
// 4、实现无参,有返回值的函数式接口
SingleReturnNoneParameter lambda4 = () -> {
System.out.println("这是无参,有返回值的方法,返回值是 = " + 10);
return 10;
};
int test4 = lambda4.test();
System.out.println(test4);
// 5、实现一个参数,有返回值的函数式接口
SingleReturnSingleParameter lambda5 = (int a) -> {
System.out.println("这是一个参数,有返回值的方法,参数a = " + a);
return a;
};
int test5 = lambda5.test(10);
System.out.println(test5);
// 6、实现多个参数,有返回值的函数式接口
SingleReturnMutipleParameter lambda6 = (int a,int b) -> {
System.out.println("这是多个参数,有返回值的方法,参数a = " + a +" 参数b = " + b);
return a + b;
};
int test6 = lambda6.test(10, 20);
System.out.println(test6);
}
}
2.2. Lambda表达式的进阶语法
在上述代码中,的确可以使用lambda表达式实现接口,但是依然不够简洁,有简化的空间。
2.2.1. 参数部分的简化
- 参数的类型
1. 由于在接口的方法中,已经定义了每个参数的类型是什么。而且在使用lambda表达式实现接口的时候,必须要保证参数的数量和类型需要和接口中的方法保持一致。因此,此时lambda表达式中的参数的类型可以省略不写,
2. 注意事项:
如果需要省略参数的类型,要保证
:要省略,每一个参数的类型都必须省略不写。绝对不能出现,有的参数类型省略了,有的参数类型没有省略。// 实现多个参数,有返回值的函数式接口 SingleReturnMutipleParameter lambda = (a,b) -> { System.out.println("这是多个参数,有返回值的方法,参数a = " + a +" 参数b = " + b); };
- 参数的小括号
1. 如果方法的参数列表中的参数数量有且只有一个,此时,参数列表的小括号是可以省略不写的。
2. 注意事项:
只有当参数的数量是一个的时候,多了,少了都不能省略。
省略掉小括号的同时,必须要省略掉参数类型。
如果没有参数的话小括号是不能省略的。// 实现一个参数,无返回值的函数式接口 NoneReturnSingleParameter lambda = a -> { System.out.println("这是一个参数,无返回值的方法,参数a = " + a); };
2.2.2. 方法体部分的简化
-
方法体的大括号
1. 如果方法体中的逻辑,有且只有一句的情况下,那么大括号是可以省略的。// 实现一个参数,无返回值的函数式接口 NoneReturnSingleParameter lambda = a -> System.out.println("这是一个参数,无返回值的方法,参数a = " + a);
-
return的精简
1. 如果一个方法中唯一的一条语句是一个返回语句,此时在省略掉大括号的同时,也省略掉return。// 实现一个参数,有返回值的函数式接口 SingleReturnSingleParameter lambda = a -> a * a;
3. 函数引用
lambda表达式是为了简化接口的实现的。在lambda表达式中,不应该出现比较复杂的逻辑。如果在lambda表达式中出现了过于复杂的逻辑,会对程序的可读性造成非常大的影响。如果在lambda表达式中需要处理的逻辑比较复杂,一般会单独的写一个方法。在lanbda表达式中直接引用这个方法即可。
或者
在有些情况下,我们需要在lambda表达式中实现的逻辑,在另一个地方已经写好了。此时我们就不需要单独写一遍,只需要直接引用这个已经存在的方法即可。
3.1. 静态方法的引用
- 语法
- 类::静态方法
- 注意事项
- 在引用的方法后面,不要加小括号。
- 引用的这个方法,参数(数量,类型)和返回值,必须要和接口中定义的一致。
- 示例
public class Syntax {
interface Calculate{
int calulate(int a,int b);
}
public static void main(String[] args) {
// 引用一个非静态方法
Calculate calculate = new Syntax()::calculate;
System.out.println(calculate.calulate(50,50));
}
private int calculate(int a,int b){
// 稍微复杂的逻辑
if (a != b){
return a - b;
}
return a + b;
}
}
3.2. 非静态方法的引用
- 语法
- 对象::非静态方法
- 注意事项
- 在引用的方法后面,不要加小括号。
- 引用的这个方法,参数(数量 ,类型)和返回值,必须要和接口中定义的一致。
- 示例
public class Syntax {
interface Calculate{
int calulate(int a,int b);
}
public static void main(String[] args) {
// 引用一个非静态方法
Calculate calculate = new Syntax()::calculate;
System.out.println(calculate.calulate(50,50));
}
private int calculate(int x,int y){
// 稍微复杂的逻辑
if (x > y){
return x -y;
} else if (y > x){
return y - x;
}
return x + y;
}
}
3.3. 构造方法的引用
- 使用场景
- 如果一个函数式接口中定义的方法,仅仅是为了得到一个类的对象。此时我们就可以使用构造方法的引用。简化这个方法的实现。
- 语法
- 类名::new
- 注意事项
- 可以通过接口的方法的参数,区分引用不同的构造方法。
- 示例
public class Syntax {
public static class Person{
String name;
int age;
public Person(){
System.out.println("Person类无参数的构造方法执行了");
}
public Person(String name){
this.name = name;
System.out.println("Person类的有参数的构造方法执行了");
}
public Person(String name,int age){
this.name = name;
this.age = age;
System.out.println("Person类的多个参数的构造方法执行了");
}
}
@FunctionalInterface
public static interface GetPersonNoneParameter{
Person get();
}
@FunctionalInterface
public static interface GetPersonSingleParameter{
Person get(String name);
}
@FunctionalInterface
public static interface GetPersonMutipleParameter{
Person get(String name,int age);
}
public static void main(String[] args) {
// 1、使用lambda表达式,实现GetPerson接口
GetPersonNoneParameter getPerson1 = Person::new;
// 2、使用lambda表达式,实现一个参数的GetPersonSingleParameter
GetPersonSingleParameter getPerson2 = Person::new;
// 3、使用lambda表达式,实现多个参数的GetPersonMutipleParameter
GetPersonMutipleParameter getPerson3 = Person::new;
// 因为它们都能够在Person类中找到对应的构造方法
Person person1 = getPerson1.get();
Person person2 = getPerson2.get("");
Person person3 = getPerson3.get("",0);
}
}
3.4. 对象方法的特殊引用
如果使用lambda表达式,实现某些接口的时候,lambda表达式中包含了某一个对象或者说它的参数中包含了某个对象,此时方法体中,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑,其他的参数,可以作为调用方法的参数。此时,可以对这种实现进行简化。
public class Syntax {
private static class Person{
private String name;
// 提供get和set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public void show(){
}
public void showTest(int a,int b){
}
}
@FunctionalInterface
private interface GetNameInterface{
String get(Person person);
}
@FunctionalInterface
private interface SetNameInterface{
void set(Person person,String name);
}
@FunctionalInterface
private interface ShowInterface{
void show(Person person);
}
@FunctionalInterface
private interface ShowInterface2{
void show2(Person person,int a,int b);
}
public static void main(String[] args) {
Person person = new Person();
person.setName("小明");
// 这个方法的逻辑就是获取Person的name
//MyInterface myInterface = p -> p.getName();
// 通过对象的getName方法直接获取
GetNameInterface getNameInterface = Person::getName;
System.out.println(getNameInterface.get(person));
// 这个方法的逻辑就是为了给对象的某个属性设置参数
SetNameInterface setNameInterface = Person::setName;
setNameInterface.set(person,"666");
System.out.println(getNameInterface.get(person));
// 和对象中定义的方法参数的返回值一致,直接调用对象里的方法
ShowInterface showInterface = Person::show;
showInterface.show(person);
ShowInterface2 showInterface2 = Person::showTest;
showInterface2.show2(person,100,200);
}
}
4. Lambda表达式需要注意的问题
这里类似于局部内部类,匿名内部类,依然存在闭包的问题。
如果在lambda表达式中,使用到了局部变量,那么这个局部变量会被隐式的声明为final。是一个常量,不能修改值。