zoukankan      html  css  js  c++  java
  • Lambda表达式

    摘要

    • 何为函数式接口?
    • 什么是lambda表达式,lambda表达式的本质;
    • 函数式接口与lambda表达式的联系:lambda是实现函数式接口的一个快捷方式,可以作为函数式接口的一个实例;
    • 常用Java8内置的函数式接口 Function、Predicate、Consumer 和 Supplier 介绍;

    一、函数式接口

    何为函数式接口?

    函数式接口也是 java interface 的一种,但还需要满足:

    1. 一个函数式接口只有一个抽象方法(SAM,single abstract method);
    2. Object 类中的 public abstract method 不会被视为单一的抽象方法;
    3. 函数式接口可以有默认方法和静态方法;
    4. 函数式接口可以用@FunctionalInterface 注解进行修饰。

    满足这些条件的 interface,就可以被视为函数式接口。例如,java8中的Comparator接口:

    @FunctionalInterface
    public interface Comparator<T> {
        /**
         * single abstract method
         * @since 1.8
         */
        int compare(T o1, T o2);
    
        /**
         * Object 类中的 public abstract method 
         * @since 1.8
         */
        boolean equals(Object obj);
    
        /**
         * 默认方法
         * @since 1.8
         */
        default Comparator<T> reversed() {
            return Collections.reverseOrder(this);
        }
    
    	......
        
    	/**
         * 静态方法
         * @since 1.8
         */
        public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
            return Collections.reverseOrder();
        }
    
    	......
    	
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    函数式接口有什么用?

    一句话,函数式接口带给我们最大的好处就是:可以使用极简的lambda表达式实例化接口。为什么这么说呢?我们或多或少使用过一些只有一个抽象方法的接口,比如 Runnable, ActionListener, Comparator …。比如,我们要用Comparator实现排序算法,我们的处理方式通常无外乎两种:

    1. 规规矩矩的写一个实现Comparator接口的java类去封装排序逻辑。若业务需要多种排序方式,那就得写多个类提供多种实现,而这些实现往往只需使用一次。。。。
    2. 另外一种聪明些的做法无外乎就是在需要的地方搞个匿名内部类去扛了。比如:
    class Test { 
        public static void main(String args[]) { 
            List<Person> persons = new ArrayList<Person>();
            Collections.sort(persons, new Comparator<Person>(){
                @Override
                public int compare(Person o1, Person o2) {
                    return Integer.compare(o1.getAge(), o2.getAge());
                }
            });
        } 
    } 
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    匿名内部类实现的代码量没有多到哪里去,结构也还算清晰。不过如今脚本语言横行无阻,无不以其简洁的语法为杀手锏,俘获程序员的欢心。jdk开发者们应该是意识到了这一点,我们从上面Comparator的实现就可以得到验证。该接口在jdk8的实现增加了FunctionalInterface注解,代表Comparator是一个函数式接口,使用者可放心的通过lambda表达式来实例化。那我们来看看使用lambda实例化所需编写的代码:

    Comparator<Person> comparator = (p1, p2) -> Integer.compare(p1.getAge(), p2.getAge());
    
    • 1

    就这么简单,->前面的()是Comparator接口中compare方法的参数列表,->后面则是run方法的方法体。

    @FunctionalInterface 使用场景

    我们知道,一个接口只要满足只有一个抽象方法的条件,即可以当成函数式接口使用,有没有@FunctionalInterface都无所谓。但是jdk定义了这个注解肯定是有原因的,对于开发者,该注解的使用一定要三思而后续行。

    如果使用了此注解,再往接口中新增抽象方法,编译器就会报错,编译不通过。换句话说,@FunctionalInterface就是一个承诺,承诺该接口世世代代都只会存在这一个抽象方法。因此,凡是使用了这个注解的接口,开发者可放心大胆的使用lambda来实例化。当然误用@FunctionalInterface带来的后果也是极其惨重的:如果哪天你把这个注解去掉,再加一个抽象方法,则所有使用lambda实例化该接口的客户端代码将全部编译错误。。。

    特别地,当某接口只有一个抽象方法,但没有增加@FunctionalInterface,则代表人家没有承诺该接口未来不增加抽象方法,所以建议不要用lambda来实例化,还是老老实实的用以前的方式比较稳妥。


    二、lambda表达式

    什么是lambda?

    lambda表达式是实现函数式接口的一个快捷方式。下面这段代码是最直观的体现:

    Runnable task = () -> System.out.println("i am coming...");
    
    • 1

    =号的前面是变量的声明,后面是一个lambda表达式。我们直接将lambda表达式赋值为Runnable类型的task变量,就意味着:lambda表达式是实现函数式接口的一个快捷方式。理解这一点是至关重要的。

    lambda表达式语法

    lambda表达式语法可抽象表示为:

    (Type1 param1, Type2 param2, ..., TypeN paramN) -> {
         statment1;
         statment2;
         ...
         return statmentN;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    以Predicate判断是否为成年人为例:

    Predicate<Person> predicate = (Person person) -> {
        Integer age = person.getAge();
        return age >= 18;
    };
    
    • 1
    • 2
    • 3
    • 4

    上面的lambda表达式语法抽象表示是比较官方的,和我们平时所写的lambda表达式比起来要繁琐一些。别着急,下面会一点一点地演示lambda表达式的简化过程。

    1. 参数类型省略

    绝大多数情况,编译器都可以从上下文环境中推断出lambda表达式的参数类型。这样,lambda表达式就变成了:

    (param1, param2, ..., paramN) -> {
        statment1;
        statment2;
        ...
        return statmentN;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    上面的例子也可简化为:

    Predicate<Person> predicate = (person) -> {
        Integer age = person.getAge();
        return age >= 18;
    };
    
    • 1
    • 2
    • 3
    • 4
    1. 当lambda表达式的参数个数只有一个,可以省略小括号。

    lambda表达式简写为:

    param -> {
        statment1;
        statment2;
        ...
        return statmentN;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    对应的例子简化为:

    Predicate<Person> predicate = person -> {
        Integer age = person.getAge();
        return age >= 18;
    };
    
    • 1
    • 2
    • 3
    • 4
    1. 当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号。

    其他省就省了,为啥return关键字也可以被省略呢?原因是,编译器会认为:既然只有一个语句,那么这个语句执行的结果就应该是返回值,所以return也就不需要了。因此,lambda表达式简化为:

    param -> statment;
    
    • 1

    所以例子又可简化成:

    Predicate<Person> predicate = person -> person.getAge() >= 18
    
    • 1

    现在这个样子,才是我们熟悉的模样。。。

    Lambda表达式眼中的外部世界

    我们前面所有的介绍,感觉上lambda表达式像一个闭关锁国的家伙:可以访问给它传递的参数,也能自己内部定义变量,但是却从来没看到其访问它外部的变量。是不是lambda表达式不能访问其外部变量?我们可以这样想:lambda表达式其实是快速创建函数式接口实例的语法糖,匿名内部类所实现的实例都可以访问接口外部变量,那么lambda表达式肯定也是可以。事实上,不但可以,在java8中还做了一个小小的升级。看下面例子:

    String[] array = {"a", "b", "c"};
    for (Integer i : Lists.newArrayList(1, 2, 3)) {
        System.out.println(i);
        Stream.of(array).map(item -> Strings.padEnd(item, i, '*')).forEach(s -> System.out.println(s));
    }/*** output
    	a
    	b
    	c
    	a*
    	b*
    	c*
    	a**
    	b**
    	c**
    	*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    上面的这个例子中,map中的lambda表达式访问外部变量Integer i,并且外部变量 i 还可以不用显式声明为final。事实上,lambda表达式同匿名内部类一样,访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)。看下面的例子:

    String[] array = {"a", "b", "c"};
    for (int i = 1; i < 4; i++) {
    	System.out.println(i);
    	Stream.of(array).map(item -> Strings.padEnd(item, i, '*')).forEach(System.out::println);
    }/*** output
    	Error:(124, 63) java: 从lambda 表达式引用的本地变量必须是最终变量或实际上的最终变量
    	*/
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    上面的代码会报编译错误,因为变量i被lambda表达式引用,所以编译器会把其当成final来处理,但如代码所示,变量i是会自增变化的(ps:大家可以想象问什么上一个例子不报错,而这个报错)。细心的读者肯定会发现:以前java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。Bingo,java8对这个限制做了优化,外部变量可以不用显式使用final修饰,但编译器会自动把它当成final来处理。

    lambda表达式与匿名类

    lambda表达式与匿名类的异同集中体现在三点上:

    1. lambda就是为了优化匿名内部类而生,lambda要比匿名类简洁的多得多;
    2. lambda仅适用于函数式接口,匿名类不受限;
    3. 在匿名类内部,this关键字指向该抽象类实例,而lambda内部this指向闭包实例。如果需要在内部通过this关键字访问实例,必须使用匿名类;

    Java8 预定义函数式接口

    Java8 引入了函数式接口,并在java.util.function 包内预定义了常用函数式接口,下表罗列了一些常用的函数式接口:
    在这里插入图片描述

    Function

    Function接口接受一个输入参数T,返回一个结果R,即: R = F u n c t i o n ( T ) R = Function(T) R=Function(T)。接口定义为:

    /**
     * Represents a function that accepts one argument and produces a result.
     * @param <T> the type of the input to the function
     * @param <R> the type of the result of the function
     * @since 1.8
     */
    @FunctionalInterface
    public interface Function<T, R> {
    
        /**
         * Applies this function to the given argument.
         */
        R apply(T t);
    
        /**
         * Returns a composed function that first applies the {@code before}
         * function to its input, and then applies this function to the result.
         * If evaluation of either function throws an exception, it is relayed to
         * the caller of the composed function.
         */
        default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
            Objects.requireNonNull(before);
            return (V v) -> apply(before.apply(v));
        }
    
        /**
         * Returns a composed function that first applies this function to
         * its input, and then applies the {@code after} function to the result.
         * If evaluation of either function throws an exception, it is relayed to
         * the caller of the composed function.
         */
        default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
            Objects.requireNonNull(after);
            return (T t) -> after.apply(apply(t));
        }
    
        /**
         * Returns a function that always returns its input argument.
         */
        static <T> Function<T, T> identity() {
            return t -> t;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43

    Function接口默认实现了3个default方法,分别是compose、andThen和identity,对应的函数表达为:

    • compose方法的含义是 V = F u n c t i o n ( P a r a m F u n c t i o n ( T ) ) V=Function(ParamFunction(T)) V=Function(ParamFunction(T));
    • andThen方法的含义是 V = P a r a m F u n c t i o n ( F u n c t i o n ( T ) ) V=ParamFunction(Function(T)) V=ParamFunction(Function(T));
    • identity方法的含义是 T = F u n c t i o n ( T ) T=Function(T) T=Function(T);

    看个例子:

    public static void main(String[] args) {
    	Function<Integer, Integer> func1 = x -> {
    		return x + 1;
    	};
    	Function<Integer, Integer> func2 = y -> {
    		return y * 7;
    	};
    
    	int x = 3, y = 5;
    	System.out.println(func1.apply(x));
    	System.out.println(func2.apply(y));
    
    	System.out.println(func1.compose(func2).apply(x));
    	System.out.println(func1.andThen(func2).apply(x));
    }/*** output
    	4
    	35
    	22
    	28
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    Function接口相关的接口包括:

    • BiFunction :R apply(T t, U u);接受两个参数,返回一个值,代表一个二元函数;
    • DoubleFunction :R apply(double value);只处理double类型的一元函数;
    • IntFunction :R apply(int value);只处理int参数的一元函数;
    • LongFunction :R apply(long value);只处理long参数的一元函数;
    • ToDoubleFunction:double applyAsDouble(T value);返回double的一元函数;
    • ToDoubleBiFunction:double applyAsDouble(T t, U u);返回double的二元函数;
    • ToIntFunction:int applyAsInt(T value);返回int的一元函数;
    • ToIntBiFunction:int applyAsInt(T t, U u);返回int的二元函数;
    • ToLongFunction:long applyAsLong(T value);返回long的一元函数;
    • ToLongBiFunction:long applyAsLong(T t, U u);返回long的二元函数;
    • DoubleToIntFunction:int applyAsInt(double value);接受double返回int的一元函数;
    • DoubleToLongFunction:long applyAsLong(double value);接受double返回long的一元函数;
    • IntToDoubleFunction:double applyAsDouble(int value);接受int返回double的一元函数;
    • IntToLongFunction:long applyAsLong(int value);接受int返回long的一元函数;
    • LongToDoubleFunction:double applyAsDouble(long value);接受long返回double的一元函数;
    • LongToIntFunction:int applyAsInt(long value);接受long返回int的一元函数;

    Predicate

    Predicate接口接受一个输入参数,返回一个布尔值结果。其定义为:

    /**
     * Represents a predicate (boolean-valued function) of one argument.
     * @param <T> the type of the input to the predicate
     * @since 1.8
     */
    @FunctionalInterface
    public interface Predicate<T> {
    
        /**
         * Evaluates this predicate on the given argument.
         */
        boolean test(T t);
    
        /**
         * Returns a composed predicate that represents a short-circuiting logical
         * AND of this predicate and another.  When evaluating the composed
         * predicate, if this predicate is {@code false}, then the {@code other}
         * predicate is not evaluated.
         */
        default Predicate<T> and(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) && other.test(t);
        }
    
        /**
         * Returns a predicate that represents the logical negation of this
         * predicate.
         */
        default Predicate<T> negate() {
            return (t) -> !test(t);
        }
    
        /**
         * Returns a composed predicate that represents a short-circuiting logical
         * OR of this predicate and another.  When evaluating the composed
         * predicate, if this predicate is {@code true}, then the {@code other}
         * predicate is not evaluated.
         */
        default Predicate<T> or(Predicate<? super T> other) {
            Objects.requireNonNull(other);
            return (t) -> test(t) || other.test(t);
        }
    
        /**
         * Returns a predicate that tests if two arguments are equal according
         * to {@link Objects#equals(Object, Object)}.
         */
        static <T> Predicate<T> isEqual(Object targetRef) {
            return (null == targetRef)
                    ? Objects::isNull
                    : object -> targetRef.equals(object);
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53

    其默认方法也封装了and、or和negate逻辑,看个例子:

    public static void main(String[] args) {
    	Predicate<Person> predicate1 = (Person person) -> {
                Integer age = person.getAge();
                return age >= 18;
        };
    
    	Predicate<Person> predicate2 = (Person person) -> {
    		String name = person.getName();
    		return name.startsWith("R");
    	};
    
    	Person rico = new Person();
    	rico.setName("Rico");
    	rico.setAge(16);
    
    	System.out.println("Rico名字以R开头且年满18岁 : " + predicate1.and(predicate2).test(rico));
    	System.out.println("Rico名字以R开头或年满18岁 : " +  predicate1.or(predicate2).test(rico));
    	System.out.println("Rico名字不是以R开头 : " +  predicate1.negate().test(rico));
    }/*** output
    	Rico名字以R开头且年满18岁 : false
    	Rico名字以R开头或年满18岁 : true
    	Rico名字不是以R开头 : true
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    其他Predicate接口:

    • BiPredicate:boolean test(T t, U u);接受两个参数的二元谓词;
    • DoublePredicate:boolean test(double value);入参为double的谓词函数;
    • IntPredicate:boolean test(int value);入参为int的谓词函数;
    • LongPredicate:boolean test(long value);入参为long的谓词函数;

    Consumer

    Consumer接口接受一个输入参数,没有返回值。其定义为:

    /**
     * Represents an operation that accepts a single input argument and returns no
     * result. Unlike most other functional interfaces, {@code Consumer} is expected
     * to operate via side-effects.
     *  * @param <T> the type of the input to the operation
     *  * @since 1.8
     */
    @FunctionalInterface
    public interface Consumer<T> {
    
        /**
         * Performs this operation on the given argument.
         */
        void accept(T t);
    
        /**
         * Returns a composed {@code Consumer} that performs, in sequence, this
         * operation followed by the {@code after} operation. If performing either
         * operation throws an exception, it is relayed to the caller of the
         * composed operation.  If performing this operation throws an exception,
         * the {@code after} operation will not be performed.
         */
        default Consumer<T> andThen(Consumer<? super T> after) {
            Objects.requireNonNull(after);
            return (T t) -> { accept(t); after.accept(t); };
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    看个例子:

    public static void main(String[] args) {
    	Person rico = new Person();
    	rico.setName("Rico");
    	rico.setAge(16);
    
    	Consumer<Person> consumer = person -> System.out.println(person);
        consumer.accept(rico);
    }/*** output
    	{"age":16,"name":"Rico"}
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其他Consumer接口:

    • BiConsumer:void accept(T t, U u);接受两个参数
    • DoubleConsumer:void accept(double value);接受一个double参数
    • IntConsumer:void accept(int value);接受一个int参数
    • LongConsumer:void accept(long value);接受一个long参数
    • ObjDoubleConsumer:void accept(T t, double value);接受一个泛型参数一个double参数
    • ObjIntConsumer:void accept(T t, int value);接受一个泛型参数一个int参数
    • ObjLongConsumer:void accept(T t, long value);接受一个泛型参数一个long参数

    Supplier

    Supplier接口不需要任何参数,返回一个值。其定义为:

    /**
     * Represents a supplier of results.
     *
     * @param <T> the type of results supplied by this supplier
     *
     * @since 1.8
     */
    @FunctionalInterface
    public interface Supplier<T> {
    
        /**
         * Gets a result.
         */
        T get();
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    看个例子:

    public static void main(String[] args) {
    	Person rico = new Person();
    	rico.setName("Rico");
    	rico.setAge(16);
    
    	Supplier<Person> supplier = () -> rico;
    	System.out.println("supplier : " + supplier.get());
    }/*** output
    	supplier : {"age":16,"name":"Rico"}
    */
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    其他Supplier接口:

    • BooleanSupplier:boolean getAsBoolean(); 返回boolean
    • DoubleSupplier:double getAsDouble();返回double
    • IntSupplier:int getAsInt();返回int
    • LongSupplier:long getAsLong();返回long
  • 相关阅读:
    react-umi 光速上手
    vue 和 react 的区别
    SP12323 NAKANJ
    UVA439 骑士的移动
    NOI 2020 Vlog
    二叉查找树
    可持久化线段树(主席树)
    权值线段树
    YNOI2020 游记
    《四月是你的谎言》语录
  • 原文地址:https://www.cnblogs.com/wangqianbao/p/13816774.html
Copyright © 2011-2022 走看看