什么是lambda表达式

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

简单的用实际代码来看一下:

不采用Lambda的老方法:

Runnable runnable1=new Runnable(){
@Override
public void run(){
    System.out.println("Running without Lambda");
}
};

使用Lambda:

Runnable runnable2=()->System.out.println("Running from Lambda");

了解完之后,我们还需要了解一下什么是函数式接口

什么是函数式接口?这个是我们理解Lambda表达式的重点,也是产生lambda表达式的“母体”,这里我们引用一个比较容易理解的说法:函数式接口是 一个只有一个抽象方法(不包含object中的方法)的接口。
这个需要说明一点,就是在Java中任何一个对象都来自Object 所有接口中自然会继承自Object中的方法,但在判断是否是函数式接口的时候要排除Object中的方法,下面举几个例子如下:
 

//这个是函数式接口
interface eat 
{  
   void eatFood();    
}
 
 
//这个不是是函数式接口
interface eat
{  
  default void eatFood()
  {
     System.out.println("hello");
  };    
}
 
 
 
//这个是一个函数式接口
interface eat 
{  
 void eatFood();    
 String toString();
}

 对于是否是函数式接口,java8中也提供了一个专用的注解:@FunctionalInterface。通过这个注解,可以确定是否是函数式接口

//此处会报编译错误
@FunctionalInterface
interface eat 
{  
  default void eatFood()
  {
    System.out.println("hello");
  };    
}

Lambda表达式的语法

基本语法:

Lambda 表达式的基础语法 : Java8 中引入了一个新的操作符 “->” 该操作符称为箭头操作符或 Lambda 操作符,箭头操作符将 Lambda 表达式拆分成两部分 :
左侧 : Lambda 表达式的参数列表
右侧 : Lambda 表达式中所需执行的功能, 即 Lambda 体

语法格式一 : 无参数,无返回值
() -> System.out.println(“Hello Lambda!”);

语法格式二 : 有一个参数,并且无返回值
(x) -> System.out.println(x)

语法格式三 : 若只有一个参数,小括号可以省略不写
x -> System.out.println(x)

Consumer con = (x) -> System. out .println(x);
con.accept( “啦啦啦,我是卖报的小行家” );

语法格式四 : 有两个以上的参数,有返回值,并且 Lambda 体中有多条语句
Comparator com = (x, y) -> {
System.out.println(“函数式接口”);
return Integer.compare(x, y);
};

语法格式五 : 若 Lambda 体中只有一条语句, return 和 大括号都可以省略不写
Comparator com = (x, y) -> Integer.compare(x, y);

语法格式六 : Lambda 表达式的参数列表的数据类型可以省略不写,因为JVM编译器通过上下文推断出,数据类型,即“类型推断”
(Integer x, Integer y) -> Integer.compare(x, y);
注 : Lambda 表达式中的参数类型都是由编译器推断得出的。 Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。 Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”。

Lambda表达式作用

1.替代匿名内部类

毫无疑问,lambda表达式用得最多的场合就是替代匿名内部类,而实现Runnable接口是匿名内部类的经典例子。lambda表达式的功能相当强大,用()->就可以代替整个匿名内部类!请看代码:

如果使用匿名内部类:

@Test
    public void oldRunable() {
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("The old runable now is using!");
            }
        }).start();
    }

而如果使用lambda表达式:

@Test
    public void runable() {
        new Thread(() -> System.out.println("It's a lambda function!")).start();
    }

2.使用lambda表达式对集合进行迭代

Java的集合类是日常开发中经常用到的,甚至说没有哪个java代码中没有使用到集合类。。。而对集合类最常见的操作就是进行迭代遍历了。请看对比:

@Test
    public void iterTest() {
        List<String> languages = Arrays.asList("java","scala","python");
        //before java8
        for(String each:languages) {
            System.out.println(each);
        }
        //after java8
        languages.forEach(x -> System.out.println(x));
        languages.forEach(System.out::println);
    }

Stream操作

我们来认识一下Lambda的好兄弟Stream,这里的Stream并不是输入流和输出流(IO),而它也是jdk8后的新特性,是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
简单来讲,Stream就是JAVA8提供给我们的对于元素集合统一、快速、并行操作的一种方式。它能充分运用多核的优势,以及配合lambda表达式、链式结构对集合等进行许多有用的操作。
在这里,我们只简单介绍几个主要方法:

