likes
comments
collection
share

Java中的Lambda表达式-概念与原理

作者站长头像
站长
· 阅读数 43

2014年发布的Java8引入了Lambda表达式,是Java历史上特别重大的语言特性增强,应用非常广泛,也是同时引入的Stream的基础。但是在仍旧有些开发小伙伴对此感到很困惑,也不太会应用,本文结合自己的理解和使用经验试着以一种比较简单直观的方式解释相关的概念,给出一些例子,帮助大家理解、应用Java中的Lambda表达式。

什么是Lambda表达式

Lambda就是匿名函数。

如果非要一句话描述什么是Lambda,那必须是:Lambda就是匿名函数。

Java语言是面向对象的语言,不是函数式语言,在Java8之后也不是函数式语言,只是利用了Lambda表达式这种语法形式支持了部分的函数式的编程风格。 结合几个例子来说明一下Lambda。

Java代码

public class SimpleLambda {

    public List<String> toUpperList(List<String> strList) {
        List<String> upper = strList.stream()
            .map(s -> s.toUpperCase())
            .collect(Collectors.toList());
        return upper;
    }

}

单体测试

    @Test
    void toUpperList() {
        List<String> strList = Lists.newArrayList("a", "b", "hello");
        SimpleLambda simpleLambda = new SimpleLambda();
        List<String> upper = simpleLambda.toUpperList(strList);
        Assertions.assertEquals(3, upper.size());
        Assertions.assertEquals("A", upper.get(0));
        Assertions.assertEquals("B", upper.get(1));
        Assertions.assertEquals("HELLO", upper.get(2));
    }

上述例子中的s -> s.toUpperCase()就是Lambda表达式,含义是将字符串转换为大写。

Lambda的基本语法

Java语法中,Lambda的基本语法如下:

(parameters) -> expression

