Java 8 lambda表达式

Posted 恒久地平线

tags:

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

Lambda表达式简介


  lambda表达式可以被理解为一个匿名函数的简明表示,它可以被传递,它没有名称,但是它有一个参数列表,一个主体,一个返回类型,也可能是可以抛出的异常列表。

  • 匿名 因为它没有一个明确的名字,不像一个方法,必须有方法名。
  • 函数 因为lambda不是属于某一个特定类的方法,但它和方法一样,lambda有一个参数列表,一个主体,一个返回类型,以及可能抛出的异常。
  • 传递 一个lambda表达式可以作为参数传递给一个方法或存储在一个变量。
  • 简洁 你不需要写很多冗长的代码,就是比写匿名内部类还简洁。

在使用Java 8以前,创建一个比较器,一般都会采用内部类方式:

Comparator<Apple> byWeight = new Comparator<Apple>() {
    public int compare(Apple a1, Apple a2) {
        return a1.getWeight().compareTo(a2.getWeight());
    }
};

使用lambda表达式之后的代码,只需一行代码。

Comparator<Apple> byWeight = (Apple a1, Apple a2) -> a1.getWeight().compareTo(a2.getWeight());

 

lambda表达式的语法结构

  lambda表达式由参数、箭头、主体,这三部分构成。

  • 参数 跟一个方法的参数类似。
  • 箭头 由参数列表指向函数主体。
  • 主体 函数的具体运算操作。

lambda表达式示例

  • (parameters) -> expression 是否有返回值取决于后面expression的返回值。
  • (parameters) -> { statements; } 多个语句需要用{}包含。
  • (parameters) -> { statements;return 23 } 用{}包含的语句,如果有返回值,需要显示return指定。


正确的表达式

(String s) -> s.length() 这个表达式有一个String的参数s,返回int,lambda表达式隐藏的return返回语句;
(Apple a) -> a.getWeight() > 150 这个表达式有一个参数Apple,返回boolean
(int x,int y) -> {System.out.println(“result:”),System.out.println(x+y)} 这个表达式有两个int型参数,没有返回值(或返回void) ,lambda表达式可以包含多个语句,但必须用{}大括号包含在里面。
() -> 42 这个表达式没有参数,但返回int型

错误的表达式

(int i) -> return “result:” + i; 正确写法(int i) -> {return “result:” + i;}
(String s) -> {“zhang” + s;} 正确写法(String s) -> {return “zhang” + s;}或者(String s) -> “zhang” + s

精简写法

参数类型可以省略,例如:Comparator<apple> c = (a1,a2) -> a1.getWeight().compareTo(a2.getWeight());
当一个lambda表达式只有一个参数时,参数类型可以省略,括号也可以省略。
例如List<Apple> greenApples = filter(inventory,a - > "green".eqauls(a.getColor()));

函数式接口


  所谓函数式接口,就是某个接口,它只有一个抽象方法。采用lambda表达式的作用,相当于该接口的唯一一个抽象方法的实现。单一抽象方法的接口,在JDK很常见,常用的有:

public interface Comparator<t> {
    int compare(T o1,T o2);
}
public interface Runnable {
    void run();
}
public interface EventListener {
    void actionPerformed(ActionEvent e);
}

@FunctionalInterface注解

 

  Java 8 专门为函数式编程,新增了java.util.function包的一些接口类,如果查看这些接口类的源码,会注意到这些函数式接口都是带@FunctionalInterface注解,此注解用于指示该接口为函数式接口。如果你定义了一个接口加上了@FunctionalInterface注解,但是接口里有多个抽象方法,这时候编译器在编译的时候就会报错。

Java 8里的函数式接口

  Java 8已经提供大量的函数式接口,在写lambda表达式时,经常会碰到,这表达式要怎么写,就要先了解对应的函数式接口里方法的参数和返回值。

Predicate

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
}

Predicate接口里的test方法,参数是T,返回boolean型,使用场景Stream的filter(Predicate<? super T> predicate)

Consumer

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
}

Consumer接口的accept方法,参数是T,返回void,使用场景Iterable的forEach(Consumer<? super T> action)

Function

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
}

Function接口的apply方法,参数是T,返回值R,使用场景Stream里的map(Function<? super T, ? extends R> mapper),flapMap(Function<? super T, ? extends R> mapper)

方法引用


  前面lambda表达式简介里,介绍了lambda表达式是一个匿名的函数,可以被当做参数传递给调用方。如果传给调用方的这个参数不再是lambda表达式,而是源自于某个类的方法,这时就称之为方法引用。
例如用lambda表示比较器:
Arrays.sort(list,(Apple a,Apple b) -> a.getWeight().compareTo(b.getWeight()));
采用方法引用的写法:
Arrays.sort(list,Comparator.comparing(Apple::getWeight))
打开Comparator的comparing源码可以看到,这个方法里已经实现了的比较器的判断规则,只不过这个规则是根据传入的函数(或方法)的返回值,做大小比较。

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));
}

方法引用的三种方式

方法引用静态方法,例如,Integer的parseInt(),写成Integer::parseInt。
一个方法引用一个任意类型的实例方法,例如String的length((),写成String::length。
方法引用现有对象的实例方法,例如有个本地变量obj,它有个getValue()的方法,写成obj::getValue

    上图中需要注意的是第二点,lambda的第一个参数是,是方法调用的实例对象,与之对应的方法引用,会排除第一个参数,也就是arg0会忽略掉,只有rest作为参数。
Function<String, Integer> stringToInteger =(String s) -> Integer.parseInt(s);,对应的方法引用是Integer::parseInt
BiPredicate<List<String>, String> contains = (list, element) -> list.contains(element);这个lambda表达式里,用第一个参数的实例对象去调用contains方法,属于方法引用的第二种方式,所以对应的方法引用是 List::contains。这里主要的混淆点是contains()方法的参数只有一个,但因为这个方法属于String的实例方法(而不是类的静态方法),隐含了第一个参数。

构造方法引用

  类的构造方法也可以被引用,使用语法是ClassName::new。例如:

Supplier<Apple> c1 = App::new; 
App a1 = c1.get(()

    这个语句引用的是无参构造方法,也可以引用有参构造方法:

BiFunction<String,Integer,Apple> c3 = Apple::new; 
App a1 = c3.apply("gree",110)

    这里的构造方法Apple(String color,Integer weight)有两个参数。

参考资料

  • 《Java 8 in Action》

以上是关于Java 8 lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章

Java 8 lambda表达式20180404

Java 8 lambda表达式示例

Lambda 表达式 in java 8

重学Java 8新特性 | 第4讲——Lambda表达式详解

Java 8 Lambda表达式

Java 8 Lambda表达式10个示例存