函数式编程:这里“函数”应该理解为数学上的函数,即y=f(x)。
函数式编程的理解出发点,比如给Swing中Button添加监听器addListener(Listener接口)为例,没有lambda表达式时一般都是通过匿名内部类new XXXListener()做,由于Java一切皆对象,所以我们传递了一个监听器对象。但是这块语义层面,传递对象并不是最终目的,我们的最终目的就是想传递一个“行为”(比如用户点击Button后的行为),所以采用Lambda表达式这种简洁写法只是在语义层面能够更好一些,但本质还是一样的,lambda也是对象。
一、Lambda表达式
1. 函数式接口
函数式接口是只能有一个抽象方法的接口(不论接口中有多少非抽象方法,其抽象方法只能有一个)。Java提供了 @FunctionalInterface 注解用于标记函数式接口,该注解只是检测作用,用于检查一个接口是否为函数式接口,如果不是,则无法编译。Lambda结合函数式接口可以让代码更加简洁明了。JDK8中引入了一个java.util.function包,该包下的接口全部是函数式接口。如果我们想要定义的接口与该包下定义的接口类似,那么拿来即用即可。
1.1 Consumer(消费型接口)
@FunctionalInterface
public interface Consumer<T> {
void accept(T t);
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
//只是返回一个Consumer对象,该对象的accept()方法如下
return (T t) -> { accept(t); after.accept(t); };
}
}
1.2 Function(函数型接口)
@FunctionalInterface
public interface Function<T, R> {
//传入一个T类型,得到一个R类型
R apply(T t);
//以下都是功能扩展:
static <T> Function<T, T> identity() {
//返回参数类型T自身
return t -> t;
}
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
}
1.3 Predicate(断定型接口)
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
//以下都是功能扩展
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
//返回两个Predicate接口实例的test()方法返回值的&&
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);
}
}
1.4 Supplier(供给型接口)
@FunctionalInterface
public interface Supplier<T> {
T get();
}
2.Lambda表达式语法
- “->”左侧:方法的参数列表。(参数类型可以省略不写,它可以按照类型自动匹配,若存在相同类型则按形参顺序做匹配。若抽象方法只有一个参数,则“()”可以省略不写,若抽象方法没有参数,则()和->都可以省略不写)
- “->”右侧:方法的具体实现(若方法体中只有一条语句则可以省略“{}”,若方法体中只有一条语句,且该语句为return语句,则return关键字可以省略)
Lambda表达式也是对象,它只是使我们实现抽象方法时的代码更加简洁。
public static void main(String[] args) {
//无参数()和->都省略不写,方法体中只有一条语句则可以省略“{}”,若方法体中只有一条语句,且该语句为return语句,则return关键字可以省略
Supplier<User> supplierObject = User::new;
//只有一个参数,()省略不写,只有一条语句,{}省略不写,参数类型不写,自动做类型推断
Predicate<Integer> predicateObject = number -> number.equals(10086);
//两个参数,参数类型不写,自动做类型推断
BiConsumer<String, User> biConsumerObject = (userId,user) -> {
System.out.println(userId);
System.out.println(user);
};
}
3. 方法引用
Lambda有一个常见的用法,比如想得到一个User的姓名,Lambda的表达式如下:
user -> user.getName()
这种用法非常普遍,因此Java 8 为其提供了一个简单的写法,叫做方法的引用,标准用法为:
ClassName::methodName,需要注意的是,虽然这是一个方法,但不需要在后面加括号。如下:
//
User::getName
二、Stream API
1.惰性求值和及早求值
- 返回为Stream对象的API叫做 “惰性求值”(如filter方法),惰性求值不会被立刻执行,它更像是在给及早求值描述一些规则。
- 返回一个新集合 或者 其他具体值 的API叫做 及早求值(如count()这种方法),及早求值会立刻被执行。
惰性求值生成一个求值链,然后由一个及早求值返回想要的结果。这个过程和建造者模式有点类似,先一系列操作设置属性和配置,然后最后build()真正返回结果。及早求值方法就是类似与build()方法。
2. 常用的流操作
调用流的API,就相当于将流中的元素交给该API方法进行处理。(Stream中的元素流出来交给lambda,作为函数接口中抽象方法的实参)
流的特点:只能遍历一次 我们可以把流想象成一条流水线,流水线的源头是我们的数据源,数据源中的元素依次被输送到流水线上,我们可以在流水线上对元素进行各种操作。一旦元素走到了流水线的另一头,那么这些元素就被“消费掉了”,我们无法再对这个流进行操作。当然,我们可以从数据源那里再获得一个新的流重新遍历一遍。
流的数据源可以是多种形式,比如集合、数组、以及多个值组合而成。如下:
//1.集合类型
Collection<String> names = new ArrayList<>();
Stream<String> stream = names.stream();
//2.数组,利用Arrays中提供的工具将数组转换为流
String[] names = new String[10];
Stream<String> stream = Arrays.stream(names);
//3.多个值组合
Stream<String> stream = Stream.of("张三","李四","王麻子");
2.1 收集collect
这是一个及早求值的操作,通过传递一个收集器,将流中的元素收集到我们想要的容器中。
public static void main(String[] args) {
Stream<String> stream = Stream.of("张三","李四","王麻子");
//将流中的元素收集到列表中
List<String> names = stream.collect(Collectors.toList());
}
2.2 map
如果一个函数可以将一种类型的值转换为另外一种类型,map操作就可以使用该函数,将一个流中的值转换为一个新的流。这是一个惰性求值的操作。map接收一个Function<T,R>接口的对象,将一个类型的值转换为另一个类型。
Stream<Integer> nameLen = Stream.of("张三","李四","王麻子")
.map(name -> {
return name.length();
});
2.3 forEach
它接受一个Consumer接口的对象,遍历流中的元素,是一个及早求值的操作。
Stream.of("张三","李四","王麻子").forEach(name -> {
System.out.println("姓名:" + name);
});
2.4 filter
它接受一个断定性接口的对象,将方法返回为false的元素过滤掉,如下,只保留了“张三”。
Stream.of("张三","李四","王麻子").filter(name -> {
return "张三".equals(name);
}).forEach(name -> {
System.out.println("姓名:" + name);
});
2.5 max和min
Stream上常用的操作之一是求最大值和最小值。而max()和min()正好可以解决这个问题。它接受一个Comparator接口的对象,通过自定义的比较规则求最大、最小值。
3.Optional介绍
Optional是Java8新加入的一个容器,这个容器 只存1个或0个元素,它用于防止出现NullPointException,它提供如下方法:
- isPresent(): 判断容器中是否有值。
- ifPresent(Consume lambda): 容器若不为空则执行括号中的Lambda表达式。
- T get(): 获取容器中的元素,若容器为空则抛出NoSuchElement异常。
- T orElse(T other): 获取容器中的元素,若容器为空则返回括号中的默认值。
of和ofNullable方法
of 和 ofNullable方法都用于创建包含值的 Optional对象。两个方法的不同之处在于如果你把 null 值作为参数传递进去,of() 方法会抛出NullPointerException。你看,我们并没有完全摆脱 NullPointerException。因此,你应该明确对象不为 null 的时候使用 of()。 如果对象即可能是 null 也可能是非 null,你就应该使用 ofNullable() 方法。
//将user对象放入容器中,但是不清楚user是否为null,如果user不是null,那么获取User的年龄,如果年龄为null,则默认是18岁。
Integer userAge = Optional.ofNullable(user).map(User::getAge).orElse(18);
4 收集器
利用流的collect()方法可以讲流中的元素收集到我们想要的数据结构中,比如前面的List,有时人们还希望将其收集到Set、Map等。JDK已经提供了许多收集器,如下:
- toSet()
- toList()
- toMap()
- toConcurrentMap()
- toCollection()
4.1 toMap
/*
1. keyMapper:Key 的映射函数
2. valueMapper:Value 的映射函数
3. mergeFunction:当 Key 冲突时,调用的合并方法
4. mapSupplier:Map 构造器,在需要返回特定的 Map 时使用
*/
public static <T, K, U, M extends Map<K, U>>
Collector<T, ?, M> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction,
Supplier<M> mapSupplier)
//这个方法没有提供mapSupplier参数,默认收集到HashMap中
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper,
BinaryOperator<U> mergeFunction) {
return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new);
}
//没有mergeFunction和mapSupplier,默认收集到HashMap中,且当键冲突时会抛出异常
public static <T, K, U>
Collector<T, ?, Map<K,U>> toMap(Function<? super T, ? extends K> keyMapper,
Function<? super T, ? extends U> valueMapper) {
return toMap(keyMapper, valueMapper, throwingMerger(), HashMap::new);
}
如下,将UserList中的元素收集到TreeMap中:
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User("10086","张三"));
userList.add(new User("10087","李四"));
Map<String,User> userMap = userList.stream().collect(Collectors.toMap(user -> user.getUserId() + user.getUserName(), Function.identity(), (key1, key2) -> key1, TreeMap::new));
}
当然Map可以自己指定,集合也可以自己指定,不仅限于Set和List,可以通过如下方式指定:
userList.stream().collect(Collectors.toCollection(TreeSet::new));
4.2 数据分组(groupingBy)
/*
1.classifier:分类器(分类函数)(根据函数返回值来分类)
2.downstream:下游收集器(起名:分组收集器)。每个组的元素将流到下游收集器中。
数据分组后存放在Map中,如下默认将分组后的数据存放在HashMap中
*/
public static <T, K, A, D>
Collector<T, ?, Map<K, D>> groupingBy(Function<? super T, ? extends K> classifier,
Collector<? super T, A, D> downstream) {
return groupingBy(classifier, HashMap::new, downstream);
}
//自定义Map类型,比如可以将分组后的数据存放在TreeMap中等。
public static <T, K, D, A, M extends Map<K, D>>
Collector<T, ?, M> groupingBy(Function<? super T, ? extends K> classifier,
Supplier<M> mapFactory,
Collector<? super T, A, D> downstream)
//如下,将UserList按照UserId进行分组,每组是一个Map<String,User>
Map<String, Map<String, User>> userIdMap =
userList.stream().collect(Collectors.groupingBy(User::getUserId,
Collectors.toMap(User::getUserName, Function.identity(), (key1, key2) -> key1, HashMap::new)));
//如下,将UserList按照UserId进行分组,每组是一个List<User>
Map<String, List<User>> userIdMap =
userList.stream().collect(Collectors.groupingBy(User::getUserId, Collectors.toList()));
//如下,将UserList按照UserId进行分组,每组是一个List<User>,将分组后的数据存放在TreeMap中
TreeMap<String, List<User>> userIdMap =
userList.stream().collect(Collectors.groupingBy(User::getUserId,TreeMap::new, Collectors.toList()));
4.3 数据分块(partitioningBy)
数据分块可以将流中元素分为两部分,它使用Predicate对象判断一个元素应该属于哪一部分,并根据布尔值返回一个Map到列表,对于true List中的元素,Predicate返回true,对于其他List中的元素,Predicate返回false。
public static <T>
Collector<T, ?, Map<Boolean, List<T>>> partitioningBy(Predicate<? super T> predicate) {
return partitioningBy(predicate, toList());
}
如下将UserList中的元素分成两部分,userId为10086的存放在一个List中,剩余其他的存放在一个List中。
public static void main(String[] args) {
List<User> userList = new ArrayList<>();
userList.add(new User("10086","张三"));
userList.add(new User("10087","李四"));
userList.add(new User("10088","李四"));
Map<Boolean,List<User>> userMap = userList.stream().collect(Collectors.partitioningBy(user -> "10086".equals(user.getUserId())));
}
4.3 computeIfAbsent
构建Map时,为给定值计算键值是常用的操作之一,一个经典的例子就是实现一个缓存。传统的处理方式是先试着从Map中取值,如果没有取到,则创建一个新值返回。Java 8引入了一个新方法computeIfAbsent,该方法接受一个Lambda表达式,值不存在时使用该Lambda计算新值,并将新值存入Map中。如下:
private static User getUser(String userId){
return userCache.computeIfAbsent(userId,this::readUserFromDB);
}
4.4 遍历Map
Java8之前我们遍历Map通常都是先找到Key的集合,然后迭代key集合获取value进行操作。Java 8为Map接口提供了forEach方法,简化我们遍历Map,该方法接受一个BiConsumer对象为参数(该对象接受两个参数,无返回值),通过内部迭代编写出易于阅读的代码。
default void forEach(BiConsumer<? super K, ? super V> action) {
Objects.requireNonNull(action);
for (Map.Entry<K, V> entry : entrySet()) {
K k;
V v;
try {
k = entry.getKey();
v = entry.getValue();
} catch(IllegalStateException ise) {
// this usually means the entry is no longer in the map.
throw new ConcurrentModificationException(ise);
}
action.accept(k, v);
}
}
如下,打印Map中的键值对:
userMap.forEach((key,value) -> System.out.println("键:" + key + "\t值:" + value));