(parameters> -> { statements; }

Lambda表达式有三个部分:

  • 参数列表:由类型和参数名组成,如果类型可以根据上下文推断出来,类型可以省略
  • 箭头->,也叫做Lambda运算符
  • Lambda主体:主体可以是表达式,我们例子中就是表达式;或者是花括号括起来的语句。

根据上面的定义,s -> s.toUpperCase()可以有多种写法,下面注释的行都是合法并且等价的写法。

    public List<String> toUpperList(List<String> strList) {
        List<String> upper = strList.stream()
            .map(s -> s.toUpperCase())
            //.map((String s) -> s.toUpperCase())
            //.map((s) -> s.toUpperCase())
            //.map((s) -> {return s.toUpperCase();})
            .collect(Collectors.toList());
        return upper;
    }

映射到我们上面给Lambda下的定义,s -> s.toUpperCase()是一个匿名函数。

  • 这个函数的参数为只有一个,类型为String(当然,结合这里的上下文,String可以推断出来,就没有写)
  • ->是Lambda运算符,表示这里是一个Lambda的定义
  • s.toUpperCase()则是一个表达式,表示将字符串转为大写。

像上面这个例子,为什么不直接调用字符串的toUpperCase()方法,却要绕一个大弯呢?

直接的原因是:

上面流操作的map步骤中,需要一个函数式接口作为参数,而不是需要一次函数的调用,这里涉及到Stream的终结操作和中间操作,不展开叙述,不清楚的可以看看我的其他文章。

更深层次的原因是:

Java希望自己也支持函数式编程,即使不是真正的函数式语言,至少从语法表达上看上去像是函数式的;函数式编程是一个很大的话题,下一节会概要介绍一下。

Java是面向对象的语言,Java中一切东西皆是对象。函数是不允许脱离对象存在的,Java中的函数也不能像JavaScript语言那样没有名字,那么面向对象的Java怎么支持函数式编程风格呢?

答案是函数式接口(Functional Interface)。

函数式接口

有且只有一个抽象方法的接口就是函数式接口。 Java8之后,接口可以有自己的方法实现,这些有实现的方法或者是静态方法,或者是默认方法,这些被实现的接口方法当然不是抽象方法,所以函数式接口可能有很多方法,但是只要保证有且只有一个抽象方法就行。在需要函数式接口作为参数的地方,传入的参数就可以是Lambda表达式,这个传入的Lambda表达式,就是函数式接口那个唯一的抽象方法的实现。

以Stream的sorted()方法为例看看。


    /**
     * Returns a stream consisting of the elements of this stream, sorted
     * according to the provided {@code Comparator}.
     *
     * <p>For ordered streams, the sort is stable.  For unordered streams, no
     * stability guarantees are made.
     *
     * <p>This is a <a href="package-summary.html#StreamOps">stateful
     * intermediate operation</a>.
     *
     * @param comparator a <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                   <a href="package-summary.html#Statelessness">stateless</a>
     *                   {@code Comparator} to be used to compare stream elements
     * @return the new stream
     */
    Stream<T> sorted(Comparator<? super T> comparator);

sorted以Comparator这个函数式接口作为参数,看看Comparator的定义。


package java.util;

import java.io.Serializable;
import java.util.function.Function;
import java.util.function.ToIntFunction;
import java.util.function.ToLongFunction;
import java.util.function.ToDoubleFunction;
import java.util.Comparators;

@FunctionalInterface
public interface Comparator<T> {
    /**
     * Compares its two arguments for order.  Returns a negative integer,
     * zero, or a positive integer as the first argument is less than, equal
     * to, or greater than the second.<p>
     *
     * The implementor must ensure that {@link Integer#signum
     * signum}{@code (compare(x, y)) == -signum(compare(y, x))} for
     * all {@code x} and {@code y}.  (This implies that {@code
     * compare(x, y)} must throw an exception if and only if {@code
     * compare(y, x)} throws an exception.)<p>
     *
     * The implementor must also ensure that the relation is transitive:
     * {@code ((compare(x, y)>0) && (compare(y, z)>0))} implies
     * {@code compare(x, z)>0}.<p>
     *
     * Finally, the implementor must ensure that {@code compare(x,
     * y)==0} implies that {@code signum(compare(x,
     * z))==signum(compare(y, z))} for all {@code z}.
     *
     * @apiNote
     * It is generally the case, but <i>not</i> strictly required that
     * {@code (compare(x, y)==0) == (x.equals(y))}.  Generally speaking,
     * any comparator that violates this condition should clearly indicate
     * this fact.  The recommended language is "Note: this comparator
     * imposes orderings that are inconsistent with equals."
     *
     * @param o1 the first object to be compared.
     * @param o2 the second object to be compared.
     * @return a negative integer, zero, or a positive integer as the
     *         first argument is less than, equal to, or greater than the
     *         second.
     * @throws NullPointerException if an argument is null and this
     *         comparator does not permit null arguments
     * @throws ClassCastException if the arguments' types prevent them from
     *         being compared by this comparator.
     */
    int compare(T o1, T o2);

    /**
     * Indicates whether some other object is &quot;equal to&quot;
     * this comparator.  This method must obey the general contract of
     * {@link Object#equals(Object)}.  Additionally, this method can
     * return {@code true} <i>only</i> if the specified object is also
     * a comparator and it imposes the same ordering as this
     * comparator.  Thus, {@code comp1.equals(comp2)} implies that
     * {@link Integer#signum signum}{@code (comp1.compare(o1,
     * o2))==signum(comp2.compare(o1, o2))} for every object reference
     * {@code o1} and {@code o2}.<p>
     *
     * Note that it is <i>always</i> safe <i>not</i> to override
     * {@code Object.equals(Object)}.  However, overriding this method may,
     * in some cases, improve performance by allowing programs to determine
     * that two distinct comparators impose the same order.
     *
     * @param   obj   the reference object with which to compare.
     * @return  {@code true} only if the specified object is also
     *          a comparator and it imposes the same ordering as this
     *          comparator.
     * @see Object#equals(Object)
     * @see Object#hashCode()
     */
    boolean equals(Object obj);

    /**
     * Returns a comparator that imposes the reverse ordering of this
     * comparator.
     *
     * @return a comparator that imposes the reverse ordering of this
     *         comparator.
     * @since 1.8
     */
    default Comparator<T> reversed() {
        return Collections.reverseOrder(this);
    }

    /**
     * Returns a lexicographic-order comparator with another comparator.
     * If this {@code Comparator} considers two elements equal, i.e.
     * {@code compare(a, b) == 0}, {@code other} is used to determine the order.
     *
     * <p>The returned comparator is serializable if the specified comparator
     * is also serializable.
     *
     * @apiNote
     * For example, to sort a collection of {@code String} based on the length
     * and then case-insensitive natural ordering, the comparator can be
     * composed using following code,
     *
     * <pre>{@code
     *     Comparator<String> cmp = Comparator.comparingInt(String::length)
     *             .thenComparing(String.CASE_INSENSITIVE_ORDER);
     * }</pre>
     *
     * @param  other the other comparator to be used when this comparator
     *         compares two objects that are equal.
     * @return a lexicographic-order comparator composed of this and then the
     *         other comparator
     * @throws NullPointerException if the argument is null.
     * @since 1.8
     */
    default Comparator<T> thenComparing(Comparator<? super T> other) {
        Objects.requireNonNull(other);
        return (Comparator<T> & Serializable) (c1, c2) -> {
            int res = compare(c1, c2);
            return (res != 0) ? res : other.compare(c1, c2);
        };
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts a key to be compared with the given {@code Comparator}.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparing(keyExtractor, cmp))}.
     *
     * @param  <U>  the type of the sort key
     * @param  keyExtractor the function used to extract the sort key
     * @param  keyComparator the {@code Comparator} used to compare the sort key
     * @return a lexicographic-order comparator composed of this comparator
     *         and then comparing on the key extracted by the keyExtractor function
     * @throws NullPointerException if either argument is null.
     * @see #comparing(Function, Comparator)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default <U> Comparator<T> thenComparing(
            Function<? super T, ? extends U> keyExtractor,
            Comparator<? super U> keyComparator)
    {
        return thenComparing(comparing(keyExtractor, keyComparator));
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts a {@code Comparable} sort key.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparing(keyExtractor))}.
     *
     * @param  <U>  the type of the {@link Comparable} sort key
     * @param  keyExtractor the function used to extract the {@link
     *         Comparable} sort key
     * @return a lexicographic-order comparator composed of this and then the
     *         {@link Comparable} sort key.
     * @throws NullPointerException if the argument is null.
     * @see #comparing(Function)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default <U extends Comparable<? super U>> Comparator<T> thenComparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        return thenComparing(comparing(keyExtractor));
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts an {@code int} sort key.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparingInt(keyExtractor))}.
     *
     * @param  keyExtractor the function used to extract the integer sort key
     * @return a lexicographic-order comparator composed of this and then the
     *         {@code int} sort key
     * @throws NullPointerException if the argument is null.
     * @see #comparingInt(ToIntFunction)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default Comparator<T> thenComparingInt(ToIntFunction<? super T> keyExtractor) {
        return thenComparing(comparingInt(keyExtractor));
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts a {@code long} sort key.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparingLong(keyExtractor))}.
     *
     * @param  keyExtractor the function used to extract the long sort key
     * @return a lexicographic-order comparator composed of this and then the
     *         {@code long} sort key
     * @throws NullPointerException if the argument is null.
     * @see #comparingLong(ToLongFunction)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default Comparator<T> thenComparingLong(ToLongFunction<? super T> keyExtractor) {
        return thenComparing(comparingLong(keyExtractor));
    }

    /**
     * Returns a lexicographic-order comparator with a function that
     * extracts a {@code double} sort key.
     *
     * @implSpec This default implementation behaves as if {@code
     *           thenComparing(comparingDouble(keyExtractor))}.
     *
     * @param  keyExtractor the function used to extract the double sort key
     * @return a lexicographic-order comparator composed of this and then the
     *         {@code double} sort key
     * @throws NullPointerException if the argument is null.
     * @see #comparingDouble(ToDoubleFunction)
     * @see #thenComparing(Comparator)
     * @since 1.8
     */
    default Comparator<T> thenComparingDouble(ToDoubleFunction<? super T> keyExtractor) {
        return thenComparing(comparingDouble(keyExtractor));
    }

    /**
     * Returns a comparator that imposes the reverse of the <em>natural
     * ordering</em>.
     *
     * <p>The returned comparator is serializable and throws {@link
     * NullPointerException} when comparing {@code null}.
     *
     * @param  <T> the {@link Comparable} type of element to be compared
     * @return a comparator that imposes the reverse of the <i>natural
     *         ordering</i> on {@code Comparable} objects.
     * @see Comparable
     * @since 1.8
     */
    public static <T extends Comparable<? super T>> Comparator<T> reverseOrder() {
        return Collections.reverseOrder();
    }

    /**
     * Returns a comparator that compares {@link Comparable} objects in natural
     * order.
     *
     * <p>The returned comparator is serializable and throws {@link
     * NullPointerException} when comparing {@code null}.
     *
     * @param  <T> the {@link Comparable} type of element to be compared
     * @return a comparator that imposes the <i>natural ordering</i> on {@code
     *         Comparable} objects.
     * @see Comparable
     * @since 1.8
     */
    @SuppressWarnings("unchecked")
    public static <T extends Comparable<? super T>> Comparator<T> naturalOrder() {
        return (Comparator<T>) Comparators.NaturalOrderComparator.INSTANCE;
    }

    /**
     * Returns a null-friendly comparator that considers {@code null} to be
     * less than non-null. When both are {@code null}, they are considered
     * equal. If both are non-null, the specified {@code Comparator} is used
     * to determine the order. If the specified comparator is {@code null},
     * then the returned comparator considers all non-null values to be equal.
     *
     * <p>The returned comparator is serializable if the specified comparator
     * is serializable.
     *
     * @param  <T> the type of the elements to be compared
     * @param  comparator a {@code Comparator} for comparing non-null values
     * @return a comparator that considers {@code null} to be less than
     *         non-null, and compares non-null objects with the supplied
     *         {@code Comparator}.
     * @since 1.8
     */
    public static <T> Comparator<T> nullsFirst(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(true, comparator);
    }

    /**
     * Returns a null-friendly comparator that considers {@code null} to be
     * greater than non-null. When both are {@code null}, they are considered
     * equal. If both are non-null, the specified {@code Comparator} is used
     * to determine the order. If the specified comparator is {@code null},
     * then the returned comparator considers all non-null values to be equal.
     *
     * <p>The returned comparator is serializable if the specified comparator
     * is serializable.
     *
     * @param  <T> the type of the elements to be compared
     * @param  comparator a {@code Comparator} for comparing non-null values
     * @return a comparator that considers {@code null} to be greater than
     *         non-null, and compares non-null objects with the supplied
     *         {@code Comparator}.
     * @since 1.8
     */
    public static <T> Comparator<T> nullsLast(Comparator<? super T> comparator) {
        return new Comparators.NullComparator<>(false, comparator);
    }

    /**
     * Accepts a function that extracts a sort key from a type {@code T}, and
     * returns a {@code Comparator<T>} that compares by that sort key using
     * the specified {@link Comparator}.
     *
     * <p>The returned comparator is serializable if the specified function
     * and comparator are both serializable.
     *
     * @apiNote
     * For example, to obtain a {@code Comparator} that compares {@code
     * Person} objects by their last name ignoring case differences,
     *
     * <pre>{@code
     *     Comparator<Person> cmp = Comparator.comparing(
     *             Person::getLastName,
     *             String.CASE_INSENSITIVE_ORDER);
     * }</pre>
     *
     * @param  <T> the type of element to be compared
     * @param  <U> the type of the sort key
     * @param  keyExtractor the function used to extract the sort key
     * @param  keyComparator the {@code Comparator} used to compare the sort key
     * @return a comparator that compares by an extracted key using the
     *         specified {@code Comparator}
     * @throws NullPointerException if either argument is null
     * @since 1.8
     */
    public static <T, U> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor,
            Comparator<? super U> keyComparator)
    {
        Objects.requireNonNull(keyExtractor);
        Objects.requireNonNull(keyComparator);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyComparator.compare(keyExtractor.apply(c1),
                                              keyExtractor.apply(c2));
    }

    /**
     * Accepts a function that extracts a {@link java.lang.Comparable
     * Comparable} sort key from a type {@code T}, and returns a {@code
     * Comparator<T>} that compares by that sort key.
     *
     * <p>The returned comparator is serializable if the specified function
     * is also serializable.
     *
     * @apiNote
     * For example, to obtain a {@code Comparator} that compares {@code
     * Person} objects by their last name,
     *
     * <pre>{@code
     *     Comparator<Person> byLastName = Comparator.comparing(Person::getLastName);
     * }</pre>
     *
     * @param  <T> the type of element to be compared
     * @param  <U> the type of the {@code Comparable} sort key
     * @param  keyExtractor the function used to extract the {@link
     *         Comparable} sort key
     * @return a comparator that compares by an extracted key
     * @throws NullPointerException if the argument is null
     * @since 1.8
     */
    public static <T, U extends Comparable<? super U>> Comparator<T> comparing(
            Function<? super T, ? extends U> keyExtractor)
    {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> keyExtractor.apply(c1).compareTo(keyExtractor.apply(c2));
    }

    /**
     * Accepts a function that extracts an {@code int} sort key from a type
     * {@code T}, and returns a {@code Comparator<T>} that compares by that
     * sort key.
     *
     * <p>The returned comparator is serializable if the specified function
     * is also serializable.
     *
     * @param  <T> the type of element to be compared
     * @param  keyExtractor the function used to extract the integer sort key
     * @return a comparator that compares by an extracted key
     * @see #comparing(Function)
     * @throws NullPointerException if the argument is null
     * @since 1.8
     */
    public static <T> Comparator<T> comparingInt(ToIntFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Integer.compare(keyExtractor.applyAsInt(c1), keyExtractor.applyAsInt(c2));
    }

    /**
     * Accepts a function that extracts a {@code long} sort key from a type
     * {@code T}, and returns a {@code Comparator<T>} that compares by that
     * sort key.
     *
     * <p>The returned comparator is serializable if the specified function is
     * also serializable.
     *
     * @param  <T> the type of element to be compared
     * @param  keyExtractor the function used to extract the long sort key
     * @return a comparator that compares by an extracted key
     * @see #comparing(Function)
     * @throws NullPointerException if the argument is null
     * @since 1.8
     */
    public static <T> Comparator<T> comparingLong(ToLongFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Long.compare(keyExtractor.applyAsLong(c1), keyExtractor.applyAsLong(c2));
    }

    /**
     * Accepts a function that extracts a {@code double} sort key from a type
     * {@code T}, and returns a {@code Comparator<T>} that compares by that
     * sort key.
     *
     * <p>The returned comparator is serializable if the specified function
     * is also serializable.
     *
     * @param  <T> the type of element to be compared
     * @param  keyExtractor the function used to extract the double sort key
     * @return a comparator that compares by an extracted key
     * @see #comparing(Function)
     * @throws NullPointerException if the argument is null
     * @since 1.8
     */
    public static<T> Comparator<T> comparingDouble(ToDoubleFunction<? super T> keyExtractor) {
        Objects.requireNonNull(keyExtractor);
        return (Comparator<T> & Serializable)
            (c1, c2) -> Double.compare(keyExtractor.applyAsDouble(c1), keyExtractor.applyAsDouble(c2));
    }
}

