澳门新葡萄京官网首页 4

澳门新葡萄京官网首页Java 8 Lambda 表达式(lambda expressions)-我的理解

Java Lambda 表达式是 Java 8
引入的一个新的功能,可以说是模拟函数式编程的一个语法糖,类似于
Javascript 中的闭包,但又有些不同,主要目的是提供一个函数化的语法来简化我们的编码。

Java 8 新特性:Lambda 表达式 ——诺诺”涂鸦”记忆

lambda表达式(lambda expressions)是函数式编程。

Lambda 基本语法

Lambda 的基本结构为 (arguments) -> body,有如下几种情况:

  • 参数类型可推导时,不需要指定类型,如 (a) -> System.out.println(a)
  • 当只有一个参数且类型可推导时,不强制写 (),
    如 a -> System.out.println(a)
  • 参数指定类型时,必须有括号,如 (int a) -> System.out.println(a)
  • 参数可以为空,如 () -> System.out.println(“hello”)
  • body 需要用 {} 包含语句,当只有一条语句时 {} 可省略

常见的写法如下:

(a) -> a * a
(int a, int b) -> a + b
(a, b) -> {return a - b;}
() -> System.out.println(Thread.currentThread().getId())

Lambda 表达式

 

(注:此文乃个人查找资料然后学习总结的,若有不对的地方,请大家指出,非常感谢!另外,知识都有串联,如果某一处看不懂,就先接着往下看,之后再回头看不明白的地方就会恍然大悟了。)

 

百度百科:
“Lambda 表达式”(lambda
expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda
abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包(注意和数学传统意义上的不同)。

维基百科:函数式编程(英语:functional
programming
)或称函数程序设计,又称泛函编程,是一种编程范型,它将电脑运算视为数学上的函数)计算,并且避免使用程序状态以及易变对象。函数编程语言最重要的基础是λ演算(lambda
calculus)。而且λ演算的函数可以接受函数当作输入(引数)和输出(传出值)。比起命令式编程,函数式编程更加强调程序执行的结果而非执行的过程,倡导利用若干简单的执行单元让计算结果不断渐进,逐层推导复杂的运算,而不是设计一个复杂的执行过程。

函数式接口 FunctionalInterface

一.为什么Java需要Lambda表达式?

 

如果忽视注解(Annotations)、泛型(Generics)等特性,自Java语言诞生时起,它的变化并不大。Java一直都致力维护其对象至上的特征,在使用过JavaScript之类的函数式语言之后,Java如何强调其面向对象的本质,以及源码层的数据类型如何严格变得更加清晰可感。其实,函数对Java而言并不重要,在Java的世界里,函数无法独立存在。

 

然而,在函数式编程语言中,函数是一等公民,它们可以独立存在,你可以将其赋值给一个变量,或将他们当做参数传给其他函数。JavaScript是最典型的函数式编程语言。函数式语言提供了一种强大的功能——闭包,相比于传统的编程方法有很多优势,闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。Java现在提供的最接近闭包的概念便是Lambda表达式,虽然闭包与Lambda表达式之间存在显著差别,但至少Lambda表达式是闭包很好的替代者。

 

Lambda表达式为Java添加了缺失的函数式编程特点,使我们能将函数当做一等公民看待。尽管不完全正确,我们很快就会见识到Lambda与闭包的不同之处,但是又无限地接近闭包。在支持一类函数的语言中,Lambda表达式的类型将是函数。但是,在Java中,Lambda表达式是对象,他们必须依附于一类特别的对象类型——函数式接口(functional
interface)。

 

背景

不过有些 Java 对象只是对单个函数的封装。例如下面这个典型用例:Java API
中定义了一个接口(一般被称为回调接口),用户通过提供这个接口的实例来传入指定行为,例如:

public interface ActionListener {
  void actionPerformed(ActionEvent e);
}

这里并不需要专门定义一个类来实现
ActionListener,因为它只会在调用处被使用一次。用户一般会使用匿名类型把行为内联(inline):

button.addActionListener(new ActionListener() {
  public void actionPerformed(ActionEvent e) {
    ui.dazzle(e.getModifiers());
  }
});

我们需要在Java中提供一种尽可能轻量级的将代码封装为数据(Model code as
data)的方法。匿名内部类并不是一个好的 选择。
大多数回调接口都拥有这个特征:比如 Runnable 接口和 Comparator
接口。我们把这些只拥有一个方法的接口称为 函数式接口。(之前它们被称为
SAM类型,即 单抽象方法类型(Single Abstract Method))

我们并不需要额外的工作来声明一个接口是函数式接口:编译器会根据接口的结构自行判断(判断过程并非简单的对接口方法计数:一个接口可能冗余的定义了一个
Object 已经提供的方法,比如
toString(),或者定义了静态方法或默认方法,这些都不属于函数式接口方法的范畴)。不过API作者们可以通过
@FunctionalInterface
注解来显式指定一个接口是函数式接口(以避免无意声明了一个符合函数式标准的接口),加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求。

概念

Java Lambda
表达式以函数式接口为基础。什么是函数式接口(FunctionalInterface)?
简单说来就是只有一个方法(函数)的接口,这类接口的目的是为了一个单一的操作,也就相当于一个单一的函数了。常见的接口如:Runnable,
Comparator 都是函数式接口,并且都标注了注解 @FunctionalInterface 。

二.函数式接口

 

函数式接口是只包含一个抽象方法的接口。函数式接口有时候被称为SAM类型,意思是单抽象方法(Single
Abstract Method)。

 

一般来说,这个抽象方法指明了接口的目标用途。因此,函数式接口通常表示单个动作。

 

eg:标准接口Runnable是一个函数式接口,因为它只定义了一个方法run();因此,run()定义了Runnable的动作。

 

此外,函数式接口定义了lambda表达式的目标类型。但lambda表达式只能用于其目标类型已经被指定的上下文中。

 

同时,Java 8引入了一个新的注解:@FunctionalInterface。

 

可以在任意函数式接口上面使用 @FunctionalInterface
来标识它是一个函数式接口,但是该注解不是强制的。

 

 

1)当你注释的接口不是有效的函数式接口时,可以使用
@FunctionalInterface 解决编译层面的错误。

 

eg:

 

自定义一个函数式接口:

 

@FunctionalInterface
public interface MyTestInterface {
    public void doSomeThing();
}

 

