Lambda表达式和方法引用

Posted

tags:

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

参考技术A 基本语法:(parameters) -> expression或(parameters) -> statements;
左侧为参数,右侧为函数式接口方法的实现

lambda表达式相当于把函数作为参数传给方法,python中就可以将函数做参数传给另个函数。果然语言都在趋同。
需要函数式接口支持

当lambda体已有方法实现时可以用::进一步简化
前提:已实现的方法必须和函数式接口的抽象方法参数和返回值一致
JDK8中有双冒号的用法,就是把方法当做参数传到stream内部,使stream的每个元素都传入到该方法里面执行一下
基本语法:类名::方法名
静态方法引用(static method)语法:classname::methodname 例如:Person::getAge
对象的实例方法引用语法:instancename::methodname 例如:System.out::println
对象的超类方法引用语法: super::methodname
类构造器引用语法: classname::new 例如:ArrayList::new
数组构造器引用语法: typename[]::new 例如: String[]:new

Java8 新特性 -- Lambda表达式:函数式接口方法的默认实现和静态方法方法引用注解类型推测Optional类Stream类调用JavaScriptBase64

文章目录

1. Lambda表达式

Lambda表达式是一个匿名函数(指的是没有函数名的函数),它基于数学中的λ演算得名,直接对应于其中的Lambda抽象。


1.1 Lambda表达式语法

Lambda表达式允许把函数作为一个方法的参数。Lambda表达式的基本语法如下所示:

(parameters) -> expression
// 或者
(parameters) ->  expression 

1.2 Lambda表达式示例

Arrays.asList(1, 3, 5, 7, 9).forEach(item -> System.out.println(item));

运行结果:

以上这种写法中,i的类型由编译器推测出来的,当然,也可以显式地指定类型,如下例所示:

Arrays.asList(1, 3, 5, 7, 9).forEach((Integer item) -> System.out.println(item));

Java8以前,Java语言通过匿名函数的方法来代替Lambda表达式。

对于列表的排序,如果列表里面存放的是自定义的类,那么通常需要指定自定义的排序方法,传统的写法如下所示:

Test.java

package com.tian;

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

/**
 * @author CodeJiao
 */
class Person 
    private String name;
    private int age;

    public Person(String name, int age) 
        this.name = name;
        this.age = age;
    

    public String getName() 
        return name;
    

    public int getAge() 
        return age;
    

    @Override
    public String toString() 
        return "Person" +
                "name='" + name + '\\'' +
                ", age=" + age +
                '';
    


public class Test 
    public static void main(String[] args) 
        Person[] peoples = new Person("Jack", 20), new Person("Rose", 18);

        // 自定义排序方法, 通过年龄进行排序
        Arrays.sort(peoples, new Comparator<Person>() 
            @Override
            public int compare(Person person1, Person person2) 
                // 根据年龄升序排序
                return person1.getAge() - person2.getAge();
            
        );

        for (Person people : peoples) 
            System.out.println(people);
        
    

运行结果:


采用Lambda表达式后,写法如下所示:

        // 自定义排序方法, 通过年龄进行排序
        Arrays.sort(peoples, (person1, person2) -> 
            // 根据年龄升序排序
            return person1.getAge() - person2.getAge();
        );

或者

        // 自定义排序方法, 通过年龄进行排序
        Arrays.sort(peoples, (Person person1, Person person2) -> 
            // 根据年龄升序排序
            return person1.getAge() - person2.getAge();
        );

1.3 说明:函数式接口

Lambda表达式是通过函数式接口(只有一个方法的普通接口)来实现的。函数式接口可以被隐式地转换为Lambda表达式。为了与普通的接口区分开(普通接口中可能会有多个方法),JDK1.8新增加了一种特殊的注解@FunctionalInterface。下面给出一个函数式接口的定义:

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface 

2. 方法的默认实现和静态方法

JDK1.8通过使用关键字default可以给接口中的方法添加默认实现
此外,接口中还可以定义静态方法,示例代码如下所示:

