Lambda 表达式 学习笔记

2年前 (2022) 程序员胖胖胖虎阿
200 0 0

Lambda表达式

  • Lambda表达式
    • 1、 Lambda表达式的简介
      • 1.1 Lambda表达式的概念
      • 1.2 Lambda表达式的使用场景
      • 1.3 Lambda 表达式对接口的要求
      • 1.4 函数式接口
        • 1.4.1 基础概念
        • 1.4.2 @FunctionalInterface
        • 1.4.3 常见函数式接口
    • 2、 Lambda表达式的语法
      • 2.1、 Lambda表达式的基础语法
      • 2.2、Lambda表达式的语法进阶
        • 2.2.1、 参数部分的精简
        • 2.2.2、方法体部分的精简
    • 3、函数引用
      • 3.1、 静态方法的引用
      • 3.2、非静态方法的引用
      • 3.3、构造方法的引用
      • 3.4、对象方法的特殊引用
    • 4、Lambda表达式需要注意的问题

Lambda表达式

1、 Lambda表达式的简介

1.1 Lambda表达式的概念

lambda表达式,是Java8的一个新特性, 也是Java8中最值得学习的新特性之一。

lambda表达式,从本质来讲,是一个匿名函数。可以使用这个匿名函数,实现接口中的方法。对接口进行非常简洁的实现,从而简化代码。

1.2 Lambda表达式的使用场景

通常来讲,使用lambda表达式,是为了简化接口实现的。

关于接口实现,可以有很多方式来实现。例如:设计接口的实现类、使用匿名内部类。但是lambda表达式,比这两种方式都简单。

public ckass program{
    public static void main(String[] args){
        //无参、无返回值的函数式接口
        interfaceImpl();
    }
    
    private static void interfaceImpl(){
        // 1. 使用显示的实现类对象
        SingleReturnSingleParameter parameter1 = new Impl();
        // 2. 使用匿名内部类实现
        SingleReturnSingleParameter parameter2 = new SingleReturnSingleParameter(){
            @Overrride
            public int test(int a){
                return a * a;
            }
        };
        // 3. 使用lambda表达式实现
        SingleReturnSingleParameter parameter3 = a -> a * a;
        
        System.out.println(parameter1.test(10));
        System.out.println(parameter2.test(10));
        System.out.println(parameter3.test(10));
    }
    
    private static class Impl implements SingleReturnSingleParameter{
        
        @Override
        public int test(int a){
            return a * a;
        }
    }
    
}

1.3 Lambda 表达式对接口的要求

虽然说,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 常见函数式接口

函数式接口 参数类型 返回类型 说明
Consumer 消费型接口 T void void accept(T t),对类型为T的对象应用操作
Supplier 供给型接口 T T get{}; 返回类型为 T 的对象
Function<T, R> 函数式接口 T R R apply(T t); 对类型为T 的对象应用操作,并返回类型为R类型的对象
Predicate 断言型接口 T boolean boolean test(T t); 确定类型为T 的对象是否满足条件,并返回Boolean类型

2、 Lambda表达式的语法

2.1、 Lambda表达式的基础语法

lambda表达式,其实本质来讲,就是一个匿名函数。因此在写lambda表达式的时候,不需要关心方法名是什么。

实际上,我们在写lambda表达式的时候,也不需要关系返回值类型。

我们在写lambda表达式的时候,只需要关注两部分内容即可: 参数列表方法体

lambda 表达式的基础语法:

(参数) -> {
    方法体
};

参数部分: 方法的参数列表,要求和是实现的接口给中的方法参数部分一致,包括参数的数量和类型。

方法体部分: 方法的实现部分,若果接口中定义的方法有返回值,则在实现的时候,注意返回值的返回。

->: 分隔参数部分和方法体部分

