Java学习笔记之 Lambda表达式

Posted JMW1407

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java学习笔记之 Lambda表达式相关的知识,希望对你有一定的参考价值。

Lambda表达式

1、引言

在 Java 8 以前,若我们想要把某些功能传递给某些方法,总要去写匿名类。以前注册事件监听器的写法与下面的示例代码就很像:

manager.addScheduleListener(new ScheduleListener() {
    @Override
    public void onSchedule(ScheduleEvent e) {        
        // Event listener implementation goes here...
    }
});

这里我们添加了一些自定义代码到 Schedule 监听器中,需要先定义匿名内部类,然后传递一些功能到 onSchedule 方法中。

正是 Java 在作为参数传递普通方法或功能的限制,Java 8 增加了一个全新语言级别的功能,称为 Lambda 表达式。

2、为什么 Java 需要 Lambda 表达式

在java中我们很容易将一个变量赋值,比如int a =0;int b=a;但是我们如何将一段代码和一个函数赋值给一个变量?这个变量应该是什么的类型?

Java 是面向对象语言,除了原始数据类型之处,Java 中的所有内容都是一个对象。而在函数式语言中,我们只需要给函数分配变量,并将这个函数作为参数传递给其它函数就可实现特定的功能。javascript 就是功能编程语言的典范(闭包)。

Lambda 表达式的加入,使得 Java 拥有了函数式编程的能力。在其它语言中,Lambda 表达式的类型是一个函数;但在 Java 中,Lambda 表达式被表示为对象,因此它们必须绑定到被称为功能接口的特定对象类型

Lambda可以让你简单的传递一个行为或者代码。可以把lambda看作是匿名函数,是没有声明名称的方法,但和匿名类一样,也可以作为参数传递给一个方法。

3、Lambda 表达式的结构

Lambda 表达式是一个匿名函数(对于 Java 而言并不很准确,但这里我们不纠结这个问题)。简单来说,这是一种没有声明的方法,即没有访问修饰符,返回值声明和名称。

在仅使用一次方法的地方特别有用,方法定义很短。它为我们节省了,如包含类声明和编写单独方法的工作。

Java 中的 Lambda 表达式通常使用语法是 (argument) -> (body),比如:
Lambda表达式的基本结构是:

  • 左侧:指定了 Lambda 表达式需要的参数列表
  • 右侧:指定了 Lambda 体,是抽象方法的实现逻辑,也即Lambda 表达式要执行的功能。
(arg1, arg2...) -> { body }

(type1 arg1, type2 arg2...) -> { body }

3.1、Lambda 表达式:语法

Lambda 表达式的结构:

  • Lambda 表达式可以具有零个,一个或多个参数。
  • 可以显式声明参数的类型,也可以由编译器自动从上下文推断参数的类型。例如 (int a) 与刚才相同 (a)。
  • 参数用小括号括起来,用逗号分隔。例如 (a, b)(int a, int b) 或 (String a, int b, float c)。 空括号用于表示一组空的参数。例如 () -> 42
  • 当有且仅有一个参数时,如果不显式指明类型,则不必使用小括号。例如 a -> return a*a。 Lambda表达式的正文可以包含零条,一条或多条语句。
  • 如果 Lambda 表达式的正文只有一条语句,则大括号可不用写,且表达式的返回值类型要与匿名函数的返回类型相同。
  • 如果 Lambda 表达式的正文有一条以上的语句必须包含在大括号(代码块)中,且表达式的返回值类型要与匿名函数的返回类型相同。


4、在哪里使用lambda?

4.1、函数式接口

函数式接口就是只定义一个抽象方法的接口。

  • 不包含object中的方法

这个需要说明一点,就是在Java中任何一个对象都来自Object 所有接口中自然会继承自Object中的方法,但在判断是否是函数式接口的时候要排除Object中的方法,下面举几个例子如下:

//这个是函数式接口
interface eat 
{  
   void eatFood();    
}
 
 
//这个不是是函数式接口
interface eat
{  
  default void eatFood()
  {
     System.out.println("hello");
  };    
}
 
 
 
//这个是一个函数式接口
interface eat 
{  
 void eatFood();    
 String toString();
}


Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。具体来说,Lambda表达式是函数是接口的具体实现的实例

通过匿名内部类也可以完成同样的事情,但是比较笨拙:需要提供一个实现,然后在直接内联将它的实现实例化。如下面的例子所示,可以清楚地看到lambda的简洁:

public class TestLambdaAndAnonymity {
    /**
     * 执行方法
     * @param r
     */
    public static void process(Runnable r){
        r.run();
    }
    