据定义,函数式接口只能有一个抽象方法,如果你尝试添加第二个抽象方法,将抛出编译时错误。

错误写法:

@FunctionalInterface
public interface MyTestInterface {
    public void doSomeThing();
    public void doMoreThing();
}

提示错误信息:

澳门新葡萄京官网首页 1

 

2)当lambda表达式被转换成一个函数式接口的实例时,需要注意处理检查时异常。

eg:

 

Runnable runnable = () -> {
            try {
                Thread.sleep(5000);
            } catch (Exception e) {
                e.printStackTrace();
            }
 };

 

若不加 try catch
语句,赋值语句就会编译错误,因为Runnable的run方法是没有异常抛出的。

3)Callable
是可以抛出任何异常,并且有返回值,
当需要不返回任何数据时可这样定义:

Callable callable = () -> {
            System.out.println("zzzzz");
            return null;
};

 

 

Note:

 

 

1)函数式接口可以定义Object定义的任何公有方法,例如equals(),而不影响其作为”函数式接口”的状态。Object的公有方法被视为函数式接口的隐式成员,因为函数式接口的实例会默认自动实现它们。

 

 

2)默认方法和静态方法(后面会具体解释)不会违反函数接口的约定。

 

 

lamdba表达式的目的:

解决匿名内部类使用的问题:

  • 语法过于冗余
  • 匿名类中的 this 和变量名容易使人产生误解
  • 类型载入和实例创建语义不够灵活
  • 无法捕获非 final 的局部变量
  • 无法对控制流进行抽象
    匿名类型最大的问题就在于其冗余的语法。有人戏称匿名类型导致了“高度问题”(height
    problem):比如前面 ActionListener
    的例子里的五行代码中仅有一行在做实际工作。

lambda表达式是匿名方法,它提供了轻量级的语法,从而解决了匿名内部类带来的“高度问题”。

下面是一些lambda表达式:

(int x, int y) -> x + y
() -> 42
(String s) -> { System.out.println(s); }

第一个 lambda 表达式接收 x 和 y 这两个整形参数并返回它们的和;第二个
lambda 表达式不接收参数,返回整数 ‘42’;第三个 lambda
表达式接收一个字符串并把它打印到控制台,不返回值。

  • lambda 表达式的语法由参数列表、箭头符号 ->
    和函数体组成。函数体既可以是一个表达式,也可以是一个语句块:

  • 表达式:表达式会被执行然后返回执行结果。

  • 语句块:语句块中的语句会被依次执行,就像方法中的语句一样——

  • return 语句会把控制权交给匿名方法的调用者

  • break 和 continue 只能在循环中使用

  • 如果函数体有返回值,那么函数体内部的每一条路径都必须返回值
    表达式函数体适合小型 lambda 表达式,它消除了 return
    关键字,使得语法更加简洁。

Java8中,想要使用Lambda表达式,需要使用一个全新的操作符”->”该操作符被称为”Lambda操作符”,它把表达式分为了左右两部分。
左边:Lambda中所使用的参数列表
右边:Lambda所要执行的操作。

****Example 1:无参数,无返回值的写法****

澳门新葡萄京官网首页 2

Example 2:有参数,无返回值的写法

澳门新葡萄京官网首页 3

Example 3:有参数(多个),有返回值的写法

澳门新葡萄京官网首页 4

举例

以 Thread 为例说明很容易理解。Runnable
接口是我们线程编程时常用的一个接口,就包含一个方法 void run(),这个方法就是线程的运行逻辑。按照以前的语法,我们新建线程一般要用到
Runnable 的匿名类,如下:

new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getId());
    }

}).start();

如果写多了,是不是很无聊,而基于 Lambda 的写法则变得简洁明了,如下:

new Thread(() -> System.out.println(Thread.currentThread().getId())).start();

注意 Thread 的参数,Runnable
的匿名实现就通过一句就实现了出来,写成下面的更好理解

Runnable r = () -> System.out.println(Thread.currentThread().getId());
new Thread(r).start();

当然 Lambda 的目的不仅仅是写起来简洁,更高层次的目的等体会到了再总结。

再看一个比较器的例子,按照传统的写法,如下:

Integer[] a = {1, 8, 3, 9, 2, 0, 5};
Arrays.sort(a, new Comparator<Integer>() {
    @Override
    public int compare(Integer o1, Integer o2) {
        return o1 - o2;
    }
});

Lambda 表达式写法如下:

Integer[] a = {1, 8, 3, 9, 2, 0, 5};
Arrays.sort(a, (o1, o2) -> o1 - o2);

三.Lambda表达式

 

1.简介

 

在Java中,Lambda 表达式(lambda expression)是一个匿名函数。

 

Lambda表达式基于数学中的λ演算得名,直接对应于其中的Lambda抽象(lambda
abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包,但又不同于函数式语言的闭包。Lambda表达式让代码变得简洁并且允许你传递行为,在java8出现之前传递行为的方法只有通过匿名内部类。

 

Lambda表达式的Java实现:第一个就是Lambda表达式自身,第二个是函数式接口。

 

Lambda表达式本质上就是一个匿名(即未命名的方法)。但是这个方法是不能独立执行的,而是用于实现由函数式接口定义的一个方法(即:使用
Lambda
表达式实例化函数式接口
)。因此,Lambda表达式会导致产生一个匿名类。

 

 

Lambda表达式不是独立执行的,而是构成了一个函数式接口定义的抽象方法的实现,该函数式接口定义了它的目标类型。结果,只有在定义了lambda表达式的目标类型的上下文中,才能使用该表达式。当把一个lambda表达式赋给一个函数式接口的引用时,就创建了这样的上下文。

当目标类型上下文中出现lambda表达式时,就会自动创建实现了函数式接口的一个类的实例(类似于匿名类),函数式接口声明的抽象方法的行为由lambda表达式定义。当通过目标调用该方法时,就会执行lambda表达式。

为了在目标类型上下文中使用lambda表达式,抽象方法的类型和lambda表达式的类型必须兼容。eg:如果抽象方法指定了两个int类型的参数,那么lambda表达式也必须执行两个参数,其类型要么被显示指定为int类型,要么在上下文中可以被隐式的推断为int类型。总的来讲lambda表达式的参数的类型和数量必须与函数式接口内的抽象方法的参数兼容;返回类型必须兼容;并且lambda表达式可能抛出的异常必须能被该方法接受。

2.优点

Lambda表达式的应用使代码变得更加紧凑,可读性增强;Lambda表达式使并行操作大集合变得更方便,可以充分发挥多核CPU的优势,更易于为多核处理器编写代码。

 

 

3.组成(或定义)

Lambda 表达式由三个部分组成:

 

 

第一部分为一个括号内用逗号分隔的形式参数,参数是函数式接口里面方法的参数;

 

 

第二部分为一个箭头符号:->澳门新葡萄京官网首页 ,;

 

 

第三部分为方法体,可以是表达式和代码块。

 

 

语法:

 

 

1)方法体为表达式,该表达式的值作为返回值返回。

 

 