Comparator的代码有几个点需要注意:

  • 这个接口被@FunctionalInterface注解修饰,明确告诉你这就是函数式接口;
  • int compare(T o1, T o2);是这个接口的抽象方法;
  • boolean equals(Object obj);也只有声明,没有实现(Comparator你个渣男,不是说好了抽象方法只有一个吗?你为什么有俩?为什么呢????自己考虑或者自己看看代码。。。);
  • 其他的已实现方法要不就是静态的,要不就是缺省的(哪位好心人能告诉我default方法和static方法有啥区别呢?)

我们可以得出结论:如果给sorted方法传递一个与int compare(T o1, T o2)相匹配的函数定义,这个函数定义以Lambda的形式写出来,就能符合sorted的参数要求,能编译通过、运行成功。

我们试一下,假设我们要求以字符串的长度来排序,字符少的string排在前面。

    public List<String> sortByLength(List<String> strList) {
        List<String> result = strList.stream()
            .sorted((s1, s2) -> s1.length() - s2.length())
            .collect(Collectors.toList());
        return result;
    }

代码(s1, s2) -> s1.length() - s2.length()就是符合条件的Lambda表达式,参数是两个String对象,由于String根据上下文可以推断出来,类型String省略掉了,Lambda的主体部分是个整形类型的表达式,完美符合int compare(T o1, T o2)对应Lambda的一切期望。

