Java8 新特性之 Lambda 表达式

Java8之前传递一个代码段并不容易,不能直接传递代码段。Java是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法能包含所需的代码。Lamabd是数学中的一个函数,Java中使用方法来代替函数,方法总是作为类或对象的一部分存在的。可以把Lamabda看做是一个匿名方法,拥有更简洁的语法。

1. 基本语法

语法:(参数列表) -> {语句;}

Lamabda表达式由参数列表和一个Lamabda体组成,通过箭头连接。

(1)完整lambda表达式。参数有括号、类型。表达式有大括号,有返回值的语句需要加return关键字。

java 复制代码
(String first, String second) -> {return first.length() - second.length();}

(2)即使lambda表达式没有参数,也需要提供一个空括号。表达式没有返回值不写return

java 复制代码
() -> { System.out.println("Hello"); }

(3)如果可以推导出参数的类型,可以不写类型。不写类型的话所有参数都不写类型。写类型的话所有参数都要写类型。

java 复制代码
(first, second) -> {return first.length() - second.length();}

(4)方法只有一个参数,而且这个参数类型可以推导,可以省略小括号和参数类型。

java 复制代码
x -> {System.out.println(x);}

(5)如果Lamabda体只有一条语句,大括号可以省略,return关键字也可以省略。没有大括号不能添加return关键字。有大括号不能省略return关键字(有返回值的情况下)。

java 复制代码
x -> x+1;  或者  x -> {return x+1;}

2. 函数式接口

只定义了一个抽象方法的接口,称为函数式接口

Java有很多封装代码块的接口,如Comparator接口,Runnable接口,都属于函数式接口 ,在需要这种接口的对象时,就可以提供一个lambda表达式。实际上在Java中,lambda表达式必须实现一个函数式接口。函数式接口就是为Lamabda表达式准备的。

2.1. 自定义函数式接口

java 复制代码
@FunctionalInterface//注解,声明接口为函数式接口。
public interface Adder{
    int add(int x, int y);
}

2.2. 基本函数式接口

java.util.function包中定义了一些基本的函数式接口,如PredicateConsumerFunctionSupplier

2.2.1. Predicate

java 复制代码
@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Predicate<T>接口定义了一个抽象方法test(T t),接收一个T类型的参数,返回一个布尔值。当需要一个涉及类型T的布尔表达式时,可以使用这个接口。示例代码:

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;

/**
 * @date 2019/10/29 23:15
 * @auther wangbo
 */
public class TestPredicate {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123", "1234", "12345");
        //传递 predicate 条件
        List<String> result = filter(list, x -> x.length() > 4);
        System.out.println(result);
    }

    public static <T>List<T> filter(List<T> list, Predicate<T> predicate){
        List<T> result = new ArrayList<>();
        //遍历list参数列表,把符合 predicate 条件的元素存储到result中
        for (T t : list) {
            if (predicate.test(t)) {
                result.add(t);
            }
        }
        return result;
    }
}

2.2.2. Consumer

java 复制代码
@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

Consumer<T>接口定义了一个抽象方法accept(T t),接收一个T类型的参数,没有返回值。当需要访问类型T的对象,对该对象做一些操作,就可以使用这个接口。在Collection集合和Map集合中都有forEach(Consumer)方法。示例代码:

java 复制代码
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @date 2019/10/29 23:33
 * @auther wangbo
 */
public class TestConsumer {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123", "1234", "12345");
        list.forEach(s -> System.out.println(s));

        Map<String, Integer> map = new HashMap<>();
        map.put("张三", 10);
        map.put("李四", 20);
        map.put("王五", 30);
        map.forEach((k, v) -> System.out.println(k + "->" + v));
    }
}

2.2.3. Function

java 复制代码
@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Function<T,R>接口定义了apply(T t)方法,接收一个T类型的参数对象,返回一个R类型的数据,如果需要定义一个Lambda,将一个输入对象T的信息加工后映射为输出对象R,就可以使用这个接口。示例代码:

java 复制代码
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.function.Function;

/**
 * @date 2019/10/29 23:45
 * @auther wangbo
 */
public class TestFunction {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("123", "1234", "12345");
        System.out.println(list);
        //将元素长度存储到result
        List<Integer> result = map(list, x -> x.length());
        System.out.println(result);
    }

    //把一个list中的元素映射到另一个list中
    public static <T,R>List<R> map(List<T> list, Function<T,R> function){
        List<R> result = new ArrayList<>();
        for (T t : list){
            result.add(function.apply(t));
        }
        return result;
    }
}

2.3. 基本数据类型函数式接口

