# Lambda

返回:java8

译者注

(虽然看着很先进,其实Lambda表达式的本质只是一个"语法糖",由编译器推断并帮你转换包装为常规的代码,因此你可以使用更少的代码来实现同样的功能。本人建议不要乱用,因为这就和某些很高级的黑客写的代码一样,简洁,难懂,难以调试,维护人员想骂娘.)
可以把Lambda表达式理解为简洁的表示可传递的匿名函数的一种方式:它没有名称,但它有参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表。

lambda表达式允许你通过表达式来代替功能接口。
lambda表达式就和方法一样,它提供了一个正常的参数列表和一个使用这些参数的主体(body,可以是一个表达式或一个代码块)。

Java SE 8添加了2个对集合数据进行批量操作的包: java.util.function 包以及java.util.stream 包。

Stream&Function

流(stream)就如同迭代器(iterator),但附加了许多额外的功能。 总的来说,lambda表达式和 stream 是自Java语言添加泛型(Generics)和注解(annotation)以来最大的变化。 在本文中,我们将从简单到复杂的示例中见认识lambda表达式和stream的强悍。

# Lambda表达式的语法

back

# 基本语法

(parameters) -> expression  
(parameters) ->{ statements; }  
1
2

java中,引入了一个新的操作符“->”,该操作符在很多资料中,称为箭头操作符,或者lambda操作符;箭头操作符将lambda分成了两个部分:

  1. 左侧:lambda表达式的参数列表
  2. 右侧:lambda表达式中所需要执行的功能,即lambda函数体
  3. lambda表达式语法格式;
  • (1).无参数,无返回值的用法 :
() -> System.out.println("hello lambda");
1
  • (2).有一个参数,无返回值的用法:
(x) -> System.out.println(x); // 或者  
 x -> System.out.println(x); // 一个参数,可以省略参数的小括号
1
2

下面是Java lambda表达式的简单例子:

// 1. 不需要参数,返回值为 5  
() -> 5  
  // 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 - x  
  // 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  // 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  // 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)  
1
2
3
4
5
6
7
8
9
10

# Lambda的域以及访问限制

# 1、访问局部变量

在Lambda表达式外部的局部变量会被JVM隐式的编译成final类型,因此只能访问外而不能修改。

# 2、访问静态变量和成员变量

在Lambda表达式内部,对静态变量和成员变量可读可写。

# 基本的Lambda例子

back

@Test
public void testLambda(){
    String[] atp = {"Rafael Nadal","Novak Djokovic","Stanislas Wawrinka",
            "David Ferrer","Roger Federer","Andy Murray",
            "Tomas Berdych","Juan Martin Del Potro"};
    List<String> players =  Arrays.asList(atp);
    players.forEach((palyer) -> System.out.println(palyer+"。"));
}
@Test
public void testJava8(){
    String[] atp = {"Rafael Nadal","Novak Djokovic","Stanislas Wawrinka",
            "David Ferrer","Roger Federer","Andy Murray",
            "Tomas Berdych","Juan Martin Del Potro"};
    List<String> players =  Arrays.asList(atp);
    players.forEach(System.out::println);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

IDE警告

lambda can be replaced with method……

这种警告可以通过修改代码如下:

@Test
public void testConsumer(){
    Consumer<String> con = System.out::println;
    con.accept("你是哪个");
}
1
2
3
4
5

双冒号::

双冒号(::)运算符在Java 8中被用作方法引用(method reference),方法引用是与lambda表达式相关的一个重要特性。它提供了一种不执行方法的方法。为此,方法引用需要由兼容的函数接口组成的目标类型上下文。

# Runnable接口

下面是使用lambdas 来实现 Runnable接口 的示例:

@Test
public void testThread(){
    new Thread(new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello HT");
        }
    }).start();
    new Thread(()-> System.out.println("Hello HT")).start();

    Runnable run1 = new Runnable() {
        @Override
        public void run() {
            System.out.println("Hello CLM");
        }
    };

    Runnable run2 = ()-> System.out.println("Hello CLM");

    run1.run();
    run2.run();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
  • (3).有两个参数,有返回值的:(x, y) -> x + y
BinaryOperator<Integer> addOp = (a,b)->a+b;
System.out.println(addOp.apply(3,5));
1
2
  • (4).剩下的多个参数

就不在继续介绍了,按照上面的推测,就可以了, 上面的例子,函数体中,都是一行语句,最后一个语法,介绍下,lambda表达式中,多行语句,分别在无返回值和有返回值的抽象类中的用法

函数式接口支持

