写本章节的原因起源于以下代码:
Optional<KeywordRouter> optional = getAllKeywordRouter().stream().filter(predicate -> predicate.getOriginKeyword().equals(originKeyword)).findFirst();
List<RfaPublicBean> rfaPublicResponseBeans = rfaPublicBeanPagedList.getList().parallelStream().map(this::converter).collect(Collectors.toList());
小白再看代码的时候都晕了。。想着何时自己才能写出这样的代码。。
简析:
这句代码使用了List集合类的stream()方法得到Stream对象;然后调用filter()过滤得到期望的对象;用Optional对象接着;过滤的过程采用lamda表达式。
涉及到的点:【JAVA8新特性】
- 抽象接口中有默认方法的实现
- Collection接口的stream()方法和parallelStream()方法
- Stream接口中各种方法
- filter()中的函数式接口
- optional类(具体看此文)
- lamda表达式与双冒号的用法
抽象接口中默认方法的实现
在Collection的接口中有多个默认方法的实现,这里只讲stream()方法和parallelStream()方法,实现了这个接口的类都默认具有这个方法。
public interface Collection<E> extends Iterable<E> {
//...
default Stream<E> stream() {
return StreamSupport.stream(spliterator(), false);
}
default Stream<E> parallelStream() {
return StreamSupport.stream(spliterator(), true);
}
// ...
}
这两个方法有什么区别呢?看下面的代码:
public static void main(String[] args){
List<Integer> i=Arrays.asList(1,2,3,4,5,6,7);
i.stream().forEach(System.out :: println);//固定结果 1234567
i.parallelStream().forEach(System.out :: println);//每次的结果都不同
i.parallelStream().forEachOrdered(System.out :: println);//结果同stream.forEach
}
parallelStream()是并行化操作,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。parallelStream执行效率要比传统的for循环和stream要快的多
那么什么时候要用stream或者parallelStream呢?可以从以下三点入手考虑
- 是否需要并行?
- 任务之间是否是独立的?是否会引起任何竞态条件?
- 结果是否取决于任务的调用顺序?
Stream接口
Stream 不是集合元素,它不是数据结构并不保存数据,它是有关算法和计算的,它更像一个高级版本的 Iterator。原始版本的 Iterator,用户只能显式地一个一个遍历元素并对其执行某些操作;高级版本的 Stream,用户只要给出需要对其包含的元素执行什么操作,比如 “过滤掉长度大于 10 的字符串”、“获取每个字符串的首字母”等,Stream 会隐式地在内部进行遍历,做出相应的数据转换。
Stream 就如同一个迭代器(Iterator),单向,不可往复,数据只能遍历一次,遍历过一次后即用尽了,就好比流水从面前流过,一去不复返。
而和迭代器又不同的是,Stream 可以并行化操作,迭代器只能命令式地、串行化操作。顾名思义,当使用串行方式去遍历时,每个 item 读完后再读下一个 item。而使用并行去遍历时,数据会被分成多个段,其中每一个都在不同的线程中处理,然后将结果一起输出。Stream 的并行操作依赖于 Java7 中引入的 Fork/Join 框架(JSR166y)来拆分任务和加速处理过程。
列了以下常用的方法:
public interface Stream<T> extends BaseStream<T, Stream<T>> {
// 过滤流
Stream<T> filter(Predicate<? super T> predicate);
// 找到流中最小的
Optional<T> min(Comparator<? super T> comparator);
// 找到流中最大的
Optional<T> max(Comparator<? super T> comparator);
// 流中是否有满足条件的
boolean anyMatch(Predicate<? super T> predicate);
// 流中是否都满足条件
boolean allMatch(Predicate<? super T> predicate);
// 流中是否都不匹配
boolean noneMatch(Predicate<? super T> predicate);
// 流中找到第一个
Optional<T> findFirst();
// 流中找到任意一个
Optional<T> findAny();
// 这个方法传入一个Function的函数式接口,这个接口,接收一个泛型T,返回泛型R
// map函数的定义,返回的流,表示的泛型是R对象,这个表示,调用这个函数后,可以改变返回的类型
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
void forEach(Consumer<? super T> action);
// ...
}
Stream< T > filter(Predicate<? super T> predicate) 根据传入的lamada表达式来过滤stream。map方法和filter方法的重要区别就是返回值,map可以映射为与输出参数不同的类型,而filter仅仅是对原有流的过滤。Predicate和Function是函数式接口。
函数式接口
JDK 1.8 API包含了很多内建的函数式接口,在老Java中常用到的比如Comparator或者Runnable接口,这些接口都增加了@FunctionalInterface注解以便能用在lambda上。
name | type | description |
---|---|---|
Consumer | Consumer< T > | 接收T对象,不返回值 |
Predicate | Predicate< T > | 接收T对象并返回boolean |
Function | Function< T, R > | 接收T对象,返回R对象 |
Supplier | Supplier< T > | 提供T对象(例如工厂),不接收值 |
UnaryOperator | UnaryOperator | 接收T对象,返回T对象 |
BinaryOperator | BinaryOperator | 接收两个T对象,返回T对象 |
标注为FunctionalInterface的接口被称为函数式接口,该接口只能有一个自定义方法,但是可以包括从object类继承而来的方法。如果一个接口只有一个方法,则编译器会认为这就是一个函数式接口。是否是一个函数式接口,需要注意的有以下几点:
- 该注解只能标记在”有且仅有一个抽象方法”的接口上。
- JDK8接口中的静态方法和默认方法,都不算是抽象方法。
- 接口默认继承java.lang.Object,所以如果接口显示声明覆盖了Object中方法,那么也不算抽象方法。
- 该注解不是必须的,如果一个接口符合”函数式接口”定义,那么加不加该注解都没有影响。加上该注解能够更好地让编译器进行检查。如果编写的不是函数式接口,但是加上了@FunctionInterface,那么编译器会报错。
- 在一个接口中定义两个自定义的方法,就会产生Invalid ‘@FunctionalInterface’ annotation; FunctionalInterfaceTest is not a functional interface错误.
Predicate接口
Predicate函数式接口的主要作用就是提供一个test方法,根据传入的lambda表达式来决定的,接受一个参数返回一个布尔类型,Predicate在stream api中进行一些判断的时候非常常用。
@FunctionalInterface
public interface Predicate<T> {
boolean test(T t);
//...
}
除此之外,Predicate还提供了另外三个默认方法和两个静态方法(JAVA8新特性)。
- and方法接收一个Predicate类型,也就是将传入的条件和当前条件以并且的关系过滤数据。
- or方法同样接收一个Predicate类型,将传入的条件和当前的条件以或者的关系过滤数据。
- negate就是将当前条件取反。看下具体使用方式
接下来我们看看Predicate默认实现的三个重要方法and,or和negate对应了java的三个连接符号&&、||和! 。
default Predicate<T> and(Predicate<? super T> other) {
Objects.requireNonNull(other);
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);
}
@SuppressWarnings("unchecked")
static <T> Predicate<T> not(Predicate<? super T> target) {
Objects.requireNonNull(target);
return (Predicate<T>)target.negate();
}
}
测试:
int[] numbers= {1,2,3,4,5,6,7,8,9,10,11,12,13,14,15};
List<Integer> list=new ArrayList<>();
for(int i:numbers) {
list.add(i);
}
Predicate<Integer> p1=i->i>5;
Predicate<Integer> p2=i->i<20;
Predicate<Integer> p3=i->i%2==0;
List test=list.stream().filter(p1.and(p2).and(p3)).collect(Collectors.toList());
System.out.println(test.toString());
/** print:[6, 8, 10, 12, 14]*/
Function接口
源码部分:
@FunctionalInterface
public interface Function<T, R> {
// 将Function对象应用到输入的参数上,然后返回计算结果
R apply(T t);
// 返回一个先执行before函数对象apply方法再执行当前函数对象apply方法的函数对象
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
// 返回一个先执行当前函数对象apply方法再执行after函数对象apply方法的函数对象。
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
/**
* Returns a function that always returns its input argument.
*/
static <T> Function<T, T> identity() {
return t -> t;
}
}
compose 和 andThen 的不同之处是函数执行的顺序不同。compose 函数先执行参数,然后执行调用者,而 andThen 先执行调用者,然后再执行参数。
双冒号的用法
双冒号运算符就是java中的方法引用,方法引用的格式是类名::方法名。
这里只是方法名,方法名的后面没有括号“()”。--------> 这样的式子并不代表一定会调用这个方法。这种式子一般是用作Lambda表达式,Lambda有所谓的懒加载,不要括号就是说,看情况调用方法。
例如:
表达式:
person ->person.getAge();
可以替换为:
Person::getAge
表达式:
()-> new HashMap<>();
可以替换为
HashMap::new
这种方法引用或者是双冒号运算对应的参数类型是Function<T,R>,T表示传入的类型,R表示返回的类型。比如表达式person -> person.getAge();传入的参数是person,返回值是peron.getAge(),那么方法引用Person::getAge就对应着Funciton<Person,Integer>类型。
示例代码:把List里面的String全部大写并返回新的ArrayList,在前面的例子中是这样写的。
List<String> collected = new ArrayList<>();
collected.add("alpha");
collected.add("beta");
collected = collected.stream().map(string -> string.toUpperCase()).collect(Collectors.toList());
System.out.println(collected);
使用双冒号操作符之后编程了下面的形式:
List<String> collected = new ArrayList<>();
collected.add("alpha");
collected.add("beta");
collected = collected.stream().map(String::toUpperCase).collect(Collectors.toList());
System.out.println(collected);
参考链接:https://segmentfault.com/a/1190000012256677?utm_source=tag-newest
参考链接:https://blog.youkuaiyun.com/huo065000/article/details/78964382
参考链接:https://www.cnblogs.com/hengzhou/p/9550250.html