泛型只能绑定引用数据类型,不能使用基本数据类型。Java8中函数式接口为基本数据类型提供了对应的接口,可以避免在进行输入输出基本数据类型时频繁进行装箱、拆箱操作。

一般来说,在针对专门的基本类型数据的函数式接口名称前面加上对应的基本类型前缀,如IntPredicateIntConsumerIntFunction等。示例代码:

java 复制代码
public static void main(String[] args) {
    IntPredicate evenNumbers = (int x) -> x%2 == 0;
    System.out.println(evenNumbers.test(10));//true
    System.out.println(evenNumbers.test(11));//false
}

3. 捕获 Lambda

Lambda表达式可以使用外层作用域中定义的变量,比如成员变量,局部变量,称为捕获Lambda

  • Lambda表达式中可以使用成员变量,静态方法中只能使用静态成员变量。
  • Lambda表达式中可以使用局部变量,局部变量必须是final修饰,或者是事实上的final变量。
java 复制代码
import java.util.function.IntUnaryOperator;

/**
 * @date 2019/10/30 0:19
 * @auther wangbo
 */
public class Test {
    int xx = 123;//实例变量
    static int yy = 456;//静态变量

    public static void main(String[] args) {
        //在Lambda表达式中可以使用成员变量,当前静态方法中只能使用静态变量。
        IntUnaryOperator operator = i -> i + yy;
        System.out.println(operator.applyAsInt(100));

        //在Lambda表达式中使用局部变量,局部变量必须是final修饰,或者是事实上的final。
        int zz = 789;//局部变量
        IntUnaryOperator operator1 = i -> i + zz;//虽然zz没有用final修饰,但是它是事实上的final,后面无法修改zz的值。
        System.out.println(operator1.applyAsInt(100));
        //zz = 11;//对zz进行修改,则上面的lambda表达式报语法错误。

        final int ff = 111;//final修饰的局部变量
        IntUnaryOperator operator2 = i -> i + ff;
        System.out.println(operator2.applyAsInt(100));
    }
}

4. 方法引用

方法引用可以让你重复使用现有的方法定义,并像Lambda一样传递它们。方法引用可以看做是仅仅调用特定方法的Lambda表达式的一种便捷写法。类似于Lambda表达式,方法引用不能独立存在,总是会转换为函数式接口的实例。

4.1. 语法

需要使用方法引用时,目标引用放在分隔符::前面,方法名放在::后面。注意,只需要方法名,不需要小括号。

::操作符分隔对象(或类名)和方法名,主要有三种情况:

  • 对象::实例方法
  • Class::静态方法
  • Class::实例方法

4.1.1. 总述

前两种情况,方法引用等价于提供方法参数的lambda表达式,例如:

java 复制代码
System.out::prinlth 等价于 x -> System.out.println(x)
Math::pow 等价于 (x, y) -> Math.pow(x, y)

第三种情况,第一个参数为方法的调用者,第二个参数为方法的参数。例如:

java 复制代码
String::compareToIgnoreCase 等价于 (x, y) -> x.compareToIgnoreCase(y)

方法引用中可以使用thissuper,属于上面的第一种情况,例如:

java 复制代码
this::equals 等价于 x -> this.equals(x)
super::equals 等价于 x -> super.equals(x)

4.1.2. 示例

java 复制代码
List list = new ArrayList();
list.forEach(x -> System.out.println(x));
list.forEach(System.out::println);

List<Student> list1 = new ArrayList<>();
list1.forEach((Student stu) -> stu.getScore());
list1.forEach(Student::getScore);
java 复制代码
() -> Thread.currentThread().dumpStack()
Thread.currentThread()::dumpStack

(Str, i) -> Str.substring(i)
String::substring

4.2. 常见方法引用

方法引用主要有三类:

4.2.1. 指向静态方法的方法引用

Lambda 表达式:(args) -> ClassName.staticMethod(args)

方法引用:ClassName::staticMethod

java 复制代码
Integer[] data = {21,90,34,76};
Arrays.sort(data, Integer::compare);//引用静态方法
System.out.println(Arrays.toString(data));

4.2.2. 指向任意类型的实例方法的引用

Lambda 表达式:(args0, args1) -> args0.instanceMethod(args1) args0ClassName类型的一个对象。

方法引用:ClassName::instanceMethod

java 复制代码
List<String> list = Arrays.asList("ccc", "bbb", "aaa");
list.sort(String::compareTo);//引用实例方法
System.out.println(list);

4.2.3. 指向现有对象的实例方法的引用

Lambda表达式:(args) -> obj.instanceMethod(args)