lamabd表达式中,需要有函数式接口的支持;
函数式接口定义:接口中只有一个抽象方法的接口,称为函数式接口;
可以使用@FunctionalInterface注解修饰,对该接口做检查;如果接口里,有多个抽象类,使用该注解,会有语法错误
在java8中,lambda表达式所用的接口,必须是函数式接口; 在java8中的函数式接口,有很多,比说,
无参无返回值的(Runnable接口),有一个参数,无返回值的(Consumer),有多个参数有返回值的(BiFunction)……等等; 根据不用的作用,java8中,内置了4个核心接口

# JAVA8-Consumer接口

back

Consumer

Consumer的语义是消费的意思,了解一些消息队列的同学,肯定对这个单词,有一定的理解,我们先看接口的定义

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
/**传入一个Consumer类型的参数,
    *他的泛型类型,
    *跟本接口是一致的T,先做本接口的accept操作,
    *然后在做传入的Consumer类型的参数的accept操作
*/
    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

这个接口,接收一个泛型的参数T,然后调用accept,对这个参数做一系列的操作,没有返回值;看到这里,是不是很懵,下面用一个简单的小例子

# JAVA8-Supplier接口

back

@FunctionalInterface
public interface Supplier<T> {

    /**
     - Gets a result.
     *
     - @return a result
     */
    T get();
}
1
2
3
4
5
6
7
8
9
10

Supplier

看语义,可以看到,这个接口是一个提供者的意思,只有一个get的抽象类,没有默认的方法以及静态的方法,传入一个泛型T的,get方法,返回一个泛型T

Supplier<AlarmBo> supS = AlarmBo::new;
AlarmBo alarmBo = supS.get();
alarmBo.setAlarmDevName("sss");
System.out.println(alarmBo.getAlarmDevName());
1
2
3
4

可以看到,这个接口,只是为我们提供了一个创建好的对象,这也符号接口的语义的定义,提供者,提供一个对象, 直接理解成一个创建对象的工厂,就可以了;

# JAVA8-Function接口以及同类型的特化的接口

back

Funtion

Funtion接口,定义了一个apply的抽象类,接收一个泛型T对象,并且返回泛型R对象,看到这里,是不是对这个接口的描述,还是一头雾水呢,

Function<Integer,String> fis = x->x*2+"aaa";
System.out.println(fis.apply(6));
1
2

传入的泛型T是Integer的,返回的泛型R是String的,里面的做的操作是传入的参数x 乘2,然后连接一个dd的字符串,然后的就是泛型R的字符串;
源码:

@FunctionalInterface
public interface Function<T, R> {
    R apply(T t);
    /**
     - 先做传入的Function类型的参数的apply操作,再做当前这个接口的apply操作
     - V表示这个Function类型的参数的传入参数类型,也就是本接口的T类型
    - @param before
     - @return
     */

    default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }
    /**
    - 先做本接口的apply操作,再做传入的Function类型的参数的apply操作
    - @param after
    - @return
    */

    default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }
    /**
    - 静态方法表示,这个传入的泛型参数T的本身
    - @return
    */

    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

Function扩展

下面我们看下Funtion这个接口的“扩展”的原始类型特化的一些函数接口
IntFunction<R>,IntToDoubleFunction,
IntToLongFunction,LongFunction<R>,
LongToDoubleFunction,LongToIntFunction,
DoubleFunction<R>,ToIntFunction<T>,
ToDoubleFunction<T>,ToLongFunction<T>

# JAVA8-Predicate接口

back

@FunctionalInterface
public interface Predicate<T> {
    boolean test(T t);
    default Predicate<T> and(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) && other.test(t);
    }
    default Predicate<T> negate() {
        return (t) -> !test(t);
    }
    default Predicate<T> or(Predicate<? super T> other) {
        Objects.requireNonNull(other);
        return (t) -> test(t) || other.test(t);
    }
    static <T> Predicate<T> isEqual(Object targetRef) {
        return (null == targetRef)
                ? Objects::isNull
                : object -> targetRef.equals(object);
    }
}

Predicate<String> ps = s->s.startsWith("N");
System.out.println(ps.test("Nasdfsd"));
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

# 其他lambda接口

back