(parameters)->expression

 

 

2)方法体为代码块,必须用{}来包裹起来,且需要一个return返回值,但若函数式接口里面方法返回值是void,则无需返回值。

 

 

(parameters) -> { statements; }

 

 

Note:

1) 一个Lambda表达式可以有零个或多个参数

2) 参数的类型既可以明确声明,也可以根据上下文来推断。

eg:(int a)与(a)效果相同

3) 所有参数需包含在圆括号内,参数之间用逗号相隔。

eg:(a, b)或(int a, int b)或(String a, int b, float c)

4) 空圆括号代表参数集为空。

eg:() -> 50

5) 当只有一个参数,且其类型可推导时,圆括号()可省略。

eg:a -> return a*a

6) Lambda表达式的主体可包含零条或多条语句

7)
如果Lambda表达式的主体只有一条语句,花括号{}可省略。匿名函数的返回类型与该主体表达式一致。

 

8) 如果 Lambda
表达式的主体包含一条以上语句,则表达式必须包含在花括号{}中(形成代码块)。匿名函数的返回类型与代码块的返回类型一致,若没有返回则为空。

9)对于Lambda表达式中的多个参数,如果需要显示声明一个参数的类型,那么必须为所有的参数都声明类型。

eg:MyNumericTest是一个自定义函数式接口,详情接口代码略,看下面应用的例子:

这样写不合法:MyNumericTest isFactor = (int n,d) ->
(n%d)==0;

正确的写法:MyNumericTest isFactor = (int n,int d) ->
(n%d)==0;

 

 

 

 

Lambda写法示例:

eg:

1)左边是指定类型的逗号分割的输入列表,右边是带有return的代码块:

(intx,inty) -> {returnx + y; }

2)左边是推导类型的逗号分割的输入列表,右边是返回值:

(x, y) -> x + y

3)左边是推导类型的单一参数,右边是一个返回值:

x -> x * x

4)左边没有输入 (官方名称: “burger arrow”),在右边返回一个值:

() -> x

5)左边是推导类型的单一参数,右边是没返回值的代码块(返回void):

x -> { System.out.println(x); }

6)静态方法引用:(注:第一次看不懂没关系,后面会提到这种用法)

String::valueOf

7)非静态方法引用:

Object::toString

8)继承的函数引用:

x::toString

9)构造函数引用:

ArrayList::new

 

4.类型推断

 

在Lambda表达式中,我们不需要明确指出参数类型,javac编译器会通过上下文自动推断参数的类型信息。根据上下文推断类型的行为称为类型推断

 

Java8提升了Java中已经存在的类型推断系统,使得对Lambda表达式的支持变得更加强大。javac会寻找紧邻lambda表达式的一些信息通过这些信息来推断出参数的正确类型。

 

Note:在大多数情况下,javac会根据上下文自动推断类型。假设因为丢失了上下文信息或者上下文信息不完整而导致无法推断出类型,代码编译就不会通过。

 

JDK英文文档:Generalized Target-Type Inference

 

 

5.Lambda 表达式的使用示例

(注:下面的例子若暂时看不懂,可通篇博客看完后再回过来看示例,第一次看难免会觉得不理解。)

1)无参Lambda表达式:

 

//函数式接口 
    interface MyNumber
    {
        double getValue();
    }

    class lambdaDemo
    {
        public static void main(String[] args)
        {
            MyNumber myNum;

            myNum = ()->123.45;
            System.out.println("A fixed value: "+myNum.getValue());

            myNum = ()->Math.random()*100;
            System.out.println("A random value: "+myNum.getValue());

            //下面情况是:lambda表达式的返回值类型与函数式接口中抽象函数的类型不匹配。
   // myNum = ()->"123.03"    //error! 
        }
    }

 

2)带参数的lambda表达式:

 

interface NumericTest
    {
        boolean test(int n);
    }

    class lambdaDemo2
    {
        public static void main(String[] args)
        {
            NumericTest isEven = (n)->(n%2)==0;
            if(isEven.test(10)){
    System.out.println("10 is even");
   }
            if(!isEven.test(9)){
    System.out.println("9 is not even");
   }

            NumericTest isNonNeg = (n)-> n>=0;
            if(isNonNeg.test(1)){
    System.out.println("1 is non-negative");
   }
            if(!isNonNeg.test(-1)){
    System.out.println("-1 is negative");
   }
        }
    }

 

 

3)接受两个参数的lambda表达式:

 

interface NumericTest2
    {
        boolean test(int n,int d);
    }

    class lambdaDemo3
    {
        public static void main(String[] args)
        {
            //测试一个数字是否是另一个数字的因子。
            NumericTest2 isFactor = (n,d) -> (n%d)==0;

            if(isFactor.test(10,2)){ 
    System.out.println("2 is a factor of 10");
   }
            if(!isFactor.test(10,3)){ 
    System.out.println("3 is not a factor of 10");
   }
        }
    }

 

 

4)线程优化:

原线程:

 

 new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Hello from thread");
            }
        }).start();

 

使用Lambda表达式:

 

new Thread(
                () -> System.out.println("Hello from thread")
        ).start();

 

5)事件处理

向一个 UI 组件添加 ActionListener

原java写法:

 

button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("The button was clicked using old java code!");
            }
        });

 

使用Lambda表达式:

 

button.addActionListener((e) -> {
            System.out.println("The button was clicked. From Lambda expressions !");
        });

 

6)打印出给定数组中的所有元素

Note:使用 Lambda 表达式的方法不止一种。

原java写法:

 

List list = Arrays.asList(1, 2, 3, 4, 5);
        for (Integer n : list) {
            System.out.println(n);
        }

使用Lambda表达式:

 

第一种方式:

 

List list = Arrays.asList(1, 2, 3, 4, 5);
        list.forEach(n -> System.out.println(n));

 