public class Test{
    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, 200);
        
        // 4. 实现无参,有返回值的函数式接口
        SingleReturnNoneParameter lambda4 = () -> {
            System.out.println("这是一个无参,有返回值的方法, 返回值是:" + 10);
            return 10;
        }
        int ret1 = lambda4.test();
        System.out.println(ret1);
       
        // 5. 实现一个参数,有返回值的函数值接口
        SingleReturnSingleParameter lambda4 = (int a) -> {
            System.out.println("这是一个参数,有返回值的方法, 返回值是:" + a);
            return a;
        }
        int ret2 = lambda4.test(100);
        System.out.println(ret2);
        
        // 6. 实现多个参数,有返回值的函数式接口
        SingleReturnSingleParameter lambda4 = (int a, int b) -> {
            System.out.println("这是一个参数,有返回值的方法");
            return a+b;
        }
        int ret3 = lambda4.test(100, 200);
        System.out.println(ret3);
    }
}

2.2、Lambda表达式的语法进阶

2.2.1、 参数部分的精简

  • 参数的类型

    • 由于在接口中的方法中,已经定义了每一个参数的类型是什么,而且在使用lambda表达式实现接口的时候,必须要保证参数的数量和类型需要和接口中的方法保持一致。因此,此时lambda表达式中的参数的类型可以省略不写。

    • 注意事项:

      • 如果需要省略参数的类型,要保证:要省略,每一个参数的类型都必须省略不写。绝对不能出现,有的参数类型省略了,有的参数类型没有省略。
      // 多个参数、无返回值的方法实现
      NoneReturnMutipleParameter lambda1 = (a, b) -> {
      	System.out.println("多个参数、无返回值方法的实现:参数a是 " + a + ", 参数b是 " + b);
      }
      
  • 参数的小括号

    • 如果方法的参数列表中的参数数量 有且只有一个,此时,参数列表的小括号是可以省略不写的

    • 注意事项:

      • 只有当参数的数量是一个的时候,多了、少了都不能省略
      • 省略掉小括号的同时,必须要省略参数的类型
      //有参、无返回值的方法实现
      NoneReturnSingleParameter lambda2 = a -> {
      	System.out.println("一个参数、无返回值方法的实现:参数是 " + a);
      }
      

2.2.2、方法体部分的精简

  • 方法体大括号的精简

    • 当一个方法体中的逻辑,有且只有一句的情况下, 大括号可以省略
    //有参、无返回值的方法实现
    NoneReturnSingleparameter lambda2= a -> System.out.println("一个参数、无返回值方法的实现:参数是 " + a);
    
  • return 的精简

    • 如果一个方法中唯一的一条语句是一个返回语句,此时在省略掉大括号的同时,也必须省略掉return
    SingleReturnMutipleParameter lambda3 = (a, b) -> a + b;
    

3、函数引用

lambda表达式是为了简化接口的实现的。在lambda表达式中,不应该出现比较复杂的逻辑。如果在1ambda表达式中出现了过于复杂的逻辑,会对程序的可读性造成非常大的影响。如果在lambda表达式中需要处理的逻辑比较复杂,一般情况会单独的写一个方法。在lambda表达式中直接引用这个方法即可。

或者,在有些情况下,我们需要在lambda表达式中实现的逻辑,在另外一个地方已经写好了。此时我们就不需要再单独写一遍,只需要直接引用这个已经存在的方法即可。

函数引用: 引用一个已经存在的方法,使其替代lambda表达式完成接口的实现。

3.1、 静态方法的引用

  • 语法:

    • 类::静态方法
  • 注意事项:

    • 在引用的方法后面,不要添加小括号
    • 引用的这个方法,参数(数量、类型)和返回值,必须要跟接口中定义的一致
  • 实例:

    public class Syntax1 {
        // 静态方法的引用
        public static void main(String[] args){
            //  实现一个多个参数的,一个返回值的接口
            //  对一个静态方法的引用
            //  类::静态方法
            SIngleReturnMutipleParameter lambda1 = Calculator::calculate;
            System.out.println(lambda1.test(10,20));
        }
        
        private static class Calculator{
            public static int calculate(int a, int b){
                //  稍微复杂的逻辑:计算 a 和 b 的差值的绝对值
                if(a > b){
                    return a - b;
                }
                return b - a;
            }
        }
    }
    