接口 说明
BiConsumer<T,U> 代表了一个接受两个输入参数的操作,并且不返回任何结果
BiFunction<T,U,R> 代表了一个接受两个输入参数的方法,并且返回一个结果
BinaryOperator<T> 代表了一个作用于于两个同类型操作符的操作,并且返回了操作符同类型的结果
BiPredicate<T,U> 代表了一个两个参数的boolean值方法
BooleanSupplier 代表了boolean值结果的提供方
Consumer<T> 代表了接受一个输入参数并且无返回的操作
DoubleBinaryOperator 代表了作用于两个double值操作符的操作,并且返回了一个double值的结果。
DoubleConsumer 代表一个接受double值参数的操作,并且不返回结果。
DoubleFunction<R> 代表接受一个double值参数的方法,并且返回结果
DoublePredicate 代表一个拥有double值参数的boolean值方法
DoubleSupplier 代表一个double值结构的提供方
DoubleToIntFunction 接受一个double类型输入,返回一个int类型结果。
DoubleToLongFunction 接受一个double类型输入,返回一个long类型结果
DoubleUnaryOperator 接受一个参数同为类型double,返回值类型也为double 。
Function<T,R> 接受一个输入参数,返回一个结果。
IntBinaryOperator 接受两个参数同为类型int,返回值类型也为int 。
IntConsumer 接受一个int类型的输入参数,无返回值 。
IntFunction<R> 接受一个int类型输入参数,返回一个结果 。
IntPredicate 接受一个int输入参数,返回一个布尔值的结果。
IntSupplier 无参数,返回一个int类型结果。
IntToDoubleFunction 接受一个int类型输入,返回一个double类型结果 。
IntToLongFunction 接受一个int类型输入,返回一个long类型结果。
IntUnaryOperator 接受一个参数同为类型int,返回值类型也为int 。
LongBinaryOperator 接受两个参数同为类型long,返回值类型也为long。
LongConsumer 接受一个long类型的输入参数,无返回值。
LongFunction<R> 接受一个long类型输入参数,返回一个结果。
LongPredicate R接受一个long输入参数,返回一个布尔值类型结果。
LongSupplier 无参数,返回一个结果long类型的值。
LongToDoubleFunction 接受一个long类型输入,返回一个double类型结果。
LongToIntFunction 接受一个long类型输入,返回一个int类型结果。
LongUnaryOperator 接受一个参数同为类型long,返回值类型也为long。
ObjDoubleConsumer<T> 接受一个object类型和一个double类型的输入参数,无返回值。
ObjIntConsumer<T> 接受一个object类型和一个int类型的输入参数,无返回值。
ObjLongConsumer<T> 接受一个object类型和一个long类型的输入参数,无返回值。
Predicate<T> 接受一个输入参数,返回一个布尔值结果。
Supplier<T> 无参数,返回一个结果。
ToDoubleBiFunction<T,U> 接受两个输入参数,返回一个double类型结果
ToDoubleFunction<T> 接受一个输入参数,返回一个double类型结果
ToIntBiFunction<T,U> 接受两个输入参数,返回一个int类型结果。
ToIntFunction<T> 接受一个输入参数,返回一个int类型结果。
ToLongBiFunction<T,U> 接受两个输入参数,返回一个long类型结果。
ToLongFunction<T> 接受一个输入参数,返回一个long类型结果。
UnaryOperator<T> 接受一个参数为类型T,返回值类型也为T。
  • JDK 1.8之前已有的函数式接口:
    • java.lang.Runnable
    • java.util.concurrent.Callable
    • java.security.PrivilegedAction
    • java.util.Comparator
    • java.io.FileFilter
    • java.nio.file.PathMatcher
    • java.lang.reflect.InvocationHandler
    • java.beans.PropertyChangeListener
    • java.awt.event.ActionListener
    • javax.swing.event.ChangeListener
// 接受两个泛型参数
@FunctionalInterface
public interface UnaryOperator<T> extends Function<T, T> {

    /**
     - Returns a unary operator that always returns its input argument.
     *
     - @param <T> the type of the input and output of the operator
     - @return a unary operator that always returns its input argument
     */
    static <T> UnaryOperator<T> identity() {
        return t -> t;
    }
}

@FunctionalInterface
public interface BiConsumer<T, U> {
    void accept(T t, U u);
    default BiConsumer<T, U> andThen(BiConsumer<? super T, ? super U> after) {
        Objects.requireNonNull(after);

        return (l, r) -> {
            accept(l, r);
            after.accept(l, r);
        };
    }
}
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

# java8-lambda表达式,方法的引用以及构造器的引用

back

在lambda表达式中,使用方法的引用,来传递方法方法的行为参数化; 方法的引用让你可以重复使用现有的方法定义,并像lambda一样传递他们,在一些情况下,比起使用lambda表达式,它们似乎更易读,感觉也更自然;

方法的引用的语法,主要有三类

  • 指向静态方法的方法引用,例如Integer的parseInt方法 ,可以写成Integer::parseInt
    • 类::静态方法名
  • 指向任意类型实例方法的方法引用,例如String的length方法,写成String::length;
    • 类::实例方法名
  • 指向现有对象的实例方法的方法引用
    • 对象::实例方法名

构造器的引用:对于一个现有构造函数,你可以利用它的名称和关键字new来创建它的一个引用ClassName::new

最后,特别要注意:

  • ①方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致!
  • ②若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName