14.1lambda表达式简介
lambda表达式可以用非常少的代码实现抽象方法。lambda表达式不能独立执行,因此,必须实现函数式接口,并且会返回一个函数式接口的对象。lambda表达式的语法非常特殊,格式如下。
()->结果表达式数
->结果表达式
(参数1.参数2....,参数n)->结果表达式
☑ 第1行实现无参方法,单独写一对圆括号表示方法无参数,操作符右侧的结果表达式表示方
法的返回值。
第2行实现只有一个参数的方法,参数可以写在圆括号里,或者不写圆括号。
☑第3行实现多参数的方法,所有参数按顺序写在圆括号里,且圆括号不可以省略。 lambda表达式也可以实现复杂方法,将操作符右侧的结果表达式换成代码块即可,语法格式如下:
()->{代码块}
参数->{代码块}
(参数1、参数2,...,参数 n)->{代码块}
☑第1行实现无参方法,方法体是操作符右侧代码块。
☑第2行实现只有一个参数的方法,方法体是操作符右侧代码块。☑第3行实现多参数的方法,方法体是操作符右侧代码块。
lambda 表达式的语法非常抽象,并且有着非常强大的自动化功能,如自动识别泛型、自动数据类型转换等,这会让初学者很难掌握。如果将lambda表达式的功能归纳总结,可以将lambda表达式语法用如下方式理解:
( ) -> {代码块}
这个方法 按照 这样的代码来实现
简单总结:操作符左侧的是方法参数,操作符右侧的是方法体。
误区警示 “->”符号是由英文状态下的“-”和“>”组成的,符号之间没有空隔。
14.1.2lambda表达式实现函数接口
1、函数式接口
函数式接口指的是仅包含一个抽象方法的接口,接口中的方法简单明了地说明了接口的用途,如线程接口Runnable、动作事件监听接口ActionListener等。开发者可以创建自定义的函数式接口,例如:
interface MyInterface {
void method():
如果接口中包含一个以上的抽象方法,则不符合函数式接口的规范,这样的接口不能用lambda 表达式创建匿名对象。本章内容中所有被lambda表达式实现的接口均为函数式接口。
2.lambda 表达式实现无参抽象方法
很多函数式接口的抽放方法是无参数的,如线程接口Runnable接口只有一个run()方法,这样的无参抽象方法在lambda表达式中使用“()”表示。
3. lambda 表达式实现有参抽象方法
抽象方法中有一个或多个参数的函数式接口也是很常见的,lambda表达式中可以用“(al,a2,a3)”的方法表示有参抽象方法,圆括号里标识符对应抽象方法的参数。如果抽象方法中只有一个参数, lambda 表达式则可以省略圆括号。
4.lambda 表达式使用代码块
当函数式接口的抽象方法需要实现复杂逻辑而不是返回一个简单的表达式的话,就需要在lambda表达式中使用代码块。lambda表达式会自动判断返回值类型是否符合抽象方法的定义。
14.1.3 lambda 表达式调用外部变量
lambda 表达式除了可以调用定义好的参数,还可以调用表达式以外的变量。但是,这些外部的变量有些可以被更改,有些则不能。例如,lambda表达式无法更改局部变量的值,但是却可以更改外部类的成员变量(也可以叫作类属性)的值。
1. lambda 表达式无法更改局部变量
局部变量在lambda 表达式中默认被定义为final(静态)的,也就是说,lambda表达式只能调用局部变量,却不能改变其值。
14.2方法的引用
lambda 表达式还添加了一类新语法,用来引用方法,也就是说方法也可以作为一个对象被调用。根据不同的方法类型,方法的引用包括引用静态方法、引用成员方法和引用构造方法等。
14.2.1 引用静态方法
引用静态方法的语法如下:类名::静态方法名
这个语法中出现了一个新的操作符“::”,这是由两个英文冒号组成的操作符,冒号之间没有空格。这个操作符左边表示方法所属的类名,右边是方法名。需要注意的是,这个语法中方法名是没有圆括号的。
14.2.2引用成员方法
语法如下:
14.2.2引用成员方法
语法如下:
操作符左侧必须是一个对象名,不是类名。
14.2.3引用带泛型的方法
14.2.4引用构造方法
lambda 表达式有3种引用构造方法的语法,分别是引用无参构造方法、引用有参构造方法和引用数组构造方法,下面分别进行讲解。
1.引用无参构造方法
引用构造方法的语法如下:
类名::new
因为构造方法与类名相同,如果操作符左右都写类名,会让操作符误以为是在引用与类名相同的静态方法,这样会导致程序出现Bug,所以引用构造方法的语法使用了new关键字。操作符右侧的写 new 关键字,表示引用构造方法。这个语法有一点要注意;ew关键字之后没有圆括号,也没有参数的定义。如果类中既有无参构造方法,又有有参构造方法,使用引用构造方法语法后,究竟哪一个方法被引用了呢?引用哪个构造方法是由函数式接口决定的,“::”操作符会返回与抽象方法的参数结构相同的构造方法。如果找不到参数接口相同的构造方法,则会发生编译错误。
若接口方法无参数,调用的就是无参的构造方法。
2. 引用有参构造方法
引用有参构造方法的语法与引用无参构造方法一样。区别就是函数
14.2.5Funtion接口
14.3流处理
14.3.1stream接口简介
流处理的接口都定义在java.uil.stream包下。Stream是泛型接口。
Collection 接口新增两个可以获取流对象的方法。第一个方法最常用,可以获取集合的顺序流,方法如下:
Stream<E> stream();
第二个方法可以获取集合的并行流,方法如下:
Stream<E>parallelstream();
因为所有集合类都是Collection接口的子类,如ArrayList类、HashSet类等,所以这些类都可以进行流处理。例如:
接口,但最常用的是 List<Integer> list = new ArrayList<Integer>(); //创建集合
Stream<Integer>s = list.stream(); //获取集合流对象
14.3.3Collection类
Collectors 类为收集器类,该类实现了java.,util.Collector接口,可以将Stream流对象进行各种各样的封装、归集、分组等操作。同时,Collectors类还提供了很多实用的数据加工方法,如数据统计计算等。
14.3.4数据过滤
数据过滤就是在杂乱的数据中筛选出需要的数据,类似SQL语句中的WHERE关键字,给出一定
的条件,将符合条件的数据过滤并 重新 展示出来。
1.filter()方法
filter()方法是Stream接口提供的过滤方法。该方法可以将lambda表达式作为参数,然后按照lambda表达式的逻辑过滤流中的元素。过滤出想要的流元素后,还需使用Stream提供的collect()方法按照指定方法重新封装。
2、distinct()方法
distinet()方法是 Stream接口提供的过滤方法。该方法可以去除流中的重复元素,效果与SQL语句中的 DISTINCT关键字一样。因为 distinct()方法属于中间操作,所以可以配合filter()方法一起使用。
3. limit()方法
limit()方法是Stream接口提供的方法,该方法可以获取流中前N个元素。
4. skip()方法
skip()方法是 Stream接口提供的方法,该方法可以忽略流中的前N个元素。
14.3.5 数据映射
数据的映射和过滤概念不同:过滤是在流中找到符合条件的元素,映射是在流中获得具体的数据。 Stream 接口提供了map0方法用来实现数据映射,map()方法会按照参数中的函数逻辑获取新的流对象,新的流对象中元素类型可能与旧流对象元素类型不相同。
14.3.7 数据收集
数据收集可以理解为高级的“数据过滤+数据映射”,是对数据的深加工。本节将讲解两种实用场景:数据统计和数据分组。
1.数据统计
数据统计不仅可以筛选出特殊元素,还可以对元素的属性进行统计计算。这种复杂的统计操作不是由Stream 实现的,而是由Collectors收集器类实现的,收集器提供了非常丰富的API,有着强大的数据挖掘能力。
2.数据分组
数据分组就是将流中元素按照指定的条件分开保存,类似SQL语言中的“GROUP BY”关键字。分组之后的数据会按照不同的标签分别保存成一个集合,然后按照“键-值”关系封装在Map对象中。
数据分组有一级分组和多级分组两种场景,首先先来介绍一级分组。
一级分组,就是将所有数据按照一个条件进行归类。例如,学校有100个学生,这些学生分布在3个年级中。学生按照年级分成了3组,然后就不再细分了,这就属于一级分组。
Collectors 类提供的 groupingBy()方法就是用来进行分组的方法,方法参数是一个Function接口对象,收集器会按照指定的函数规则对数据进行分组。