Java函数式编程(二)

一、迭代

public static void main(String[] args) {
        int[] nums = {1, 2, 3};
        //外部迭代
        int sum = 0;
        for (int i: nums){
            sum += i;
        }

        //内部迭代
        int sum2 = IntStream.of(nums).sum();

        System.out.println(sum == sum2);
}
  1. 中间操作:返回Stream
  2. 终止操作:返回最终结果

惰性求值:若没有执行终止操作,则中间操作不会执行。
流只能遍历一次。

二、流的创建

常见的Stream流创建.png
public class Test {
    public static void main(String[] args) {

        //从集合创建
        List list = new ArrayList();
        list.stream();

        //从数组创建
        Arrays.stream(new int[]{1, 2, 3});

        //创建数字流
        IntStream.of(1, 2, 3);
        IntStream.rangeClosed(1, 10);

        //使用Random创建无限流
        new Random().ints().limit(10);

        //自己创建
        Random random = new Random();
        Stream.generate(() -> random.nextInt(10)).limit(10);

        try(Stream<String> lines = Files.lines(Paths.get(""))){
            //由文件生成流
        }catch (IOException e){
            //显式捕获流中的异常
        }
    }
  
}

对象流和数值流的转化:

  1. 映射到数值流:mapToInt、mapToDouble、mapToLong返回一个数值流IntStream、DoubleStream、LongStream。
  2. 转换回对象流:使用boxed()方法,如intStream.boxed()转化为Stream<Integer>类型。

三、中间操作

中间操作.png
  1. map:映射,对值进行处理。
  2. flatMap:多个stream连成一个stream。
  3. filter:过滤,保留通过某项测试的对象。
  4. peak:用于debug,是个中间操作,类似foreach。
  5. limit:返回一个不超过给定长度的流。有状态-有界。
  6. skip:跳过前n个元素,若元素不足n个,返回一个空流。有状态-有界。
  7. sorted:排序。有状态-无界。
  8. distinct:返回一个元素各异的流。(根据流所生成的元素的hashCode和equal方法实现)。有状态-无界。
  9. peek:将中间变量的值输出到日志中。

tips:filter和map一起使用时,尽管它们是两个独立的操作,但它们会被合并到同一次遍历中,这种技术叫做循环合并。
有状态操作:需要知道先前的历史。

四、终止操作

终止操作.png
  1. collect:收集到集合。
  2. reduce:把流合成一个对象。有状态-有界。
  3. foreach:消费流中的每个元素,返回void。
  4. count:返回流中元素的个数。
  5. allMatch、anyMatch、noneMatch、findFirst、findAny
  6. min、max
public class Test {
    public static void main(String[] args) {

        String str = "hello world";

        List<Character> list = Stream.of(str.split(" ")).flatMap(s -> s.chars().boxed()).map(c -> (char)c.intValue()).collect(Collectors.toList());

        System.out.println(list);

        //带初始值的reduce
        Integer len = Stream.of(str.split(" "))
                .map(s -> s.length())
                .reduce(0, (s1, s2) -> s1+s2);
        System.out.println(len);
    }

}

五、收集器

  1. 将流元素归约和汇总为一个值
    (1)Collectors.toList()、Collectors.toSet()、Collectors.toCollection(TreeSet::new),将流转化为集合。
    (2)counting()、minBy()、maxBy()、averagingXX()、summarizingXX(),对流进行统计个数、最小值、最大值、平均数、汇总等操作。
    (3)joining()对流中的每个对象的toString()得到的字符串连接成一个字符串。
    (4)reducing(初始值,转换函数,累积函数),转换函数的类型为BiFunction<T,T,T>,reducing()方法是上述这些特殊情况的一般化。

  2. 元素分组
    (1)groupingBy(?)返回一个Map<T, List<T>>
    (2)多级分组:groupingBy(?, groupingBy(?))
    (3)可搭配其他收集器使用:groupingBy(?, counting())、groupingBy(?,mapping() )
    (4)mapping(映射函数,收集成集合)。
    (5)collectingAndThen(要转换的收集器,转换函数),将收集器的结果转化为另一种类型。

  3. 元素分区
    partitioningBy(?) 返回一个Map<Boolean, List<T>>
    可类比groupingBy()的用法

  4. 自定义收集器
    (1)Collector接口解析