第二种方式:(这里用到了方法引用)

 

List list = Arrays.asList(1, 2, 3, 4, 5);
        list.forEach(System.out::println);

 

7)使用断言(Predicate)函数式接口创建一个测试,并打印所有通过测试的元素

代码:

 

import java.util.Arrays;
import java.util.List;
import java.util.function.Predicate;
public class LambdaTest {
    public static void main(String[] a) {
        List list = Arrays.asList(1, 2, 3, 4, 5, 6, 7);

        System.out.println("all numbers:");
        evaluate(list, (n) -> true);

        System.out.println("no numbers:");
        evaluate(list, (n) -> false);

        System.out.println("even numbers:");
        evaluate(list, (n) -> n % 2 == 0);

        System.out.println("odd numbers:");
        evaluate(list, (n) -> n % 2 == 1);

        System.out.println("numbers greater than 5:");
        evaluate(list, (n) -> n > 5);
    }
    public static void evaluate(List list, Predicate predicate) {
        for(Integer n: list)  {
            if(predicate.test(n)) {
                System.out.println(n + " ");
            }
        }
    }
}

结果显示:

 

 

all numbers: 1 2 3 4 5 6 7 
no numbers: 
even numbers: 2 4 6 
odd numbers: 1 3 5 7 
numbers greater than 5: 6 7

 

8)Lambda 表达式打印数值中每个元素的平方

以下使用了 .stream() 方法将常规数组转化为流。Java 8 新增加了流
APIs。java.util.stream.Stream接口包含许多有用的方法,能结合 Lambda
表达式产生非常棒的效果。我们将 Lambda 表达式x -> x*x传给 map()
方法,该方法会作用于流中的所有元素。之后,我们使用 forEach
方法打印数据中的所有元素。

原java实现方式:

 

 List list = Arrays.asList(0,1,2,3,4,5);
        for(Integer n : list) {
            int x = n * n;
            System.out.println(x);
        }

 

使用Lambda表达式和Java8新特性 流 实现方式:

 

List list = Arrays.asList(0,1,2,3,4,5);
        list.stream().map((x) -> x*x).forEach(System.out::println);

 

9)计算给定数值中每个元素平方后的总和

原java实现方式:

 

 List list = Arrays.asList(1,2,3,4,5);
        int sum = 0;
        for (Integer n : list) {
            int x = n * n;
            sum = sum + x;
        }
        System.out.println(sum);

 

使用Lambda表达式:

 

List list = Arrays.asList(1,2,3,4,5);
        int sum = list.stream().map(x -> x * x).reduce((x, y) -> x + y).get();
        System.out.println(sum);

 

6.块Lambda表达式

显示的Lambda方法体内只包含单个表达式,这种类型的Lambda体被称为表达式体,具有表达式体的Lambda表达式可以被称为表达式Lambda。在表达式体中,操作符右侧的代码必须包含单独一个表达式。

Java支持另外一种类型的Lambda表达式,其中操作符右侧的代码可以由一个代码块构成,其中可以包含多条语句。这种类型的Lambda体被称为块体。具有块体的Lambda表达式有时候被称为块Lambda

块Lambda表达式扩展了Lambda表达式内部可以处理的操作类型,因为它允许Lambda体包含多条语句。创建块Lambda很容易,只需要使用花括号包围Lambda体。

在块Lambda中必须显示使用return语句来返回值。必须这么做,因为块Lambda体代表的不是一个单独的表达式。

Note:当lambda表达式中出现return语句时,只是从lambda体返回,而不会导致包围lambda体的方法返回。

eg:使用块lambda来计算并返回一个int类型值的阶乘

 

interface NumericFunc {
    inf func(int n);
}
class BlockLambdaDemo {
    public static void main(String[] args) {
        NumericFunc factorial = (n) ->
        {
            int result = 1;
            for (int i = 1; i <= n; i++) {
                result = i * result;
            }
            return result;
        };
        System.out.println("The factorial of 3 is " + factorial.func(3));
        System.out.println("The factorial of 5 is " + factorial.func(5));
    }
}

 

 

7.泛型函数式接口

Lambda表达式自身不能指定类型参数。因此,Lambda表达式不能是泛型(当然,由于存在类型推断,所有Lambda表达式都展现出了一些类似于泛型的特征)。但是,与Lambda表达式关联的函数式接口可以泛型。此时,Lambda表达式的目标类型部分由声明函数式接口引用时指定的参数类型决定。

 

eg:

 

interface SomeFunc {
    T func(T t);
}
class GenericFunctionalInterfaceDemo {
    public static void main(String[] args) {
        SomeFunc reverse = (str) ->
        {
            int i;
            String result = "";
            for (i = str.length() - 1; i >= 0; i--) {
                result += str.charAt(i);
            }
            return result;
        };
        System.out.println("lambda reserved is " + reverse.func("lambda"));
        
        SomeFunc factorial = (n) ->
        {
            int result = 1;

            for (int i = 1; i <= n; i++) {
                result = result * i;
            }
            return result;
        };
        System.out.println("The factorial of 3 is " + factorial.func(3));
    }
}

结果:

 

lambda reserved is adbmal
The factorial of 3 is 6

 

分析:

T指定了func()函数的返回类型和参数类型。这意味着它与任何
只接收一个参数,并返回一个相同类型的值的lambda表达式兼容。

SomeFunc接口用于提供对两种不同类型的lambda表达式的引用。第一种表达式使用String类型,第二种表达式使用Integer类型。因此,同一个函数式接口可以用于reserve
lambda表达式和factorial
lambda表达式。区别仅在于传递给SomeFunc的参数类型。

 

8.作为参数传递的Lambda表达式

为了将lambda表达式作为参数传递,接收lambda表达式的参数的类型必须是与该lambda表达式兼容的函数式接口的类型。

eg:Lambda表达式 作为方法参数使用

 