单体测试一下:

    @Test
    void sortByLength() {
        List<String> strList = Lists.newArrayList("AB", "C", "ABCD");
        SimpleLambda simpleLambda = new SimpleLambda();
        List<String> result = simpleLambda.sortByLength(strList);
        Assertions.assertEquals(3, result.size());
        Assertions.assertEquals("C", result.get(0));
        Assertions.assertEquals("AB", result.get(1));
        Assertions.assertEquals("ABCD", result.get(2));
    }

Lambda背后的逻辑

假设我们是Java语言编译器(javac)的作者,既要支持函数式,又不能把Java语言推翻,那怎么搞呢?Java不能把函数当做对象,也不能把函数当做参数,但是Java有接口啊。接口可以当做参数类型,接口的实现类可以示例化为Java对象,如果我们再加一个小小的限制:让接口只有一个抽象方法。

那么我们把这种只有一个抽象方法的接口当做参数时,编译器做一点小小的处理(识别Lambda语法),是不是等同于支持了函数式编程呢?

我们自己新建一个函数式接口:

@FunctionalInterface
public interface GreaterInterface {

    boolean isGreaterThan(Integer num);
}

我们以Lambda表达式的形式实现这个接口:

public class SimpleLambda {

    public boolean byLambda(Integer num) {
        // 使用Lambda表达式定义了一个“函数”
        GreaterInterface greaterInterface = number -> number > 5;
        // 调用刚刚定义的函数
        return greaterInterface.isGreaterThan(num);
    }
}

