【JAVA8新特性】之stream

写本章节的原因起源于以下代码:

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.csdn.net/huo065000/article/details/78964382
参考链接:https://www.cnblogs.com/hengzhou/p/9550250.html

发布了33 篇原创文章 · 获赞 9 · 访问量 8694

猜你喜欢

转载自blog.csdn.net/Serena0814/article/details/97135073