3.2、非静态方法的引用

  • 语法:

    • 对象::非静态方法
  • 注意事项:

    • 在引用的方法后面,不要添加小括号
    • 引用的这个方法,参数(数量、类型)和返回值,必须要跟接口中定义的一致
  • 实例

    public class Syntax2 {
     
        public static void main(String[] args){
               // 非静态方法的引用, 需要使用对象来完成
            SIngleReturnMutipleParameter lambda2 = new Calculator()::calculate;
            System.out.println(lambda1.test(10, 30));
        }
        
        private static class Calculator{
            public int calculate(int a, int b){
                return a > b ? a-b : b-a;
            }
        }
    }
    

3.3、构造方法的引用

  • 使用场景

    • 如果某一个函数式接口中定义的方法,仅仅是为了得到一个类的对象。此时我们就可以使用构造方法的引用,简化这个方法的实现。
  • 语法:

    • 类名::new
  • 注意事项:

    • 可以通过接口中的方法的参数,区分引用不同的构造方法
  • 实例

    public class Syntax3{
        private static class Person{
            String name;
            int age;
            public Person(){
                System.out.println("Person类的无参构造方法执行了");
            }
            public Person(String name){
                System.out.println("Person类的有参构造方法执行了");
                this.name = name;
            }
            public Person(String name, int age){
                System.out.println("Person类的有两个参数构造方法执行了");
                this.name = name;
                this.age = age;
            }
        }
    }
    
    public class Lambda2{
        
        @FunctionalInterface
    	private interface GetPersonWithNoneParameter {
    		Person get();
    	}
        
    	@FunctionalInterface
    	private interface GetPersonWithSingleParameter {
    		Person get(String name);
        }
        
    	@FunctionalInterface
    	private interface GetPersonwithMutipleParamleter {
    		Person get(String name,int age);
        }
        
        public static void main(String[] args) {
            // 1.使用lambda表达式,实现GetPersonWithNoneParameter接口
            GetPersonWithNoneParameter getPerson = Person::new;
            
            // 2.使用lambda表达式,实现GetPersonwithSingleParameter接口
            GetPersonwithSingleParameter getPerson2 = Person::new;
            
            // 3. 使用lambda表达式,实现GetPersonWithMutipleParameter接口
            GetPersonwithMutipleParameter getPerson3 = Person::new;
            
            
            Person person = getPerson.get();
        }
    }
    

3.4、对象方法的特殊引用

如果在使用 lambda 表达式,实现某些接口的时候,lambda表达式中包含了某一个对象,此时方法体重,直接使用这个对象调用它的某一个方法就可以完成整体的逻辑。其他的参数,可以作为调用方法的参数。此时,可以对这种实现进行简化。

public class Lambda3{
    pirvate String name;
    
    public void setName(String name){
        this.name = name;
    }
    
    public String getName(){
        retrun name;
    }
    
}
public class Syntax {
	public static void main(String[] args){
        
		//如果对于这个方法的实现逻辑,是为了获取到对象的名字
        GetField field = person -> person.getName();
		//对于对象方法的特殊引用
		GetField field = Person::getName;
		//如果对于这个方法的实现逻辑,是为了给对象的某些属性进行赋值
        SetField lambda = (p,n) -> p.setName(n);
        SetField lambda = Person::setName;
        
		//如果对于这个方法的实现逻辑,正好是参数对象的某一个方法
        ShowTest lambda2 = person -> person.show();
		ShowTest lambda2 = Person::show;
    }
}

interface ShowTest {
	void test(Person person);
}

interface SetField {
	void set(Person person, String name);
}

interface SetField {
	void set(Person person,String name);
}

interface GetField {
	String get(Person person);
}

class Person {
	private String name;
    
	public void setName(String name){
		this.name = name;
    }
    
	public String getName( {
		return name;
    }
	public void show() {
        
    }
}

4、Lambda表达式需要注意的问题

这里类似于局部内部类、匿名内部类,依然存在闭包的问题。

如果在lambda表达式中,使用到了局部变量,那么这个局部变量会被隐式的声明为 final。是一个常量,不能修改值。

版权声明:程序员胖胖胖虎阿 发表于 2022年11月3日 下午9:32。
转载请注明:Lambda 表达式 学习笔记 | 胖虎的工具箱-编程导航

相关文章

暂无评论

暂无评论...