1、实体类:

package com.money.pojo;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
 
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Person {
    private String name;
    private String sex;
    private int age;
    private String province;
    private String city;
}

2. Steam流操作

package com.money.service;
 
import com.money.pojo.Person;
 
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
 
public class StreamDemo {
    public static void main(String[] args) {
        // 添加集合
        List<Person> personList = new ArrayList<>();
        personList.add(new Person("王安石","man",24,"江西","抚州"));
        personList.add(new Person("黄庭坚","man",25,"江西","九江"));
        personList.add(new Person("程长文","woman",22,"江西","上饶"));
        personList.add(new Person("文天祥","man",25,"江西","吉安"));
        personList.add(new Person("欧阳修","man",24,"江西","吉安"));
        personList.add(new Person("顾恺之","man",28,"江苏","无锡"));
        personList.add(new Person("李煜","man",29,"江苏","徐州"));
        personList.add(new Person(" 刘邦","man",28,"江苏","徐州"));
        personList.add(new Person("赵飞燕","woman",21,"江苏","苏州"));
        personList.add(new Person("周恩来","man",24,"江苏","淮安"));
 
        // 过滤
        List<Person> list1 = personList.stream().filter(p -> p.getProvince().equals("江苏")).collect(Collectors.toList());
        System.out.println("list1 = " + list1);
 
        // 映射
        List<String> list2 = personList.stream().map(Person::getName).collect(Collectors.toList());
        System.out.println("list2 = " + list2);
 
        // 分部,按照性别
        Map<Boolean, List<Person>> listMap = personList.stream().collect(Collectors.partitioningBy(person -> person.getSex().equals("woman")));
        System.out.println("listMap = " + listMap);
 
        // 分组, 按照省份和城市
        Map<String, Map<String, List<Person>>> cityMap = personList.stream().collect(Collectors.groupingBy(Person::getProvince, Collectors.groupingBy(Person::getCity)));
        System.out.println("cityMap = " + cityMap);
 
        // 平均值
        Map<String, Map<String, Double>> avgAgeMap = personList.stream().collect(Collectors.groupingBy(Person::getProvince, Collectors.groupingBy(Person::getCity, Collectors.averagingInt(Person::getAge))));
        System.out.println("avgAgeMap = " + avgAgeMap);
 
        // 排序
        Map<String, List<Person>> ageSort = personList.stream().collect(Collectors.groupingBy(Person::getSex, Collectors.collectingAndThen(Collectors.toList(), s -> {
            s.sort(Comparator.comparing(Person::getAge));
            return s;
        })));
        System.out.println("ageSort = " + ageSort);
    }
}

3.实现效果

过滤出江苏省:

list1 = [Person(name=顾恺之, sex=man, age=28, province=江苏, city=无锡), Person(name=李煜, sex=man, age=29, province=江苏, city=徐州), Person(name= 刘邦, sex=man, age=28, province=江苏, city=徐州), Person(name=赵飞燕, sex=woman, age=21, province=江苏, city=苏州), Person(name=周恩来, sex=man, age=24, province=江苏, city=淮安)]

映射姓名:
list2 = [王安石, 黄庭坚, 程长文, 文天祥, 欧阳修, 顾恺之, 李煜,  刘邦, 赵飞燕, 周恩来]

按照性别分部:
listMap = {false=[Person(name=王安石, sex=man, age=24, province=江西, city=抚州), Person(name=黄庭坚, sex=man, age=25, province=江西, city=九江), Person(name=文天祥, sex=man, age=25, province=江西, city=吉安), Person(name=欧阳修, sex=man, age=24, province=江西, city=吉安), Person(name=顾恺之, sex=man, age=28, province=江苏, city=无锡), Person(name=李煜, sex=man, age=29, province=江苏, city=徐州), Person(name= 刘邦, sex=man, age=28, province=江苏, city=徐州), Person(name=周恩来, sex=man, age=24, province=江苏, city=淮安)], true=[Person(name=程长文, sex=woman, age=22, province=江西, city=上饶), Person(name=赵飞燕, sex=woman, age=21, province=江苏, city=苏州)]}