    public static void main(String[] args) {
        // 使用匿名类
        Runnable r1 = new Runnable() {
            @Override
            public void run() {
                System.out.println("this is r1");
            }
        };

        // 使用Lambda
        Runnable r2 = ()-> System.out.println("this is r2");
        
        process(r1);
        process(r2);
        // 直接将lambda作为参数传递
        process(()-> System.out.println("this is r3"));
    }
}

4.2、函数描述符?

函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。我们将这种抽象方法叫作函数描述符

举个例子:
Runnable 接口可以看作一个什么也不接受什么也不返回( void )的函数的签名,因为它只有一个叫作 run 的抽象方法,这个方法什么也不接受,什么也不返回( void )。

@FunctionalInterface
public interface Runnable {

    public abstract void run();
}

如上个小节的例子:“()-> System.out.println("this is r3")”, () -> void就是这个函数的签名, 代表了参数列表为空,且返回 void 的函数。这正是 Runnable 接口所代表的。

举另一个例子, (Apple,Apple) -> int 代表接受两个 Apple 作为参数且返回 int 的函数。

4.3、@FunctionalInterface 又是怎么回事?

如果你去看看新的Java API,会发现函数式接口带有 @FunctionalInterface 的标注。这个标注用于表示该接口会设计成一个函数式接口。如果你用 @FunctionalInterface 定义了一个接口,而它却不是函数式接口的话,编译器将返回一个提示原因的错误。如下:

    @FunctionalInterface
    private interface ITest{
        void test();

        void test1();
    }


表示该接口存在多个抽象方法。

@FunctionalInterface 不是必需的,但对于为此设计的接口而言,使用它是比较好的做法。

4.3、简单使用

下面我们写一段lambda简单的代码,找出指定的身份证号码,并打印出来。

最终的调用:

对于上面的代码实现,在我们调用excutor方法前,并不知道findName的实现方法,直到在最后把一个方法作为参数传入到excutor方法中

反思:函数式接口NameCheckInterface,是不是可以用来表示所有返回值为bool类型的,有两个形参(类型是passager 和String类型)的lambda表达式?

如果我们再配合泛型的话,是不是我们只需要定义一个通用的函数式接口?下面我们改写下代码:

public class SocketTest {
	public static void main(String[]args)throws Exception{
		List<Passager> passagerList = new ArrayList<>();
		  passagerList.add(new Passager("李四", "123456789"));
		  passagerList.add(new Passager("张三", "123456789"));
		  passagerList.add(new Passager("王二", "123456789"));
		  excutor(passagerList,(p,str)->p.getPassagerName().equals(str),str-> System.out.println(str));
 
	}	 
	private static void excutor(List<Passager> passagerList, NameCheckInterface<Boolean,Passager,String> checker, PrintInterface<String> printer) 
	{
	  for (Passager p : passagerList) {
	  	if (checker.findName(p,"李四")){
	  	  printer.printName(p.getPassagerNo());
	  	}
	  }
	}
 
}
 
class Passager{
	public String name;
	String passagerNo;
	public Passager(String name,String passagerNo){
		this.name=name;
		this.passagerNo=passagerNo;
	}
	public String getPassagerNo(){
		return passagerNo;
	}
	public String getPassagerName(){
		return name;
	}
}
 
 interface NameCheckInterface<T,T1,T2> 
{
   T findName(T1 passager,T2 name);
}
 
@FunctionalInterface
interface PrintInterface<T> 
{
  void printName(T name);
}

5、Lambda 表达式的使用

5.1、使用函数式接口

函数式接口定义且只定义了一个抽象方法。函数式接口很有用,因为抽象方法的签名可以描述Lambda表达式的签名。函数式接口的抽象方法的签名称为函数描述符。所以为了应用不同的Lambda表达式,你需要一套能够描述常见函数描述符的函数式接口。

Java 8的库设计师帮你在 java.util.function 包中引入了几个新的函数式接口。

Runnable r = () -> System.out.printf("say hello");//没有输入参数,也没有输出

Supplier<String> sp = () -> "hello";//只有输出消息,没有输入参数

Consumer<String> cp = r -> System.out.printf(r);//有一个输入参数,没有输出

Function<Integer, String> func = r -> String.valueOf(r);//有一个输入参数 有一个输出参数

BiFunction<Integer, Integer, String> biFunc = (a, b) -> String.valueOf(a + b);//有两个输入参数 有一个输出参数

BiConsumer<Integer, Integer> biCp = (a, b) -> System.out.printf(String.valueOf(a + b));//有两个输入参数 没有输出参数

PS:上面是基本的方法,其他的都是基于这几个扩展而来

如果上面的代码使用jdk中的函数式接口的话,就不用额外的定义NameCheckInterface和PrintInterface 接口了。根据上面的参数和返回值的形式,可以使用BiFunction和Consumer直接改写excutor方法:

