1 函数式接口和lambda表达式
函数式接口
函数式接口就是有且只有一个抽象方法的接口,需要注意的是,如果方法覆盖了Object的方法,那么不会被认为是一个抽象方法。如果需要指定接口就是函数式接口,可以使用@FunctionalInterface注解,使用该注解,编译器会按照函数式接口的定义来检查接口,如果不满足定义,会抛出异常。也可以不使用该注解,但是只要该接口只有一个抽象方法,编译器也会认为该接口是函数式接口。
java8已经提供了很多的函数式接口了,基本不需要我们自己定义。
如下是部分函数式接口:
Function
Predicate
Consumer
Supplier
UnaryOperator
lambda表达式
lambda表达式就是基于函数式接口实现的,lambd语法接口如下:
(参数)-> {执行体}
(参数)-> {执行体} 可以看作传统的方法格式 方法名(参数){执行体} ,只是省略了方法名而已 。
在lambda表达式出现之前,都是使用匿名类来实现接口并重写其的方法,代码比较臃肿,而有了lambda表达式之后,使用起来就比较简短了。例如重写java8提供的函数式接口Function
匿名类的方式:
Function fun2 = new Function() {
@Override
public Integer apply(Integer from) {
return from*3;
}
};
System.out.println(fun2.apply(3));
lambda的方式:
Function fun = (Integer from) -> from * 3;
System.out.println(fun.apply(3));
以上两种方式的输出结果都是 9;
lambda的一些特性:
1)编译器自动推导参数类型与返回类型。这个特性需要结合函数式接口来解释,例如这里的Function
Function
2)所以根据编译器自动推导参数类型的特性,lambda的特性2就是可以不用指定参数类型,因为它能自动推导。所以以上lambda的方式还可以写成如下方式,
Function fun = (from) -> from * 3;
System.out.println(fun.apply(3));
并且,java8为了更简洁一点,当该函数式接口的抽象方法只有一个参数时,可以去掉括号,写成如下方式:
Function fun = from -> from * 3;
System.out.println(fun.apply(3));
3)还有一个特性就是当方法体只有一句时可以不用使用return返回值,也可以不用{}括起来。
个人理解lambda最大的作用就是替换掉了使用匿名类来创建接口的子类对象并实现方法这样臃肿的方式,使得代码变得更加的简洁。
2 Stream
java8的引进的一种新的特性,流,它主要对数据进行筛选、排序等操作。使用得最多的就是集合和数组。
Stream的使用主要是三个步骤分别是获取流对象、流的中间操作(对数据的操作,可以调用多种操作,形成中间操作链)、终止操作(终止操作是执行中间操作链,产生结果)。只有执行终止操作才会具体执行中间操作,这个就是所谓的惰性求值。
并且,还需要了解的是,stream不会存储数据,一般都会执行中间操作后返回结果。
2.1 获取流对象(常用的)
获取数组的流
Integer[] array = new Integer[]{3,4,8,16,19,27,23,99};
Stream arrayStream = Arrays.stream(array);
获取集合的流
ArrayList list = new ArrayList<>();
final Stream stream = list.stream();
由于集合的父接口Collection实现了stream()方法,所以任何集合都可以通过stream()获取到流。
2.2 流的中间操作
Stream filter(Predicate super T> predicate);
过滤方法,该方法会将流里面满足条件的对象过滤出来并返回一个流对象(没调用流的终止操作,filter是不会执行的,这里的终止操作是count(),执行中间操作,返回流中剩下元素个数)。
list.add(new User("张三",22));
list.add(new User("李四",25));
list.add(new User("王麻子",27));
list.add(new User("小明",33));
//输出年龄大于等于25的元素个数
long count = list.stream().filter(u -> u.getAge() >= 25).count();
System.out.println(count);
2.2.1 Stream map(Function super T, ? extends R> mapper);
该方法的作用是对元素进行类型转换,返回我们需要的类型的集合。如下代码是获取用户名集合。
ArrayList list = new ArrayList<>();
list.add(new User("张三",22));
list.add(new User("李四",25));
list.add(new User("王麻子",27));
list.add(new User("小明",33));
//获取姓名集合
List names = list.stream().map(u -> u.getName()).collect(Collectors.toList());
names.forEach(System.out::println);
需要注意的是map(Function super T, ? extends R>)这个方法,这里传入的是lambda表达式 u -> u.getName() ,里面的参数u也就是Function super T, ? extends R>的泛型T由list对象的泛型决定,这里就是User类型,而Function super T, ? extends R>的R由u.getName(),执行后返回的类型是什么就是什么类型的集合。可以加深对编译器自动推导lambda表达式的参数类型与返回值类型的特性的理解。
2.2.2 Stream sorted(Comparator super T> comparator);
按年龄从小到大排序(默认)
ArrayList list = new ArrayList<>();
list.add(new User("张三",22));
list.add(new User("李四",18));
list.add(new User("王麻子",14));
list.add(new User("小明",33));
//获取升序集合
List collect = list.stream()
.sorted(Comparator.comparing(u -> u.getAge()))
.collect(Collectors.toList());
collect.forEach(System.out::println);
排序需要用到Comparator的一些方法,Comparator常用方法如下(例子都是以上面代码为例子):
public static
List collect = list.stream()
.sorted(Comparator.comparing(u -> u.getAge(),Comparator.reverseOrder()))
.collect(Collectors.toList());
public static
public static
不知道具体比较类型时使用该方法进行比较,虚拟机能自动推导出具体类型。需要注意的是最终比较的类型必须实现了Comparator接口,常见的Integer与String都实现了Comparator该接口,可以进行比较。
如果知道使用int或者long类型进行比较,可以调用对应的比较方法,例如
public static Comparator comparingInt(ToIntFunction super T> keyExtractor)
public static Comparator comparingLong(ToLongFunction super T> keyExtractor)
public static
指定排序方式进行排序比较,第二个参数就是排序方式,为reverseOrder的返回值。
default Comparator
default > Comparator
thenComparing类比comparing,该方法只能在调用了comparing后链式调用,可以实现多字段排序。类似于数据库 order by 字段1 字段2。需要注意的是,链式调用编译器不能自动推导出参数类型,需要自己指定(不知道为啥链式调用不能自动推导,而单独使用却能自动推导,和我理解的泛型有点不同,个人理解是能够层级递推的),如下,实现先按照年龄降序排列,再按照名字升序排列。
2.2.3 Stream sorted();
该方法是使用自然排序,流中的元素必须实现Comparable接口
2.2.4 Stream distinct();
该方法是去掉重复的元素,核心是通过元素类型的equals与hashcode方法判断元素是否是一个元素的,当hashcode相等并且equals返回true即为重复元素。如果比较Integer和String类型的话,就是通过具体的值判断是否相等。
2.2.5 Stream peek(Consumer super T> action);
可以对流中的元素进行修改,Consumer的accept方法没有返回值。
2.3 流的终止操作
当我们定义好中间需要执行的操作过后,就可以调用流的终止操作开始执行所有中间操作了。
2.3.1 R collect(Collector super T, A, R> collector)
需要使用的一个Collector的工具Collectors,常用的方法如下:
toSet():将stream里面的元素转化成Set
toList():将stream里面的元素转化成List
toMap(Function super T, ? extends K> keyMapper,Function super T, ? extends U> valueMapper):将里面的元素生成一个Map,由于Map与List、Set不同,需要指定key与value,所以需要指定key与value的值。例如将用户名作为key,用户对象作为value,实例代码如下。
ArrayList list = new ArrayList<>();
list.add(new User("王麻子",24));
list.add(new User("张三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Map collect = list.stream()
.collect(Collectors.toMap(u -> u.getName(), u -> u));
toCollection(Supplier
inked
ArrayList list = new ArrayList<>();
list.add(new User("王麻子",24));
list.add(new User("张三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
LinkedList collect = list.stream()
.collect(Collectors.toCollection(() -> new LinkedList<>()));
maxBy(Comparator super T> comparator):按照指定规则最值元素,例如获取年龄最大的用户(如果有多个,取第一个)
ArrayList list = new ArrayList<>();
list.add(new User("王麻子",24));
list.add(new User("张三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
User user = list.stream()
.collect(Collectors.maxBy(Comparator.comparing(u -> u.getAge()))).get();
minBy(Comparator super T> comparator):
类比maxBy
public static
将元素的某个Integer字段求和。例如将用户年龄求和
ArrayList list = new ArrayList<>();
list.add(new User("王麻子",25));
list.add(new User("张三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Integer sum = list.stream().collect(Collectors.summingInt(u -> u.getAge()));
System.out.println(sum);
public static
public static
这两个方法求和类型不同以外,其他基本类比summingInt。
public static
public static
public static
这三个方法都是用于求平均值,和求和用法基本相同。
public static
该方法主要用于给元素分组,如下按照用户年龄给用户分组,返回的是分组关键字与该组元素集合的map映射,由于分组后的也是List集合,可以继续对集合做分组后的平均值,最大值,求和等操作。
ArrayList list = new ArrayList<>();
list.add(new User("王麻子",25));
list.add(new User("张三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Map> collect = list.stream()
.collect(Collectors.groupingBy(u -> u.getAge()));
System.out.println(collect);
public static
默认情况下,使用的是List来接收分组后的元素,也可以指定类型,例如用set接收分组后的元素。
ArrayList list = new ArrayList<>();
list.add(new User("王麻子",25));
list.add(new User("张三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Map> collect = list.stream()
.collect(Collectors.groupingBy(u -> u.getAge(), Collectors.toSet()));
System.out.println(collect);
public static
一般默认返回的是HashMap实例,如果需要指定返回Map类型,可以通过该方法指定,例如指定返回LinkedHashMap。
ArrayList list = new ArrayList<>();
list.add(new User("王麻子",25));
list.add(new User("张三",18));
list.add(new User("李四",15));
list.add(new User("小明",25));
Map> collect = list.stream()
.collect(
Collectors
.groupingBy(
u -> u.getAge(),
()-> new LinkedHashMap<>(),
Collectors.toSet()
)
);
System.out.println(collect);