/**
 * 前四个方法都会返回一个被collect方法调用的函数
 * 第五个方法提供了一个提示列表,告诉collect方法在执行归约操作时可以应用哪些优化
 * @param <T>集合中要收集的项目的泛型
 * @param <A>累加器的类型,累加器是在收集过程用于累积部分结果的对象
 * @param <R>收集操作得到的对象
 */
interface Collector<T, A, R> {
    /**
     * 建立新的结果容器
     * @return
     */
    Supplier<A> supplier();

    /**
     * 将元素添加到结果容器
     * @return
     */
    BiConsumer<A, T> accumulator();

    /**
     * 对结果容器应用最终转换
     * @return
     */
    Function<A, R> finisher();

    /**
     * 合并两个结果容器
     * @return
     */
    BinaryOperator<A> combiner();

    /**
     *Characteristics是一个包含CONCURRENT、UNORDERED、IDENTITY_FINISH三个项目的枚举类
     *CONCURRENT accumulator函数可以从多个线程同时调用,且收集器可以并行归约流。如果没有声明为UNORDERED,仅在无序数据源才可以并行归约
     * UNORDERED归约结果不受流中项目遍历和累积顺序的影响
     * IDENTITY_FINISH表明完成器方法返回的函数是一个恒等函数。这种情况下,累加器对象将会直接用作归约过程的最终结果
     * @return
     */
    Set<Characteristics> characteristics();
}

(2)自定义收集器:模拟toList()

public class TestDemo {
    public static void main(String[] args) {
        System.out.println(IntStream.rangeClosed(0, 100).boxed().collect(new ToListCollector<>()));
    }
}

class ToListCollector<T> implements Collector<T, List<T>, List<T>> {

    /**
     * 创建一个空的容器
     * @return
     */
    @Override
    public Supplier<List<T>> supplier() {
        return ArrayList::new;
    }

    /**
     * 加元素添加到容器中
     * @return
     */
    @Override
    public BiConsumer<List<T>, T> accumulator() {
        return List::add;
    }

    /**
     * 恒等函数
     * @return
     */
    @Override
    public Function<List<T>, List<T>> finisher() {
        return Function.identity();
    }

    /**
     * 将两个累加器合并并返回
     * @return
     */
    @Override
    public BinaryOperator<List<T>> combiner() {
        return (list1, list2) -> {
            list1.addAll(list2);
            return list1;
        };
    }

    /**
     * 为收集器添加IDENTITY_FINISH和CONCURRENT标志
     * @return
     */
    @Override
    public Set<java.util.stream.Collector.Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(java.util.stream.Collector.Characteristics.IDENTITY_FINISH, java.util.stream.Collector.Characteristics.CONCURRENT));
    }
}

(3)自定义收集器:筛选质数

import java.util.*;
import java.util.function.*;
import java.util.stream.Collector;
import java.util.stream.IntStream;

/**
 * @description: ${todo}
 * @author: wxz1997
 * @date: 18-9-26 下午7:50
 */
public class TestCollector{
    public static void main(String[] args) {
        System.out.println(IntStream.rangeClosed(2, 100).boxed().collect(new PrimeNumbersCollector()));
    }
}

class PrimeNumbersCollector implements Collector<Integer, Map<Boolean, List<Integer>>, Map<Boolean, List<Integer>>> {