方法引用:obj::instanceMethod

java 复制代码
list.forEach(System.out::println);

4.3. 构造方法引用

对于一个现有的构造方法,可以使用类名和关键字new来创建一个构造方法的引用:ClassName::new

java 复制代码
package com.wangbo.cto.lambda;

import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;

/**
 * @date 2019/11/14 23:08
 * @auther wangbo
 */
public class Test1 {
    public static void main(String[] args) {

        //引用无参数构造方法
        Supplier<Person> supplier = Person::new;
        Person p1 = supplier.get();
        System.out.println(p1);

        //引用有一个参数构造方法
        Function<String, Person> function = Person::new;
        Person p2 = function.apply("wangbo");
        System.out.println(p2);

        //应用有两个参数的构造方法
        BiFunction<String, Integer, Person> biFunction = Person::new;
        Person p3 = biFunction.apply("wangbo", 28);
        System.out.println(p3);

        //如果引用有三个参数及三个以上参数的构造方法,需要自定义匹配的函数式接口
        TriFunction<String, Integer, String, Person> triFunction = Person::new;
        Person p4 = triFunction.myMethod("zhangsan", 90, "男");
        System.out.println(p4);
    }
}
java 复制代码
/**
 * 自定义函数式接口
 */
@FunctionalInterface
public interface TriFunction<T, U, V, R>{
    R myMethod(T t, U u, V v);
}
java 复制代码
public class Person{
    String name;
    Integer age;
    String gender;

    public Person(){}

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

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

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

    @Override
    public String toString() {
        return "Person{" +
                "name=" + name +
                ", age=" + age +
                ", gender="+ gender +
                "}";
    }
}

运行结果:

java 复制代码
Person{name=null, age=null, gender=null}
Person{name=wangbo, age=null, gender=null}
Person{name=wangbo, age=28, gender=null}
Person{name=zhangsan, age=90, gender=男}

5. 综合示例

java 复制代码
package com.wangbo.cto.lambda;

import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;

/**
 * @date 2019/11/15 0:24
 * @auther wangbo
 */
public class LambdaTest {
    public static void main(String[] args) {
        //定义List存储Student
        List<Student> list = new ArrayList<>();
        list.add(new Student("zhangsan", 10));
        list.add(new Student("lisi", 9));
        list.add(new Student("wangwu", 13));

        //使用匿名内部类排序(按名字升序)
        list.sort(new Comparator<Student>() {
            @Override
            public int compare(Student o1, Student o2) {
                return o1.name.compareTo(o2.name);
            }
        });
        System.out.println(list);

        //使用lambda表达式(按名字降序)
        list.sort((s1, s2) -> s2.name.compareTo(s1.name));
        System.out.println(list);

        //使用Comparator接口中的comparing静态方法,可以返回一个Comparator比较器(按名字升序)
        list.sort(Comparator.comparing(student -> student.name));
        System.out.println(list);

        //方法引用(按分数升序)
        list.sort(Comparator.comparing(Student::getAge));
        System.out.println(list);
        
    }

}
java 复制代码
public class Student{
    String name;
    Integer age;

    public Integer getAge() {
        return age;
    }

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

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

运行结果:

java 复制代码
[Student{name='lisi', age=9}, Student{name='wangwu', age=13}, Student{name='zhangsan', age=10}]
[Student{name='zhangsan', age=10}, Student{name='wangwu', age=13}, Student{name='lisi', age=9}]
[Student{name='lisi', age=9}, Student{name='wangwu', age=13}, Student{name='zhangsan', age=10}]
[Student{name='lisi', age=9}, Student{name='zhangsan', age=10}, Student{name='wangwu', age=13}]
相关推荐
2401_857439699 分钟前
SpringBoot框架在资产管理中的应用
java·spring boot·后端
怀旧66611 分钟前
spring boot 项目配置https服务
java·spring boot·后端·学习·个人开发·1024程序员节
李老头探索13 分钟前
Java面试之Java中实现多线程有几种方法
java·开发语言·面试
芒果披萨18 分钟前
Filter和Listener
java·filter
qq_49244844622 分钟前
Java实现App自动化(Appium Demo)
java
阿华的代码王国31 分钟前
【SpringMVC】——Cookie和Session机制
java·后端·spring·cookie·session·会话
找了一圈尾巴1 小时前
前后端交互通用排序策略
java·交互
哎呦没3 小时前
SpringBoot框架下的资产管理自动化
java·spring boot·后端
m0_571957585 小时前
Java | Leetcode Java题解之第543题二叉树的直径
java·leetcode·题解
魔道不误砍柴功7 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python