//Use lambda expressions as an argument to method
interface StringFunc {
    String func(String n);
}
class lambdasAsArgumentsDemo {
    static String stringOp(StringFunc sf, String s) {
        return sf.func(s);
    }
    public static void main(String[] args) {
        String inStr = "lambda add power to java";
        String outStr;
        System.out.println("Here is input string: " + inStr);
        //Lambda表达式 作为方法参数使用
        //第一种方式
        outStr = stringOp((str) -> str.toUpperCase(), inStr);
        System.out.println("The string in uppercase: " + outStr);
        //第二种方式
        outStr = stringOp((str) ->
        {
            String result = "";
            for (int i = 0; i < str.length(); i++) {
                if (str.charAt(i) != '') {
                    result += str.charAt(i);
                }
            }
            return result;
        }, inStr);
        System.out.println("The string with spaces removed: " + outStr);
        //第三种方式
        //当块lambda看上去特别长,不适合嵌入到方法的调用中时,很容易把块lambda赋给一个函数式接口变量.
        //然后,可以简单地把该引用传递给方法。
        StringFunc reverse = (str) ->
        {
            String result = "";
            for (int i = str.length() - 1; i >= 0; i--) {
                result += str.charAt(i);
            }
            result result;
        };
        System.out.println("The string reserved: " + stringOp(reverse, inStr));
    }
}

 

输出结果:

Here is input string: lambda add power to java
The string in uppercase: LAMBDAS ADD POWER TO JAVA
The string with spaces removed: lambdaaddpowertojava
The string reserved: avaJ ot rewop dda sadbmal

分析:

首先注意stringOp()方法。它有两个参数,第一个参数的类型是StringFunc,而StringFunc是一个函数式接口。因此,这个参数可以接受对任何StringFunc实例的引用,包括由lambda表达式创建的实例。stringOp的第二个参数是String类型,也就是要操作的字符串。接下来,注意对stringOp()的第一次调用,如下所示:
outStr = stringOp((str)->str.uppercase(),inStr);

这里,传递了一个简单的表达式lambda作为参数。这会创建函数式接口StringFunc的一个实例,并把对该实例的一个引用传递给stringOp()方法的第一个参数这就把嵌入在一个类实例中的lambda代码传递给了方法。目标类型上下文由参数的类型决定。因此lambda表达式与该类型兼容,调用是合法的。

当块lambda看上去特别长,不适合嵌入到方法的调用中时,很容易把块lambda赋给一个函数式接口变量,正如上面代码中那样。然后,可以简单地把该引用传递给方法。

 

9.为什么抽象类不能通过利用lambda实例化?

 

(注:此处可以忽略.在网上看到有这样的说法,目前此处本人也不是很清晰,先记录下来,以后明白了再补充。)

抽象类,哪怕只声明了一个抽象方法,也不能使用lambda来实例化。

下面有两个类Ordering和CacheLoader,都带有一个抽象方法,摘自于Guava库。如果能够声明它们的实例,像这样使用lambda表达式么?

Ordering order = (a, b) -> …;

CacheLoader loader = (key) -> …;

 

1)这样做会增加阅读lambda的难度。

以这种方式实例化一段抽象类将导致隐藏代码的执行:抽象类的构造方法。

2)它抛出了lambda表达式可能的优化。

在未来,它可能是这种情况,lambda表达式都不会计算到对象实例。放任用户用lambda来声明抽象类将妨碍像这样的优化。

 

此外,有一个简单地解决方法。事实上,上述两个摘自Guava库的实例类已经证明了这种方法。增加工厂方法将lambda转换成实例。

Ordering order = Ordering.from((a, b) -> …);

 

CacheLoader loader = CacheLoader.from((key) -> …);

 

 

10.lambda表达式是否只是一个匿名内部类的语法?

答案是NO。原因有两点:

 

 

·性能影响:
假如lambda表达式是采用匿名内部类实现的,那么每一个lambda表达式都会在磁盘上生成一个class文件。当JVM启动时,这些class文件会被加载进来,因为所有的class文件都需要在启动时加载并且在使用前确认,从而会导致JVM的启动变慢。

 

 

·向后的扩展性:如果Java8的设计者从一开始就采用匿名内部类的方式,那么这将限制lambda表达式未来的使发展范围。

 

11.匿名类与 lambda表达式的区别:

1)在匿名类中,this
指代的是匿名类本身;而在lambda表达式中,this指代的是lambda表达式所在的这个类。

2)lambda表达式的类型是由上下文决定的,而匿名类中必须在创建实例的时候明确指定。

3)Lambda 表达式的编译方法是:Java 编译器编译 Lambda
表达式并将他们转化为类里面的私有函数,它使用invokedynamic指令(Java
7 ,即动态启用)动态绑定该方法。

 

java.util.function

Java SE
8中增加了一个新的包:java.util.function,它里面包含了常用的函数式接口,例如:

Predicate<T>——接收 T 并返回 boolean
Consumer<T>——接收 T,不返回值
Function<T, R>——接收 T,返回 R
Supplier<T>——提供 T 对象(例如工厂),不接收值
UnaryOperator<T>——接收 T 对象,返回 T
BinaryOperator<T>——接收两个 T,返回 T

JDK中的函数式接口

为了现有的类库能够直接使用 Lambda 表达式,Java 8
以前存在一些接口已经被标注为函数式接口的:

  • java.lang.Runnable
  • java.util.Comparator
  • java.util.concurrent.Callable
  • java.io.FileFilter
  • java.security.PrivilegedAction
  • java.beans.PropertyChangeListener

Java 8
中更是新增加了一个包 java.util.function,带来了常用的函数式接口:

  • Function<T, R> – 函数:输入 T 输出 R
  • BiFunction<T, U, R> – 函数:输入 T 和 U 输出 R 对象
  • Predicate<T> – 断言/判断:输入 T 输出 boolean
  • BiPredicate<T, U> – 断言/判断:输入 T 和 U 输出 boolean
  • Supplier<T> – 生产者:无输入,输出 T
  • Consumer<T> – 消费者:输入 T,无输出
  • BiConsumer<T, U> – 消费者:输入 T 和 U 无输出
  • UnaryOperator<T> – 单元运算:输入 T 输出 T
  • BinaryOperator<T> – 二元运算:输入 T 和 T 输出 T

另外还对基本类型的处理增加了更加具体的函数是接口,包括:BooleanSupplierDoubleBinaryOperatorDoubleConsumerDoubleFunction<R>DoublePredicateDoubleSupplierDoubleToIntFunctionDoubleToLongFunctionDoubleUnaryOperatorIntBinaryOperatorIntConsumerIntFunction<R>IntPredicateIntSupplierIntToDoubleFunctionIntToLongFunctionIntUnaryOperatorLongBinaryOperatorLongConsumer,LongFunction<R>LongPredicateLongSupplierLongToDoubleFunction,LongToIntFunctionLongUnaryOperatorToDoubleBiFunction<T, U>ToDoubleFunction<T>,ToIntBiFunction<T, U>ToIntFunction<T>ToLongBiFunction<T, U>ToLongFunction<T> 。结合上面的函数式接口,对这些基本类型的函数式接口通过类名就能一眼看出接口的作用。