这行代码 GreaterInterface greaterInterface = number -> number > 5; 在Java8之前是不支持的,Java8中,以Lambda表达式的形式实现了接口。

翻译成Java8之前的代码类似这样:

    public boolean byInnerClass(Integer num) {
        // 使用匿名内部类的形式定义了一个“函数”
        GreaterInterface greaterInterface = new GreaterInterface() {
            @Override
            public boolean isGreaterThan(Integer num) {
                return num > 5;
            }
        };
        // 调用刚刚定义的函数
        return greaterInterface.isGreaterThan(num);
    }

尽管Java编译器的工作要复杂的多,但是本质来说,Lambda表达式就是以匿名内部类的形式实现了函数式接口。 对比一下反编译的class文件

byLambda


  // access flags 0x1
  public byLambda(Ljava/lang/Integer;)Z
    // parameter  num
   L0
    LINENUMBER 20 L0
    INVOKEDYNAMIC isGreaterThan()Lcom/sptan/sbe/lambda/GreaterInterface; [
      // handle kind 0x6 : INVOKESTATIC
      java/lang/invoke/LambdaMetafactory.metafactory(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodType;Ljava/lang/invoke/MethodHandle;Ljava/lang/invoke/MethodType;)Ljava/lang/invoke/CallSite;
      // arguments:
      (Ljava/lang/Integer;)Z, 
      // handle kind 0x6 : INVOKESTATIC
      com/sptan/sbe/lambda/SimpleLambda.lambda$byLambda$0(Ljava/lang/Integer;)Z, 
      (Ljava/lang/Integer;)Z
    ]
    ASTORE 2
   L1
    LINENUMBER 22 L1
    ALOAD 2
    ALOAD 1
    INVOKEINTERFACE com/sptan/sbe/lambda/GreaterInterface.isGreaterThan (Ljava/lang/Integer;)Z (itf)
    IRETURN
   L2
    LOCALVARIABLE this Lcom/sptan/sbe/lambda/SimpleLambda; L0 L2 0
    LOCALVARIABLE num Ljava/lang/Integer; L0 L2 1
    LOCALVARIABLE greaterInterface Lcom/sptan/sbe/lambda/GreaterInterface; L1 L2 2
    MAXSTACK = 2
    MAXLOCALS = 3