private void excutor(List<passager> passagerList, BiFunction<passager,String,Boolean> checker, Consumer<String> printer) {
		for (passager p : passagerList) {
			if (checker.apply(p,"李四")){
				printer.accept(p.getPassagerNo());
			}
		}
	}

5.2、Consumer

java.util.function.Consumer<T> 定义了一个名叫 accept 的抽象方法,它接受泛型 T的对象,没有返回( void )。你如果需要访问类型 T 的对象,并对其执行某些操作,就可以使用这个接口。

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

如下例子,分别打印每个值:

public class TestConsumer {

    public static void main(String[] args) {
        forEach(Arrays.asList(1, 2, 3, 4, 5), (Integer i) -> System.out.println(i));
    }

    public static <T> void forEach(List<T> list, Consumer<T> c) {
        for (T t : list) {
            c.accept(t);
        }
    }
}

5.3、Function

java.util.function.Function<T, R> 接口定义了一个叫作 apply 的方法,它接受一个泛型 T 的对象,并返回一个泛型 R 的对象。如果你需要定义一个Lambda,将输入对象的信息映射到输出,就可以使用这个接口。

@FunctionalInterface
public interface Function<T, R> {

    /**
     * Applies this function to the given argument.
     *
     * @param t the function argument
     * @return the function result
     */
    R apply(T t);

如下所示,将String列表的长度映射到Integer列表:

    public static <T, R> List<R> map(List<T> list, Function<T, R> function) {
        List<R> results = new ArrayList<>();
        for (T t : list) {
            results.add(function.apply(t));
        }
        return results;
    }

    public static void main(String[] args) {
        List<Integer> map = map(
	        Arrays.asList("Tom", "Jerry",  "XiaoMing"), 
	        (String s) -> s.length()
        );
        System.out.println(map);
    }

6、方法引用

6.1、方法引用简介

方法引用是某些Lambda的快捷写法。让你可以重复使用现有的方法定义,并像Lambda一样传递它们。

当你需要使用方法引用时,目标引用放在分隔符 :: 前,方法的名称放在后面。如下面的例子:

Apple::getWeight

Apple::getWeight 就是引用了 Apple 类中定义的方法 getWeight,方法引用就是Lambda表达式 (Apple a) -> a.getWeight() 的快捷写法。

6.2、构建方法引用

方法引用主要有三类:

1)指向静态方法的方法引用

如下所示,指向静态方法parseInt。

        //指向任意实例类型方法
        Function<String, Integer> function = Integer::parseInt;
        Integer apply = function.apply("100");
        System.out.println(apply);

//lambda对比

Function<String, Integer> function = s -> parseInt(s);
      Integer apply = null;
      try {
        apply = function.apply("100");
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println(apply);

2)指向任意类型实例方法的方法引用

如下所示,指向String的length方法

        //指向任意实例类型方法
        Function<String,Integer> function1 = String::length;
        Integer hello_world = function1.apply("hello world");
        System.out.println(hello_world);

//lambda对比
Function<String,Integer> function1 = s -> s.length();
      Integer hello_world = null;
      try {
        hello_world = function1.apply("hello world");
      } catch (Exception e) {
        e.printStackTrace();
      }
      System.out.println(hello_world);

可以这样理解这句话:你在引用一个对象的方法,而这个对象本身是Lambda的一个参数。

3)指向现有实例对象的方法的方法引用

如下所示:

    @Data
    static class Student{
        private String name;
        private Integer age;

        public Student(String name, Integer age) {
            this.name = name;
            this.age = age;
        }
    }

        //指向现有对象的方法
        Student student = new Student("Jack",10);
        Supplier<String> supplier = student::getName;
        String s = supplier.get();
        System.out.println(s);

与第二条不同,这个方法引用的对象时外部已经存在的对象,如上述例子中的student。

6.3 、构造函数引用

对于一个现有构造函数,你可以利用它的名称和关键字 new 来创建它的一个引用:ClassName::new 。它的功能与指向静态方法的引用类似。

有如下实体类:

    @Data
    static class Student {
        private String name;
        private Integer age;
        private String address;

        public Student() {
        }

        public Student(String name) {
            this.name = name;
        }

        public Student(String name, Integer age) {
            this.name = name;
            this.age = age;
        }

        public Student(String name, Integer age, String address) {
            this.name = name;
            this.age = age;
            this.address = address;
        }
    }