四.Lambda表达式在Java8中的运行机制

 

Lambda表达式的类型是一些类似于Comparator的接口。但并不是每个接口都可以使用Lambda表达式,只有那些仅仅包含一个非实例化抽象方法的接口才能使用Lambda表达式。这样的接口被称为函数式接口。并且它们能够被@FunctionalInterface注解注释。Runnable接口就是一个典型的函数式接口的。

 

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

 

@FunctionalInterface注解不是必须的,但它能够让工具知道这一个接口是一个函数式接口并表现有意义的行为。

eg:若试着编译一个用@FunctionalInterface注释自己并且含有多个抽象方法的接口,编译就会报错Multiple
non-overriding abstract methods found

同样的,若给一个不含有任何方法的接口添加@FunctionalInterface注解,会报错No
target method found
.

 

 

Lambda表达式是采用动态启用(Java7)来延迟在运行时的加载策略。当javac编译代码时,它会捕获代码中的Lambda表达式并且生成一个动态启用的调用地址(称为Lambda工厂)。当动态启用被调用时,就会向Lambda表达式发生转换的地方返回一个函数式接口的实例。然后将Lambda表达式的内容转换到一个将会通过动态启用来调用的方法中。在这一步骤中,JVM实现者有自由选择策略的权利。

 

 

类型

需要注意的是,函数式接口的名称并不是 lambda
表达式的一部分。那么问题来了,对于给定的 lambda
表达式,它的类型是什么。
编译器负责推导 lambda 表达式类型。它利用 lambda 表达式所在上下文
所期待的类型 进行推导,这个 被期待的类型 被称为 目标类型。lambda
表达式只能出现在目标类型为函数式接口的上下文中。
当然,lambda 表达式对目标类型也是有要求的。编译器会检查 lambda
表达式的类型和目标类型的方法签名(method
signature)是否一致。当且仅当下面所有条件均满足时,lambda
表达式才可以被赋给目标类型 T:

T 是一个函数式接口
lambda 表达式的参数和 T 的方法参数在数量和类型上一一对应
lambda 表达式的返回值和 T 的方法返回值相兼容(Compatible)
lambda 表达式内所抛出的异常和 T 的方法 throws 类型相兼容

创建函数式接口

有时候我们需要自己实现一个函数式接口,做法也很简单,首先你要保证此接口只能有一个函数操作,然后在接口类型上标注注解 @FunctionalInterface 即可。

五.Lambda 作用域

在lambda表达式中访问外层作用域和旧版本的匿名对象中的方式很相似。可以直接访问标记了final的外层局部变量,或者实例的字段以及静态变量。

欲看详情请点击:Lambda 作用域

(注:鉴于此篇博客文字篇幅过长,容易视觉疲劳,第一次学习也容易被吓到等等原因,故将此详情内容单独写了篇博客。)

 

方法引用(Method references)

lambda
表达式允许我们定义一个匿名方法,并允许我们以函数式接口的方式使用它。我们也希望能够在
已有的 方法上实现同样的特性。
对于静态方法引用,我们需要在类名和方法名之间加入 :: 分隔符,例如
Integer::sum
其实是lambda表达式的一个简化写法,所引用的方法其实是lambda表达式的方法体实现,语法也很简单,左边是容器(可以是类名,实例名),中间是”::”,右边是相应的方法名。如下所示:

ObjectReference::methodName

Comparator<Person> byName = Comparator.comparing(Person::getName);

这里的 Person::getName 可以被看作为 lambda 表达式的简写形式。尽管方法引用不一定(比如在这个例子里)会把语法变的更紧凑,但它拥有更明确的语义——如果我们想要调用的方法拥有一个名字,我们就可以通过它的名字直接调用它。
方法引用有很多种,它们的语法如下
> 静态方法引用:ClassName::methodName
实例上的实例方法引用:instanceReference::methodName
超类上的实例方法引用:super::methodName
类型上的实例方法引用:ClassName::methodName
构造方法引用:Class::new
数组构造方法引用:TypeName[]::new

类型推导

类型推导是 Lambda 表达式的基础,类型推导的过程就是 Lambda
表达式的编译过程。以下面的代码为例:

Function<String, Integer> strToInt = str -> Integer.parseInt(str);

编译期间,我理解的类型推导的过程如下:

  1. 先确定目标类型 Function
  2. Function 作为函数式接口,其方法签名为:Integer apply(String t)
  3. 检测 str -> Integer.parseInt(str)
    是否与方法签名匹配(方法的参数类型、个数、顺序 和返回值类型)
  4. 如果不匹配,则报编译错误

这里的目标类型是关键,通过目标类型获取方法签名,然后和 Lambda
表达式做出对比。

六.捕获和非捕获的Lambda表达式

在lambda表达式中,可以访问其外层作用域定义的变量。
eg:lambda表达式可以使用其外层定义的实例或静态变量。lambda表达式也可以显示或隐式地访问this变量,该变量引用lambda表达式的外层类的调用的实例。因此,lambda表达式可以获取或设置其外层类的实例或静态变量的值,以及调用其外层类定义的方法。

 

Lambda表达式是否是捕获的和性能相关。一个非不捕获的Lambda通常比捕获的更高效,虽然这一点目前没有书面的规范说明(据我所知),而且也不能为了程序的正确性指望它做什么,非捕获的Lambda只需要计算一次.
然后每次使用到它都会返回一个唯一的实例。而捕获的lambda表达式每次使用时都需要重新计算一次,而且从目前实现来看,它非常像实例化一个匿名内部类的实例。

 

当Lambda表达式访问一个定义在Lambda表达式体外的非静态变量或者对象时,这个Lambda表达式称为“捕获的”。

当lambda表达式使用其外层作用域定义的局部变量时,会产生一种特殊的情况,称为变量捕获。在这种情况下,lambda表达式只能使用实质上final的局部变量。实质上final的变量是指在第一次赋值以后,值不再发生变化的变量(即在后面的程序中没有改变变量的值)。没有必要显示地将这种变量声明为final,不过声明为final也不是错误(Note:外层作用域的this参数自动是实质上final变量,lambda表达式没有自己的this参数)。