byInnerClass:

  // access flags 0x1
  public byInnerClass(Ljava/lang/Integer;)Z
    // parameter  num
   L0
    LINENUMBER 55 L0
    NEW com/sptan/sbe/lambda/SimpleLambda$1
    DUP
    ALOAD 0
    INVOKESPECIAL com/sptan/sbe/lambda/SimpleLambda$1.<init> (Lcom/sptan/sbe/lambda/SimpleLambda;)V
    ASTORE 2
   L1
    LINENUMBER 62 L1
    ALOAD 2
    ALOAD 1
    INVOKEINTERFACE com/sptan/sbe/lambda/GreaterInterface.isGreaterThan (Ljava/lang/Integer;)Z (itf)
    IRETURN
   L2
    LOCALVARIABLE this Lcom/sptan/sbe/lambda/SimpleLambda; L0 L2 0
    LOCALVARIABLE num Ljava/lang/Integer; L0 L2 1
    LOCALVARIABLE greaterInterface Lcom/sptan/sbe/lambda/GreaterInterface; L1 L2 2
    MAXSTACK = 3
    MAXLOCALS = 3

可以看出来,没有本质上的区别。但是Lambda形式的代码要精练很多,更重要的是,更加“函数式”。

函数式编程

函数的定义很抽象,我也不从网上摘抄一些"大家"下的定义了,感兴趣的可以去维基百科了解一下。我只列出我个人认为的一些重要的点,这里大家随便看看,等看完了文章的其他部分,再回过头来回味一下。