    //创建一个HashMap容器,含有两个空的List
    @Override
    public Supplier<Map<Boolean, List<Integer>>> supplier() {
        return () -> new HashMap<Boolean, List<Integer>>(){{
            put(true, new ArrayList<>());
            put(false, new ArrayList<>());
        }};
    }

    @Override
    public BiConsumer<Map<Boolean, List<Integer>>, Integer> accumulator() {
        return (Map<Boolean, List<Integer>> acc, Integer candidate) -> {
            acc.get(isPrime(acc.get(true), candidate)).add(candidate);
        };
    }

    private static boolean isPrime(List<Integer> primes, Integer candidate) {
        int candidateRoot = (int)Math.sqrt((double)candidate);
        return takeWhile(primes, i -> i <= candidateRoot)
                .stream()
                .noneMatch(p -> candidate % p == 0);
    }

    private static <A> List<A> takeWhile(List<A> list, Predicate<A> p){
        int i=0;
        for (A item: list){
            if (!p.test(item)){
                return list.subList(0, i);
            }
            i++;
        }
        return list;
    }

    @Override
    public Function<Map<Boolean, List<Integer>>, Map<Boolean, List<Integer>>> finisher() {
        return Function.identity();
    }

    @Override
    public BinaryOperator<Map<Boolean, List<Integer>>> combiner() {
        return (Map<Boolean, List<Integer>> map1, Map<Boolean, List<Integer>> map2) -> {
            map1.get(true).addAll(map2.get(true));
            map1.get(false).addAll(map2.get(false));
            return map1;
        };
    }

    @Override
    public Set<java.util.stream.Collector.Characteristics> characteristics() {
        return Collections.unmodifiableSet(EnumSet.of(java.util.stream.Collector.Characteristics.IDENTITY_FINISH));
    }
}

六、并行流

  1. 并行标志parallel(),串行标志sequential(),若多次使用,以最后一个为准决定该流为串行还是并行。
  2. 并行流内部使用了默认的ForkJoinPool,它默认的线程数量为处理器的数量。
  3. 自动装箱和拆箱操作会大大降低性能,尽可能使用原始类型流IntStream、LongStream、DoubleStream等来避免这些操作。
    根据可分解性判断其是否适合并行


    流的数据源和可分解性.png
  4. 工作窃取:每个线程都为分配给它的任务保存一个双向链式队列,每完成一个任务,就会从队列头上取出下一个任务开始执行。基于前面所述的原因,某个线程可能早早完成了分配给它的所有任务,也就是它的队列已经空了,而其他的线程还很忙。这时,这个线程并没有闲下来,而是随机选了一个别的线程,从队列的尾巴上“偷走”一个任务。这个过程一直继续下去,直到所有的任务都执行完毕,所有的队列都清空。这就是为什么要划成许多小任务而不是少数几个大任务,这有助于更好地在工作线程之间平衡负载。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,324评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,356评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,328评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,147评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,160评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,115评论 1 296
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,025评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,867评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,307评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,528评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,688评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,409评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,001评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,657评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,811评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,685评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,573评论 2 353

推荐阅读更多精彩内容

  • Java 8函数式编程学习笔记 author:Gavin date:2018/11/09 什么是函数式编程 在思考...
    安静点就睡吧阅读 1,274评论 0 10
  • Java8 in action 没有共享的可变数据,将方法和函数即代码传递给其他方法的能力就是我们平常所说的函数式...
    铁牛很铁阅读 1,227评论 1 2
  • Java流库(java.util.stream) 流提供了一种让我们可以在比集合更高的概念级别上指定计算的数据视图...
    thorhill阅读 4,840评论 0 4
  • 流提供了一种让我们可以在比集合更高的概念级别上指定计算的数据试图,用来解决“做什么而非怎么做”的问题。 从迭代到流...
    _gitignore阅读 1,205评论 0 1
  • Java中的值传递和引用传递:http://www.jianshu.com/p/6d0f7258f2e0 ** 深...
    峰峰小阅读 248评论 0 0