eg:下面这个Lambda表达式捕捉了变量x

int x = 5; return y -> x + y;

为了保证这个Lambda表达式声明是正确的,被它捕获的变量必须是“有效final”的。所以要么它们需要用final修饰符号标记,要么保证它们在赋值后不能被改变。

 

Lambda表达式不能修改外层作用域内的局部变量。因为修改局部变量会移除其实质上的final状态,从而使捕获该变量变得不合法。

eg:演示实质上的final的局部变量和可变局部变量的区别

 

interface MyFunc {
    int func(int n);
}
class VarCapture {
    int num = 10;
    MyFunc mylambda = (n) ->
    {
        //这里num的使用是正确的,它不修改num变量。
        int v = num + n;
        //但是,下面的num表达式是非法的,因为它试图修改num的值。
        //  num++;
        return v;
    };
    //下面的也会导致一个错误,因为这将消除num的有效的final状态
    //  num = 9;
}

分析:

 

num实质是final变量,所有可以在mylambda内使用。但是,如果修改了num,不管是在lambda表达式内还是表达式外,num就会丢失其实质上final的状态。这会导致发生错误,程序将无法通过编译。

Note:lambda表达式可以使用和修改调用其调用类的实例变量,只是不能使用其外层作用域内的局部变量,除非该变量实质上是final变量

 

方法引用

方法引用(Method
Reference)的基础同样是函数式接口,可以直接作为函数式接口的实现,与
Lambda
表达式有相同的作用,同样依赖于类型推导。方法引用可以看作是只调用一个方法的
Lambda 表达式的简化。

方法引用的语法为: Type::methodName 或者 instanceName::methodName ,
构造函数对应的 methodName 为 new。

例如上面曾用到例子:

Function<String, Integer> strToInt = str -> Integer.parseInt(str);

对应的方法引用的写法为

Function<String, Integer> strToInt = Integer::parseInt;

根据方法的类型,方法引用主要分为一下几种类型,构造方法引用、静态方法引用、实例上实例方法引用、类型上实例方法引用等

七.Lambdas不能做的事

记住:有一些Lambdas不提供的特性。为了Java
8,它们被考虑到了,但是没有被包括进去,由于简化以及时间限制的原因。

1.Non-final* 变量捕获

如果一个变量被赋予新的数值,它将不能被用于lambda之中。”final”关键字不是必需的,但变量必须是“有效final”的(上面讨论过)。下面代码不会被编译:

eg:

 

int count = 0;
List strings = Arrays.asList("a", "b", "c");
strings.forEach(s -> {
    count++; // error: 不能修改count值
});

 

2.例外的透明度

如果一个已检测的例外可能从Lambda内部抛出,功能性的接口也必须声明已检测例外可以被抛出。这种例外不会散布到其包含的方法。下面代码不会被编译:

eg:

 

    void appendAll(Iterable values, Appendable out) throws IOException { // doesn't help with the error
        values.forEach(s -> {
            out.append(s); // error: can't throw IOException here 
            // Consumer.accept(T) doesn't allow it
        });
    }

 

有绕过这个的办法,可以定义自己的功能性接口,扩展Consumer的同时通过像RuntimeException类抛出
IOException。

3.控制流程 (break, early return)

在上面的 forEach例子中,传统的继续方式有可能通过在Lambda之内放置
“return;”来实现。但是,没有办法中断循环或者从Lambda中通过包含方法的结果返回一个数值。

eg:

 

final String secret = "foo"; 
boolean containsSecret(Iterable values) {
    values.forEach(s -> { 
  if (secret.equals(s)) {
            ??? // want to end the loop and return true, but can't 
   }
    });
}

 

 

构造方法引用

语法为: Type::new 。 如下面的函数为了将字符串转为数组

方法引用写法

Function<String, Integer> strToInt = Integer::new;

Lambda 写法

Function<String, Integer> strToInt = str -> new Integer(str);

传统写法

Function<String, Integer> strToInt = new Function<String, Integer>() {
    @Override
    public Integer apply(String str) {
        return new Integer(str);
    }
};

八.函数式通用接口的出现

 

想使用 Lambda
表达式,需要定义一个函数式接口,这样往往会让程序充斥着过量的仅为
Lambda 表达式服务的函数式接口。为了减少这样过量的函数式接口,Java 8 在
java.util.function
中增加了不少新的函数式通用接口,有UnaryOperator,BinaryOperator,Consumer,Supplier,Function,Predicate等等。

 

1.Predicate :将 T
作为输入。用来定义对一些条件的检查。Predicate接口有一个叫test的方法,它需要一个T类型的值,返回值为布尔类型。该接口包含多种默认方法来将
Predicate 组合成其他复杂的逻辑(与、或、非)。

eg:在一个names列表中找出所有以a开头的name,可以这样使用Predicate。

 

Predicate nameWithA= name -> name.startsWith(“a”);

 

2.Consumer:将 T
作为输入,不返回任何内容,表示在单个参数上的操作。

 

用于表现那些不需要产生任何输出的行为。Consumer接口中有一个叫做accept的方法,它需要一个T类型的参数并且没有返回值。

 

eg:输出指定信息

 

Consumer messageConsumer = message ->System.out.println(message);

 

3.Function:将 T 作为输入,返回 R
作为输出,它还包含了与其他函数组合的默认方法。需要一个值并返回一个结果。

 

eg:如果需要将所有names列表中的name转换为大写,可以像下面这样写一个Function:

 

Function toUpperCase = name -> name.toUpperCase();

 

 

4.Supplier: 这个函数式接口不需要传值,但是会返回一个值。

 

eg: 用来生成唯一的标识符

 

Supplier uuidGenerator= () -> UUID.randomUUID().toString();

 

5.BinaryOperator:两个T作为输入,返回一个T作为输出,对于“reduce”操作很有用

 

这些最原始的特征同样存在。他们以int,long和double的方式提供。

 

eg:IntConsumer-以int作为输入,执行某种动作,没有返回值

 

这里存在性能上的一些原因,主要是在输入或输出的时候避免装箱和拆箱操作。