函数式编程是什么

  • 函数是一等公民
  • 匿名函数
  • 闭包
  • 柯里化
  • 惰性求值
  • 参数多态

Java中函数式的方法

一个方法可以是函数式的,只要它满足纯函数的要求:

  • 它不能修改函数外的任何东西。外部观测不到内部的任何变化。
  • 它不能修改自己的参数。
  • 它不能抛出错误或异常。
  • 它必须返回一个值。
  • 只要调用它的参数相同,结果也必须相同。

类型推断

在我们应用Lambda表达式的过程中,我们很少需要指定参数类型,这是因为Java编译器能够根据Lambda表达式的上下文信息推断出参数的正确类型。实际上我们的程序仍旧需要通过类型检查来保证程序的安全性,只是不用再显式声明类型,这就是所谓的类型推断。

看看下面这个例子:

Predicate<Integer> asLeast5 = x -> x > 5;

Predicate的定义:

@FunctionalInterface
public interface Predicate<T> {

    /**
     * Evaluates this predicate on the given argument.
     *
     * @param t the input argument
     * @return {@code true} if the input argument matches the predicate,
     * otherwise {@code false}
     */
    boolean test(T t);
    ......
}

根据Predicate的定义, Predicate只有一个泛型类型的参数,当然Integer是可以的,x -> x > 5的参数类型很自然的可以推断出x的类型为Integer,Predicate要求返回值为boolean, 显然x > 5就是一个布尔表达式。 类型推断用于让我们知道需要接收函数式接口的时候,我们的Lambda表达式怎么写;也有助于我们阅读比较"现代"的代码库。

Lambda表达式必须满足以下条件才算是合法的Java代码:

  • Lambda表达式必须出现在期望使用接口类型实例的地方;
  • 期望使用的接口类型必须只有一个抽象方法;
  • 这个抽象方法的方法签名必须完全匹配Lambda表达式。

Lambda的几个重要特性

方法引用

有时候你想传递给其他代码的操作已经有实现的方法了,这时候可以使用一种特殊的语法:方法引用。这比Lambda更简短,类似的便捷方法构造函数也有。

本质上来说,方法引用是某些Lambda表达式的进一步简写。

Lambda表达式有三部分:参数列表、箭头符(->)、表达式主体