public interface Test 
    void method1();

    default void method2() 
        System.out.println("我是接口的默认实现方法");
    

    static void method3() 
        System.out.println("我是接口中的静态方法");
    

那么,为什么要引入接口中方法的默认实现呢?

这样做的最重要的一个目的就是为了实现接口升级。在原有的设计中,如果想要升级接口,例如给接口中添加一个新的方法,那么会导致所有实现这个接口的类都需要被修改,这给 Java语言已有的一些框架进行升级带来了很大的麻烦。如果接口能支持默认方法的实现,那么可以给这些类库的升级带来许多便利。例如,为了支持Lambda表达式,Collection中引入了foreach方法,可以通过这个语法增加默认的实现,从而降低了对这个接口进行升级的代价,不需要所有实现这个接口的类进行修改。


3. 方法引用

方法引用指的是可以直接引用Java类或对象的方法。它可以被看成是一种更加简洁易懂的Lambda表达式,使用方法引用后,上例中的排序代码就可以使用下面更加简洁的方式来编写:

原来:

        // 自定义排序方法, 通过年龄进行排序
        Arrays.sort(peoples, (person1, person2) -> 
            // 根据年龄升序排序
            return person1.getAge() - person2.getAge();
        );

现在:

        // 自定义排序方法, 通过年龄进行排序
        Arrays.sort(peoples, Comparator.comparing(Person::getAge));

方法引用共有下面4种形式:

  • 引用构造方法:ClassName::new
  • 引用类静态方法:ClassName::methodName
  • 引用特定类的任意对象方法:ClassName::methodName
  • 引用某个对象的方法:instanceName::methodName

3.1 方法引用示例

Test.java

package com.tian;

import java.util.Arrays;
import java.util.Comparator;
import java.util.function.Supplier;

/**
 * @author CodeJiao
 */
class Person 
    private String name;
    private int age;

    public Person() 
    

    public Person(String name, int age) 
        this.name = name;
        this.age = age;
    

    public static Person getInstance(final Supplier<Person> supplier) 
        return supplier.get();
    

    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;
    

    public static int compareByAge(Person a, Person b) 
        return b.age - a.age;
    

    @Override
    public String toString() 
        return "Person" +
                "name='" + name + '\\'' +
                ", age=" + age +
                '';
    


class CompareProvider 
    public int compareByAge(Person a, Person b) 
        return a.getAge() - b.getAge();
    


public class Test 
    public static void main(String[] args) 
        // 引用构造方法
        Person p1 = Person.getInstance(Person::new);
        p1.setAge(22);
        System.out.println("测试引用构造方法:" + p1.getAge());
        Person[] peoples = new Person("Jack", 20), new Person("Rose", 18);
        // 引用特定类的任意对象方法
        Arrays.sort(peoples, Comparator.comparing(Person::getAge));
        System.out.println("测试引用特定类的任意对象方法:");
        for (Person people : peoples) 
            System.out.println(people);
        
        // 引用静态方法
        Arrays.sort(peoples, Person::compareByAge);
        System.out.println("测试引用静态方法:");
        for (Person people : peoples) 
            System.out.println(people);
        
        // 引用某个对象的方法
        Arrays.sort(peoples, new CompareProvider()::compareByAge);
        System.out.println("测试引用某个对象的方法:");
        for (Person people : peoples) 
            System.out.println(people);
        
    

运行结果:


4. 注解(Annotation)

  • 1)JDK1.5中引入了注解机制,注解为开发人员在代码中添加信息提供了一种形式化的方法,它使得开发人员可以在某个时刻方便地使用这些数据(通过解析注解来使用这些数据)。注解的语法比较简单,除了@符号的使用以外,它基本上与Java语言的语法一致,Java语言内置了三种注解方式,它们分别是@Override(表示当前方法是覆盖父类的方法)、@Deprecated(表示当前元素是不赞成使用的)、@SuppressWarnings(表示关闭一些不当的编译器警告信息)。需要注意的是,它们都定义在 java.lang 包中。
  • 2) JDK1.8对注解进行了扩展。使得注解被使用的范围更广,例如可以给局部变量、泛型、方法异常等提供注解。