实例:

    /**
     * description: 自定义三个参数的接口

     * @return:
     * @author: weirx
     * @time: 2021/10/19 18:03
     */
    interface ThreeParamsFunction<T, U, P, R> {
        R apply(T t, U u, P p);
    }

    public static void main(String[] args) {
        // 无构造参数 ()->new Student() 转化成 Student::new
        Supplier<Student> supplier = Student::new;
        Student student = supplier.get();
        // 一个构造参数 (name)->new Student(name) 转化成 Student::new
        Function<String, Student> function = Student::new;
        function.apply("JACK");
        // 两个构造参数 (name,age)->new Student(name,age) 转化成 Student::new
        BiFunction<String, Integer, Student> biFunction = Student::new;
        biFunction.apply("JACK", 10);
        // 三个构造参数,没有提供三个构造参数的,需要自己写个接口
        // (name,age,address)->new Student(name,age,address) 转化成 Student::new
        ThreeParamsFunction<String, Integer, String, Student> threeParamsFunction = Student::new;
        threeParamsFunction.apply("JACK", 10, "Haerbin");
    }

7、案例总结

使用List的sort方法对苹果进行排序:

void sort(Comparator<? super E> c)

根据上面的sort方法构成,我们需要一个Comparator对象对苹果进行比较。在没有java8之前,我们需要这样做,传递代码的方式:

7.1 、传递代码

package test2;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class LambdaTest {
  public static class Apple {
    public static void main(String[] args) {
      List<Apple> apples = Arrays.asList(
          new Apple(10),
          new Apple(19),
          new Apple(9),
          new Apple(22)
      );
      apples.sort(new CompareApple());
      for( Apple apple : apples) {
        System.out.println(apple.getWeight());
      }
    }

    private Integer weight;

    public Apple(Integer weight) {
      this.weight = weight;
    }

    public Integer getWeight() {
      return weight;
    }
  }

  public static class  CompareApple implements Comparator<Apple> {

    @Override
    public int compare(Apple o1, Apple o2) {
      return o1.getWeight().compareTo(o2.getWeight());
    }
  }
}

输出

没有java8: 9
没有java8: 10
没有java8: 19
没有java8: 22

7.2 、匿名类

package test2;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class LambdaTest {
  public static class Apple {
    public static void main(String[] args) {
      List<Apple> apples = Arrays.asList(
          new Apple(10),
          new Apple(19),
          new Apple(9),
          new Apple(22)
      );
      apples.sort(new Comparator<Apple>() {
        @Override
        public int compare(Apple o1, Apple o2) {
          return o1.getWeight().compareTo(o2.getWeight());
        }
      });
      for( Apple apple : apples) {
        System.out.println("匿名类: " + apple.getWeight());
      }
    }

    private Integer weight;

    public Apple(Integer weight) {
      this.weight = weight;
    }

    public Integer getWeight() {
      return weight;
    }
  }
}

输出:

匿名类: 9
匿名类: 10
匿名类: 19
匿名类: 22

7.3、 lambda表达式

package test2;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class LambdaTest {
  public static class Apple {
    public static void main(String[] args) {
      List<Apple> apples = Arrays.asList(
          new Apple(10),
          new Apple(19),
          new Apple(9),
          new Apple(22)
      );
      apples.sort((o1, o2) -> o1.getWeight().compareTo(o2.getWeight()));
      for( Apple apple : apples) {
        System.out.println("lambda表达式: " + apple.getWeight());
      }
    }

    private Integer weight;

    public Apple(Integer weight) {
      this.weight = weight;
    }

    public Integer getWeight() {
      return weight;
    }
  }
}

输出:

lambda表达式: 9
lambda表达式: 10
lambda表达式: 19
lambda表达式: 22

7.4 、方法引用

package test2;

import java.util.Arrays;
import java.util.Comparator;
import java.util.List;

public class LambdaTest {
  public static class Apple {
    public static void main(String[] args) {
      List<Apple> apples = Arrays.asList(
          new Apple(10),
          new Apple(19),
          new Apple(9),
          new Apple(22)
      );
      apples.sort(Comparator.comparing(Apple::getWeight));
      for( Apple apple : apples) {
        System.out.println("方法引用: " + apple.getWeight());
      }
    }

    private Integer weight;

    public Apple(Integer weight) {
      this.weight = weight;
    }

    public Integer getWeight() {
      return weight;
    }
  }
}

输出

方法引用: 9
方法引用: 10
方法引用: 19
方法引用: 22

参考

1、Java 8 Lambda 表达式详解
2、java8(二)lambda表达式手把手学习
3、Java SE8 之 Lambda 表达式详解
4、Java8 lambda表达式详解
5、java8 之如何使用函数引用
6、【java8新特性】方法引用

以上是关于Java学习笔记之 Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章

Python学习笔记之三lambda表达式用法小结

Java多线程学习笔记— “Lambda表达式”

Java多线程学习笔记— “Lambda表达式”

Java学习笔记3.9.1 Lambda表达式 - Lambda表达式入门

Java重温学习笔记,Java8新特性:Java Lambda 表达式

Java之函数编程