方法引用也有三部分:目标引用、域操作符(::)、方法名称

这里方法名称大致用来代替表达式主体(当然表达式主体中只有对方法的调用才行),目标引用起到参数列表的作用,目标引用的表现形式相对比较单一,所以我们有理由推测,在实现编译器的时候,方法引用的类型推断或者目标参数推断变得更加困难,不是三言两语能说清楚的(实际上说不清楚的原因是我没有理清楚),这里向实现Java编译器的大神们致以崇高的敬意和无限的崇拜。

好消息是方法引用只有5种形式

方法引用格式代码等价的Lambda表达式
类::实例方法TypeName::method(instance, args) -> instance.method(args)
对象::实例方法instance::method(args) -> instance.method(args)
类::静态方法TypeName::method(args) -> TypeName.method(args)
构造方法TypeName::new(args) -> new TypeName(args)
数组构造方法TypeName[]::new(int size) -> new TypeName[size]

类::实例方法

第一个参数变成方法的接收者,并且其他参数也传递给该方法。

方法引用等价的Lambda表达式
String::compareToIgnoreCase(x,y) -> x.compareToIgnoreCase(y)
String::toUpperCases -> s.toUpperCase()

对象::实例方法

在给定的对象上调用方法,并且参数传递给实例方法

方法引用等价的Lambda表达式
System.out::printlnx -> System.out.println(x)
s::toUpperCase() -> s.toUpperCase()

类::静态方法

所有参数传递给静态方法。

方法引用等价的Lambda表达式
Objects::isNullx -> Objects.isNull(x)
String::valueOfs -> String.valueOf(s)

构造方法

构造函数引用中方法名都是new。类有多个构造函数时,根据上下文推断使用哪个构造函数。

方法引用等价的Lambda表达式
String::new() -> new String()
Employee::news -> new Employee(s)
*假设Employee有构造函数Employee(String)

数组构造方法

方法引用等价的Lambda表达式
String[]::newn -> new String[n]

Lambda表达式与局部变量

Lambda表达式不仅仅能使用主体里面的参数,也能使用自由变量,就是Lambda外层作用域定义的变量。

例如

int portNumber = 6350;
Runnable r = () -> System.out.pringln(portNumber);

Lambda可以没有限制的使用实例变量和静态变量,但是局部变量必须显式声明为final或者是事实上的final才行。下面代码无法编译:

int portNumber = 6350;
Runnable r = () -> System.out.pringln(portNumber);
portNumber = 1234;

为啥访问实例变量没有限制,访问局部变量要限制自由局部变量是事实上final(只被赋值一次)的呢?

  1. 实例变量存储在堆上,局部变量存储在栈中。如果Lambda可以直接访问自由局部变量,而且Lambda是在一个线程中使用的,则使用Lambda的线程,可能会在分配该变量的线程将这个变量收回之后,去访问该变量。Java在访问自由局部变量时,实际上是访问它的副本,如果限制这个变量是final的(只被赋值一次),就不会产生问题。
  2. 有这个限制的存在,不用担心局部变量的改变从而使得并行处理变得很容易。
  3. 这个限制使得Lambda更符合函数式编程的思想,即没有副作用,不改变函数外的任何东西。

可以看出Lambda事实上符合闭包的特点:

  • Lambda可以当做参数传递给方法
  • 可以访问作用域之外的变量

Lambda中的return

注意Lambda中的return仅仅从Lambda中返回,不是从调用Lambda的代码块中返回。

void demo() {
    threeTimes(i -> {
        System.out.println(i);
        return; // 从lambda 返回到 threeTimes!
    });
}

练习一下

看看下面哪个不是有效的Lambda表达式?

  1. () -> {}
  2. () -> "Hello"
  3. () -> { return "Hello"; }
  4. (Integer i) -> return "Alan" + i;
  5. (String s) -> { "Hello"; }

关于Lambda的概念和原理部分,暂时先说这么多,有空我再写一篇介绍一下使用方法,给出一些例子。

大家有什么想了解的,在评论区说一下,回头我补充。

转载自:https://juejin.cn/post/7375351908897538067
评论
请登录