8 新特性:Lambda 表达式 ——诺诺涂鸦记忆
Lambda 表达式
(注:此文乃个人查找资料然后学习总结的,若有不对的地方,请大家指出,非…

数组构造方法引用

语法为: Type[]::new 。如下面的函数为了构造一个指定长度的字符串数组

方法引用写法

Function<Integer, String[]> fixedArray = String[]::new;

方法引用写法

Function<Integer, String[]> fixedArray = length -> new String[length];

传统写法

Function<Integer, String[]> fixedArray = new Function<Integer, String[]>() {
    @Override
    public String[] apply(Integer length) {
        return new String[length];
    }
};

静态方法引用

语法为: Type::new 。 如下面的函数同样为了将字符串转为数组

方法引用写法

Function<String, Integer> strToInt = Integer::parseInt;

Lambda 写法

Function<String, Integer> strToInt = str -> Integer.parseInt(str);

传统写法

Function<String, Integer> strToInt = new Function<String, Integer>() {
    @Override
    public Integer apply(String str) {
        return Integer.parseInt(str);
    }
};

实例上实例方法引用

语法为: instanceName::methodName 。如下面的判断函数用来判断给定的姓名是否在列表中存在

List<String> names = Arrays.asList(new String[]{"张三", "李四", "王五"});
Predicate<String> checkNameExists = names::contains;
System.out.println(checkNameExists.test("张三"));
System.out.println(checkNameExists.test("张四"));

类型上实例方法引用

语法为: Type::methodName 。运行时引用是指上下文中的对象,如下面的函数来返回字符串的长度

Function<String, Integer> calcStrLength = String::length;
System.out.println(calcStrLength.apply("张三"));
List<String> names = Arrays.asList(new String[]{"zhangsan", "lisi", "wangwu"});
names.stream().map(String::length).forEach(System.out::println);

又比如下面的函数已指定的分隔符分割字符串为数组

BiFunction<String, String, String[]> split = String::split;
String[] names = split.apply("zhangsan,lisi,wangwu", ",");
System.out.println(Arrays.toString(names));

Stream 对象

概念

什么是 Stream ? 这里的 Stream 不同于 io 中的 InputStream 和
OutputStream,Stream 位于包 java.util.stream 中, 也是 java 8
新加入的,Stream
只的是一组支持串行并行聚合操作的元素,可以理解为集合或者迭代器的增强版。什么是聚合操作?简单举例来说常见的有平均值、最大值、最小值、总和、排序、过滤等。

Stream 的几个特征:

  • 单次处理。一次处理结束后,当前Stream就关闭了。
  • 支持并行操作

常见的获取 Stream 的方式

  • 从集合中获取
    • Collection.stream();
    • Collection.parallelStream();
  • 静态工厂
    • Arrays.stream(array)
    • Stream.of(T …)
    • IntStream.range()

这里只对 Stream 做简单的介绍,下面会有具体的应用。要说 Stream 与 Lambda
表达式有什么关系,其实并没有什么特别紧密的关系,只是 Lambda
表达式极大的方便了 Stream 的使用。如果没有 Lambda 表达式,使用 Stream
的过程中会产生大量的匿名类,非常别扭。

举例

以下的demo依赖于 Employee 对象,以及由 Employee 对象组成的 List 对象。

public class Employee {

    private String name;
    private String sex;
    private int age;

    public Employee(String name, String sex, int age) {
        super();
        this.name = name;
        this.sex = sex;
        this.age = age;
    }
    public String getName() {
        return name;
    }

    public String getSex() {
        return sex;
    }
    public int getAge() {
        return age;
    }
    @Override
    public String toString() {
        StringBuilder builder = new StringBuilder();
        builder.append("Employee {name=").append(name).append(", sex=").append(sex).append(", age=").append(age)
                .append("}");
        return builder.toString();
    }   
}

List<Employee> employees = new ArrayList<>();
employees.add(new Employee("张三", "男", 25));
employees.add(new Employee("李四", "女", 24));
employees.add(new Employee("王五", "女", 23));
employees.add(new Employee("周六", "男", 22));
employees.add(new Employee("孙七", "女", 21));
employees.add(new Employee("刘八", "男", 20));

打印所有员工

Collection 提供了 forEach 方法,供我们逐个操作单个对象。

employees.forEach(e -> System.out.println(e)); 
或者
employees.stream().forEach(e -> System.out.println(e));

按年龄排序

Collections.sort(employees, (e1, e2) -> e1.getAge() - e2.getAge());
employees.forEach(e -> System.out.println(e));
或者
employees.stream().sorted((e1, e2) -> e1.getAge() - e2.getAge()).forEach(e -> System.out.println(e));

打印年龄最大的女员工

max/min 返回指定排序条件下最大/最小的元素

Employee maxAgeFemaleEmployee = employees.stream()
        .filter(e -> "女".equals(e.getSex()))
        .max((e1, e2) -> e1.getAge() - e2.getAge())
        .get();
System.out.println(maxAgeFemaleEmployee);

打印出年龄大于20 的男员工

filter 可以过滤出符合条件的元素

employees.stream()
        .filter(e -> e.getAge() > 20 && "男".equals(e.getSex()))
        .forEach(e -> System.out.println(e));

打印出年龄最大的2名男员工

limit 方法截取有限的元素

employees.stream()
        .filter(e -> "男".equals(e.getSex()))
        .sorted((e1, e2) -> e2.getAge() - e1.getAge())
        .limit(2)
        .forEach(e -> System.out.println(e));

打印出所有男员工的姓名,使用 , 分隔

map 将 Stream 中所有元素的执行给定的函数后返回值组成新的 Stream

String maleEmployeesNames = employees.stream()
        .map(e -> e.getName())
        .collect(Collectors.joining(","));
System.out.println(maleEmployeesNames);

统计信息

IntSummaryStatistics, DoubleSummaryStatistics, LongSummaryStatistics
包含了 Stream 中的汇总数据。

IntSummaryStatistics stat = employees.stream()
        .mapToInt(Employee::getAge).summaryStatistics();
System.out.println("员工总数:" + stat.getCount());
System.out.println("最高年龄:" + stat.getMax());
System.out.println("最小年龄:" + stat.getMin());
System.out.println("平均年龄:" + stat.getAverage());

总结

Lambda
表达式确实可以减少很多代码,能提高生产力,当然也有弊端,就是复杂的表达式可读性会比较差,也可能是还不是很习惯的缘故吧,如果习惯了,相信会喜欢上的。凡事都有两面性,就看我们如何去平衡这其中的利弊了,尤其是在一个团队中。

发表评论

电子邮件地址不会被公开。 必填项已用*标注