按照省份和城市分组:
cityMap = {江苏={徐州=[Person(name=李煜, sex=man, age=29, province=江苏, city=徐州), Person(name= 刘邦, sex=man, age=28, province=江苏, city=徐州)], 淮安=[Person(name=周恩来, sex=man, age=24, province=江苏, city=淮安)], 无锡=[Person(name=顾恺之, sex=man, age=28, province=江苏, city=无锡)], 苏州=[Person(name=赵飞燕, sex=woman, age=21, province=江苏, city=苏州)]}, 江西={上饶=[Person(name=程长文, sex=woman, age=22, province=江西, city=上饶)], 抚州=[Person(name=王安石, sex=man, age=24, province=江西, city=抚州)], 吉安=[Person(name=文天祥, sex=man, age=25, province=江西, city=吉安), Person(name=欧阳修, sex=man, age=24, province=江西, city=吉安)], 九江=[Person(name=黄庭坚, sex=man, age=25, province=江西, city=九江)]}}

按照城市求年龄平均值:
avgAgeMap = {江苏={徐州=28.5, 淮安=24.0, 无锡=28.0, 苏州=21.0}, 江西={上饶=22.0, 抚州=24.0, 吉安=24.5, 九江=25.0}}

按照性别分组后再按年龄排序:
ageSort = {woman=[Person(name=赵飞燕, sex=woman, age=21, province=江苏, city=苏州), Person(name=程长文, sex=woman, age=22, province=江西, city=上饶)], man=[Person(name=王安石, sex=man, age=24, province=江西, city=抚州), Person(name=欧阳修, sex=man, age=24, province=江西, city=吉安), Person(name=周恩来, sex=man, age=24, province=江苏, city=淮安), Person(name=黄庭坚, sex=man, age=25, province=江西, city=九江), Person(name=文天祥, sex=man, age=25, province=江西, city=吉安), Person(name=顾恺之, sex=man, age=28, province=江苏, city=无锡), Person(name= 刘邦, sex=man, age=28, province=江苏, city=徐州), Person(name=李煜, sex=man, age=29, province=江苏, city=徐州)]}

4、最后介绍另外几个简单的方法:

1)limit(截断)
对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素

2)distinct(去重)
对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素

3)count(统计)
count方法是一个流的终点方法,可使流的结果最终统计,返回int,比如我们计算一下满足18岁的总人数
 

int countOfAdult=persons.stream()
                       .filter(p -> p.getAge() > 18)
                       .map(person -> new Adult(person))
                       .count();

 4)Collect(收集流的结果)

collect方法也是一个流的终点方法,可收集最终的结果

List adultList= persons.stream()
                       .filter(p -> p.getAge() > 18)
                       .map(person -> new Adult(person))
                       .collect(Collectors.toList());

stream与函数式接口Predicate配合

除了在语言层面支持函数式编程风格,Java 8也添加了一个包,叫做 java.util.function。它包含了很多类,用来支持Java的函数式编程。其中一个便是Predicate,使用 java.util.function.Predicate 函数式接口以及lambda表达式,可以向API方法添加逻辑,用更少的代码支持更多的动态行为。Predicate接口非常适用于做过滤。
 

public static void filterTest(List<String> languages, Predicate<String> condition) {
    languages.stream().filter(x -> condition.test(x)).forEach(x -> System.out.println(x + " "));
}

public static void main(String[] args) {
    List<String> languages = Arrays.asList("Java","Python","scala","Shell","R");
    System.out.println("Language starts with J: ");
    filterTest(languages,x -> x.startsWith("J"));
    System.out.println("\nLanguage ends with a: ");
    filterTest(languages,x -> x.endsWith("a"));
    System.out.println("\nAll languages: ");
    filterTest(languages,x -> true);
    System.out.println("\nNo languages: ");
    filterTest(languages,x -> false);
    System.out.println("\nLanguage length bigger three: ");
    filterTest(languages,x -> x.length() > 4);
}

最后的输出结果:

Language starts with J: 
Java 

Language ends with a: 
Java 
scala 

All languages: 
Java 
Python 
scala 
Shell 

No languages: 

Language length bigger three: 
Python 
scala 
Shell