5. 类型推测

JDK1.8加强了类型推测机制,这种机制可以使得代码更为简洁,假如有如下类的定义。

在调用的时候,可以使用下面的代码:

在 Java 7的时候,这种写法将会产生编译错误,Java 7中的正确写法如下所示:
同理,在调用cons方法的时候的写法为:

Java 7 的时候需要显式地指定类型:List.cons(5,List.<Integer>nil());


6. 新增Optional类

在使用Java语言进行编程的时候,经常需要使用大量的代码来处理空指针异常,而这种操作往往会降低程序的可读性。JDK1.8引入了Optional类来处理空指针的情况,从而增强了代码的可读性,下面给出一个简单的例子:

示例代码:

package com.tian;

import java.util.Optional;

public class Test1 
    public static void main(String[] args) 
        Optional<String> s1 = Optional.of("Hello");

        // 判断是否有值
        if (s1.isPresent()) 
            System.out.println(s1.get());
        
        Optional<String> s2 = Optional.ofNullable(null);
        
        // 判断是否有值
        if (s2.isPresent()) 
            System.out.println(s2.get());
        
    

运行结果:


7. 新增Stream类

JDK1.8 新增加了Stream类,从而把函数式编程的风格引入到Java语言中,StreamAPI提供了非常强大的功能,使用Stream后,可以写出更加强大、更加简洁的代码(例如可以代替循环控制语句)。示例代码如下所示:

package com.tian;

import java.util.*;
import java.util.stream.Collectors;

class Student 
    private String name;
    private Integer age;

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

    public String getName() 
        return name;
    

    public Integer getAge() 
        return age;
    

    @Override
    public String toString() 
        return "Student" +
                "name='" + name + '\\'' +
                ", age=" + age +
                '';
    


public class Test2 
    public static void main(String[] args) 
        ArrayList<Student> students = new ArrayList<>();
        // 初始化students
        students.add(new Student("张有余", 10));
        students.add(new Student("雷磊", 13));
        students.add(new Student("侯家领", 10));
        students.add(new Student("徐曦", 15));

        System.out.println("找出年龄为10的第一个学生");
        Optional<Student> s = students.stream().filter(student ->
                student.getAge() == 10
        ).findFirst();
        if (s.isPresent()) 
            System.out.println(s.toString());
        

        System.out.println("找出年龄为10的所有学生");
        List<Student> listStu = students.stream().filter(student -> student.getAge() == 10).collect(Collectors.toList());
        listStu.forEach(stu -> 
            System.out.println(stu.toString());
        );

        System.out.println("对学生按照年龄分组");
        Map<Integer, List<Student>> map = students.stream().collect(Collectors.groupingBy(Student::getAge));
        Iterator<Map.Entry<Integer, List<Student>>> iterator = map.entrySet().iterator();
        while (iterator.hasNext()) 
            Map.Entry<Integer, List<Student>> entry = iterator.next();
            Integer age = entry.getKey();
            System.out.println("age: " + age);
            List<Student> group = entry.getValue();
            for (Student student : group) 
                System.out.println(student.getName() + " ");
            
        
    

运行结果:

此外,Stream类还提供了parallelmapreduce等方法,这些方法用

以上是关于Lambda表达式和方法引用的主要内容,如果未能解决你的问题,请参考以下文章

java8新特性→方法和构造函数引用:替代Lambda表达式

Lambda 表达式与方法引用

Lambda表达式之方法的引用

Java8 新特性 -- Lambda表达式:函数式接口方法的默认实现和静态方法方法引用注解类型推测Optional类Stream类调用JavaScriptBase64

Java8新特性:Lambda表达式函数式接口以及方法引用

委托Lambda表达式和事件