/*
    了解Steam

        Java8中有两大最为重要的改变:Lambda表达式、StreamAPI(steam流,java.util.stream.*)

        Stream是Java8中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、
        过滤和映射数据等操作。

        使用StreamAPI对集合数据进行操作,就类似于使用SQL执行的数据库查询。

        也可以使用StreamAPI来并行执行操作。简而言之,StreamAPI提供了一种高效且易于使用的处理数据的方式。

    流(Stream)到底的什么呢?

        流是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
        “集合讲的是数据,流讲的是计算”。

    注意:
        1、Stream自己不会存储元素。
        2、Stream不会改变源对象。相反,它们会返回一个持有结果的新Stream。
        3、Stream操作是延迟执行的。这意味着它们会等到需要结果的时候才执行。

    Stream的操作三个步骤:
        1、创建Stream
            一个数据源(如集合、数组),获取一个流
        2、中间操作
            一个中间操作链,对数据源的数据进行处理
        3、终止操作
            一个终止操作,执行中间操作,并产生结果

    创建Stream:
        Java8中的Collection接口被扩展,提供两个获取流的方法:list.stream();
            |- default Stream<E> stream() 返回一个顺序流
            |- default Stream<E> parallelStream() 返回一个并行流
    由数组创建流:
        Java8中的Arrays的静态方法stream()以获取数组流:
            |- static <T> Stream<T> stream(T[] array) 返回一个流
            |- public static IntStream stream(int[] array)
            |- public static LongStream stream(long[] array)
            |- public static DoubleStream stream(double[] array)
    由值创建流:
        可以使用静态方法Stream.of(),通过显示值创建一个流。它可以接收任意数量的参数。
            |- public static<T> Stream<T> of(T... values) 返回一个流
    由函数创建流:创建无限流
        可以使用静态方法Stream.iterate()和Stream.generate(),创建无限流。
        |- 迭代
            public static<T> Stream<T> iterate(final T seed,final UnaryOperator<T> f)
        |- 生成
            public static<T> Stream<T> generate(Supplier<T> s)

    Stream的中间操作:
        多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理。
        而在终止操作时一次性全部处理,称为“惰性求值”。

    Optional类是一个可以为null的容器对象,如果值存在则isPresent()方法返回true,调用get()返回该对象。

 */


import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamAPI {

    public static List<Product> getProduct(){
        List<Product> products = Arrays.asList(
                new Product("iphone","电子产品",7000,3),
                new Product("book1","书籍",20,10),
                new Product("computer","电子产品",10000,5),
                new Product("vivo","电子产品",4000,4),
                new Product("book2","书籍",15,20),
                new Product("chicken","食品",25,2),
                new Product("rice","食品",60,3),
                new Product("lol","游戏",80,1),
                new Product("cf","游戏",50,5),
                new Product("picture","纪念品",12,6),
                new Product("flower","纪念品",20,1),
                new Product("nick","服装",199,3)
        );

        return products;
    }

    /*
        创建stream流
    */
    public static void createStream(){

        // 通过java.util.Collection.stream()方法用集合创建流
        List<String> strList = Arrays.asList("H","e","l","l","o");
        // 创建一个顺序流
        Stream<String> strStream = strList.stream();
        // 创建一个并行流
        Stream<String> parallelStream = strList.parallelStream();
        /*
            stream和parallelStream区分:
                stream是顺序流,由主线程按顺序对流执行操作。
                parallelStream流是并行流,内部以多线程并行执行的方式对流进行操作,
                前提是流中的数据处理没有顺序要求。
            如果流中数据量足够大,并行流可以加快处理速度。
            除了可以创建并行流,也可以调用顺序流的parallel()方法,把流转为并行流。
        */
        // 寻找出strList里第一个长度大于6的字符串,使用并行流
        Optional<String> findFirst = strList.stream().parallel().filter(x->x.length()>6).findFirst();

        // 使用java.util.Arrays.stream(T[] array)方法用数组创建流
        int[] array = new int[]{1,3,5,6,8};
        IntStream intStream = Arrays.stream(array);

        // 使用Stream的静态方法:of()、iterate()、generate()方法
        Stream<Integer> integerStream1 = Stream.of(1,3,2,4,6);
        integerStream1.forEach(System.out::print); // 13246
        System.out.println();
        Stream<Integer> integerStream2 = Stream.iterate(0,(x)->x+3).limit(4);
        integerStream2.forEach(System.out::print); // 0369
        System.out.println();
        Stream<Double> integerStream3 = Stream.generate(Math::random).limit(3);
        integerStream3.forEach(System.out::print); // 0.218 0.932 0.887

    }

    /*
        遍历/匹配(foreach/find/match)

        Stream流也是支持类似集合的遍历和匹配元素的,只是Stream流中的元素是以Optional类型存在的。
        所有Stream流的遍历、匹配非常简单。
     */
    public static void ForeachAndFind(){
        List<Product> list = getProduct();

        // 遍历输出符合条件的product 类别为”电子产品“
        list.stream().filter(product -> product.getCategory().equals("电子产品")).forEach(System.out::println);

        // 匹配第一个类别为”电子产品“且价格大于8000的商品
        Optional<Product> findFirst = list.stream().filter(
                product -> product.getCategory().equals("电子产品") && product.getPrice()>8000
        ).findFirst();
        System.out.println("第一个类别为”电子产品“且价格大于5000的商品:"+findFirst.get()); // computer

        // 任意匹配,返回流中任意元素。返回任意一本书
        Optional<Product> findAny = list.stream().parallel().filter(product -> product.getCategory().equals("书籍")).findAny();
        System.out.println("findAny = " +findAny.get()); // book1/book2

        // 流中是否包含符合条件的元素  类别:书籍,价格大于50
        boolean anyMatch = list.stream().anyMatch(product -> product.getCategory().equals("书籍") && product.getPrice()>50);
        System.out.println("是否包含价格大于50的书籍:"+anyMatch);  // false

        // 流中的商品数量是否都大于5
        // allMatch方法,是否匹配所有元素; noneMatch方法,检查是否没有匹配所有元素
        boolean allMatch = list.stream().allMatch(product -> product.getNum()>=5);
        System.out.println("流中商品数量是否都大于5:"+allMatch); // false
    }

    /*
        筛选(filter)与切片
            筛选,是按照一定的规则校验流中的元素,将符合条件的元素提取到新的流中的操作
     */
    public static void filter(){
        List<Product> list = getProduct();

        // 筛选产品中价格大于100元或小于20元的产品,形成新的集合。collect收集
        List<Product> pros = list.stream()
                .filter(x->x.getPrice()>100 || x.getPrice()<20)
                .collect(Collectors.toList());
        pros.forEach(System.out::println);

        System.out.println("--------");

        // 去除重复元素,通过流所生成元素的hashCode()和equals()去除重复元素
        Product product = new Product("ads","服装",259,1);

        // 1、Collections.copy(新列表,源列表); 方法复制
        /* asList获取的集合,使用add会报错,UnsupportedOperationException,我们需要再接收一下值
        List<Product> products = Arrays.asList(new Product[list.size()]);  使用new ArrayList()<>报错
        Collections.copy(products,list); // 将list元素复制到products  新列表的长度不能小于源列表长度;目标列表必须至少与源列表一样长。*/

        // 2、addAll
        /*List<Product> products = new ArrayList<>();
        products.addAll(list);*/

        // 3、用stream流复制列表
        List<Product> products = list.stream().collect(Collectors.toList());
        products.add(product);
        products.add(product);
        products.stream().distinct().forEach(System.out::println); // 输出只有一个ads

        System.out.println("-------");

        // limit(max)截断流,使元素不超过max个。
        // skip(long n)跳过元素,返回一个扔掉了前n个元素的流。若流中元素不足n个,则返回一个空流。与limit互补
        // 取前2个电子产品
        list.stream().filter(x->x.getCategory().equals("电子产品")).limit(2).forEach(System.out::println);
        // 跳过前两个电子产品
        list.stream().filter(x->x.getCategory().equals("电子产品")).skip(2).forEach(System.out::println);

    }

    /*
        聚合 max/min/count
        统计
     */
    public static void aggregate(){
        List<Product> list = getProduct();

        // 取list中价格最高且数量大于10的产品
        // max/min 要传入 Comparator.comparing(类名::方法名)
        Optional<Product> max = list.stream().filter(x->x.getNum()>10).max(Comparator.comparing(Product::getPrice));
        System.out.println("list中价格最高且数量大于10的产品:"+max.get());  // book2

        // 筛选单价最低的产品
        Optional<Product> min = list.stream().min(Comparator.comparing(Product::getPrice));
        System.out.println("list中价格最低的商品:"+min.get()); // picture

        // 计算流中总数
        System.out.println(list.stream().count()); // 12

        List<Integer> nums = Arrays.asList(7, 6, 9, 4, 11, 6);
        // 自然排序
        Optional<Integer> max1 = nums.stream().max(Integer::compareTo);
        // 自定义排序(从大到小排序)
        Optional<Integer> max2 = nums.stream().max((o1, o2) -> o2 - o1); // o2-o1>0,逆序。自定义,小的大
        System.out.println("自然排序的最大值:" + max1.get()); // 11
        System.out.println("自定义排序的最大值:" + max2.get()); // 4

    }

    /*
        映射 map/flatMap/mapToInt/mapToDouble/mapToLong
        映射可以将一个流的元素按照一定的映射规则映射到另一个流中。
            |- map:接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射为一个新元素。
            |- flatMap:接收一个函数作为参数,将流中的每一个值都换成另一个流,然后把所有流连接成一个流。
            |- mapToInt:接收一个函数作为参数,该函数会被应用到每个元素上,然后返回一个IntStream
     */
    public static void map(){

        // 将字符串数组全部改为大写。
        char[] charArr = {'a','c','e','g'};

        String newStr = new String(charArr);  // 字符数组转为字符串1
        String newStr2 = String.valueOf(charArr); // 字符数组转为字符串2

        char[] chars = newStr2.toCharArray(); // 字符串转字符数组

        String newStr3 = Arrays.toString(chars); // 数组转字符串 ['a','c','e','g']

        String[] strArr = newStr2.split(""); // 字符串转为字符数组

        List<String> strList = Arrays.stream(strArr).map(String::toUpperCase).collect(Collectors.toList());
        String listStr = strList.stream().collect(Collectors.joining(","));  // 如果joinin不传参或传“”,则list元素拼接
        System.out.println(listStr); // A,C,E,G

        // 整数数组每个元素+3
        List<Integer> intList = Arrays.asList(1,3,5,9);
        List<Integer> intList2 = intList.stream().map(x -> x + 3).collect(Collectors.toList());
        System.out.println(intList2); // [4,6,8,12]

        // 每个商品的数量+1
        List<Product> products = getProduct();
        products = products.stream().map(product->{
            product.setNum(product.getNum()+1);
            product.setNum(product.getNum()+1);
            return product;
        }).collect(Collectors.toList());
        products.forEach(System.out::println);

        // flatMap用法同map,只是执行过程有区别

        // mapToInt 返回一个新流IntStream,后序可以做很多操作
        List<String> strs = Arrays.asList("hi","nihaoa","666","hey");
        // 获取字符串数组中每个字符串的长度
        IntStream intStream = strs.stream().mapToInt(String::length);
        //System.out.println("长度最大值:"+intStream.max().getAsInt());  // 6  流只能用一次
        //System.out.println("长度平均值:"+intStream.average().getAsDouble());  // 3.5
        System.out.println("长度之和:"+intStream.sum()); // 14

    }

    /*
        规约 reduce
        规约,也称缩减,顾名思义,是把一个流缩减成一个值,能实现对集合求和、求乘积、求最值操作
     */
    public static void reduce(){
        List<Integer> countList = Arrays.asList(1,3,2,8,11,4);
        // 求和1
        Optional<Integer> sum1 = countList.stream().reduce((x,y)->x+y);
        // 求和2
        Optional<Integer> sum2 = countList.stream().reduce(Integer::sum);
        // 求和3
        Integer sum3 = countList.stream().reduce(0,Integer::sum);

        // 求乘积
        Optional<Integer> chengji = countList.stream().reduce((x,y)->x*y);

        // 求最大值方式
        Optional<Integer> max1 = countList.stream().reduce((x,y) -> x>y?x:y);
        // 求最大值方式
        Integer max2 = countList.stream().reduce(1,Integer::max);

        System.out.println("和:"+sum3);
        System.out.println("乘积:"+chengji.get());
        System.out.println("最大值:"+max2);

        List<Product> list = getProduct();
        // 求价格和1
        Optional<Integer> sumPrice = list.stream().map(Product::getPrice).reduce(Integer::sum);
        // 求价格和2
        Integer sumPrice2 = list.stream().reduce(0,(sum,p) -> sum+=p.getPrice(),(x,y)->x+y);
        // 求价格和3
        Integer sumPrice3 = list.stream().reduce(0,(sum,p) -> sum+=p.getPrice(),Integer::sum);

        // 求最高工资方式1
        Integer maxPrice1 = list.stream().reduce(0,(max,p) -> max > p.getPrice()?max:p.getPrice(),Integer::max);
        // 求最高工资方式2
        Integer maxPrice2 = list.stream().reduce(0,(max,p) -> max > p.getPrice()?max:p.getPrice(),(x,y)->x>y?x:y);
        // 求最高工资方式3
        Optional<Integer> maxPrice3 = list.stream().map(Product::getPrice).reduce(Integer::max);

        System.out.println("最高价格:"+maxPrice3.get());

    }

    /*
        收集collect
        collect,收集,可以说是内容最繁多、功能最丰富的部分了。
        从字面上理解,就是把一个流收集起来,最终收集成一个值也可以收集成一个新的集合。

        collect主要依赖与java.util.stream.Collectors类内置的静态方法
     */

    /*
        归集  toList toSet toMap
        因为流不存储数据,那么在流中的数据处理完成后,需要将流中的数据重新归集到新的集合里。
        toList、toSet、toMap比较常用,另外还有toCollection、toConcurrentMap等复杂的用法

        下面演示 toList toSet toMap
     */
    public static void toListOrSet(){
        List<Integer> countList = Arrays.asList(1,2,3,4,4,5,6,10,2);
        // 筛选出偶数,放到新的集合里
        List<Integer> doubleNum = countList.stream().filter(x->x%2==0).collect(Collectors.toList());
        System.out.println(doubleNum);
        // countList去重1,放到set
        List<Integer> countListCopy = Arrays.asList(1,2,3,4,4,5,6,10,2);
        //Set<Integer> set = countListCopy.stream().collect(Collectors.toSet());
        //System.out.println(set);
        // countList去重2
        List<Integer> dis = countListCopy.stream().
                distinct().collect(Collectors.toList());
        System.out.println(dis);

        List<Product> list = getProduct();
        // 去除价格低于50和高于4000的商品,存到map
        Map<String,Product> map = list.stream().filter(p->p.getPrice()>50 && p.getPrice()<4000).collect(Collectors.toMap(Product::getName,p->p));
        System.out.println(map);

    }

    /*
        统计(count/averaging)
        Collectors提供了一系列用于数据统计的静态方法

        计数:count
        平均值:averagingInt、averagingLong、averagingDouble
        最值:maxBy、minBy
        求和:summingInt、summingLong、summingDouble
        统计以上所有:summarizingInt、summarizingLong、summarizingDouble
     */
    public static void count(){
        List<Product> list = getProduct();

        // 求总数
        Long count = list.stream().collect(Collectors.counting());
        Long count2 = list.stream().count();
        // 求平均价格
        Double average = list.stream().collect(Collectors.averagingDouble(Product::getPrice));
        // 求最高价格
        Optional<Integer> maxPrice = list.stream().map(Product::getPrice).collect(Collectors.maxBy(Integer::compare));
        Integer maxPrice2 = list.stream().max(Comparator.comparing(Product::getPrice)).get().getPrice();
        Integer maxPrice3 = list.stream().map(Product::getPrice).max(Integer::compareTo).get();
        // 求价格之和
        Integer priceAll = list.stream().collect(Collectors.summingInt(Product::getPrice));
        // 一次性统计所有信息
        DoubleSummaryStatistics collect = list.stream().collect(Collectors.summarizingDouble(Product::getPrice));

        System.out.println("价格总数:"+count);
        //System.out.println("平均价格:"+average);
        System.out.println("平均价格:"+String.format("%.2f",average));
        System.out.println("最高价格:"+maxPrice3);
        System.out.println("价格之和:"+priceAll);
        System.out.println("所有信息:"+collect); // collect.getMax()....
        // 所有信息:DoubleSummaryStatistics{count=12, sum=21481.000000, min=12.000000, average=1790.083333, max=10000.000000}

    }

    /*
        分组(partitioningBy/groupingBy)
        分区:
            将stream按条件分为两个Map,比如按商品价格是否高于1000分为两部分
        分组:
            将集合分为多个Map,比如商品按类别分组。有单级分组和多级分组
     */
    public static void group(){
        List<Product> list = getProduct();

        // 将商品价格按1000分组
        Map<Boolean,List<Product>> part = list.stream().collect(Collectors.partitioningBy(x->x.getPrice()>1000));
        System.out.println("按价格分组:"+part);

        // 商品按类别分组
        Map<String,List<Product>> group = list.stream().collect(Collectors.groupingBy(Product::getCategory));
        System.out.println("按照类别分组:"+group);
        System.out.println("电子产品:"+group.get("电子产品"));

        // 商品先按类别分类,再按价格分类
        Map group2 = list.stream().collect(Collectors.groupingBy(Product::getCategory,Collectors.groupingBy(Product::getPrice)));
        System.out.println(group2);

    }

    /*
        接合(joining)
        joining可以将stream中的元素用特定的连接符(没有的话,直接连接)连接成一个字符串
     */
    public static void joining(){
        List<Product> list = getProduct();

        // 商品所有名字连接成字符串
        String names = list.stream().map(p->p.getName()).collect(Collectors.joining(","));
        System.out.println("所有名字:"+names);
    }

    /*
        规约(reducing)
        Collectors类提供的reducing方法,相比于stream本身的reduce方法
     */
    public static void reducing(){
        List<Product> list = getProduct();

        // 每个商品的价格扣税
        list.stream().collect(Collectors.reducing(0,Product::getPrice,(i,j)->(i+j-10)));
        // stream的reduce
        Optional<Integer> sum = list.stream().map(Product::getPrice).reduce(Integer::sum);
        System.out.println("商品总价:"+sum.get());

    }

    /*
        排序sorted
        中间操作。有两种排序:
            1、sorted(),自然排序,流中元素需要实现Comparable接口
            2、sorted(Comparator com):Comparator排序器自定义排序
     */
    public static void sorted(){
        List<Product> list = getProduct();

        // 按商品价格升序排序(自然排序)
        List<Product> sorted1 = list.stream().sorted(Comparator.comparing(Product::getPrice)).collect(Collectors.toList());
        System.out.println("价格升序:"+sorted1);
        // 价格降序
        List<Product> sorted2 = list.stream().sorted(Comparator.comparing(Product::getPrice).reversed()).collect(Collectors.toList());
        System.out.println("价格降序:"+sorted2);
        // 先按价格再按数量升序排序
        List<String> sorted3 = list.stream()
                .sorted(Comparator.comparing(Product::getPrice).thenComparing(Product::getNum))
                .map(Product::getName)
                .collect(Collectors.toList());
        System.out.println(sorted3);
        // 自定义排序 降序
        List<Product> sorted4 = list.stream().sorted((p1,p2)->{
            return p2.getPrice()- p1.getPrice();  //正降负升
        }).collect(Collectors.toList());
        System.out.println(sorted4);
    }

    /*
        提取/组合
        流也可以进行合并、去重、限制、跳过等操作
        distinct去重,skip跳过,limit限制
     */
    public static void limit(){
        String[] arr1 = {"a","b","c","d"};
        String[] arr2 = {"d","e","f","g","h"};

        Stream<String> stream1 = Stream.of(arr1);
        Stream<String> stream2 = Stream.of(arr2);

        // concat:合并两个流;distinct:去重
        List<String> concatList = Stream.concat(stream1,stream2).distinct().collect(Collectors.toList());
        // list拼接成字符串
        String concatStr = concatList.stream().collect(Collectors.joining("-"));
        System.out.println("拼接成的字符串:"+concatStr);  // a-b-c-d-e-f-g-h

        // limit:限制流中获得前n个数据
        List<String> limitList = concatList.stream().limit(3).collect(Collectors.toList());
        System.out.println("前三个元素:"+limitList);  // [a, b, c]
        // skip:跳过前n个数据
        List<String> skipList = concatList.stream().skip(6).collect(Collectors.toList());
        System.out.println("跳过前6个元素:"+skipList);  // [g, h]

        // 模拟分页  .skip((long) (pageNum - 1) * pageSize).limit(pageSize)
        List<Product> list = getProduct();
        // 获取第2页,每页3个
        List<Product> pageList = list.stream().skip((2-1)*3).limit(3).collect(Collectors.toList());
        System.out.println("第2页,3个:"+pageList);
    }



    /* 测试 */
    public static void main(String[] args) {

        // 创建流
        //createStream();

        // 遍历 匹配
        //ForeachAndFind();

        // 过滤
        //filter();

        // 聚合  总数/最大/最小
        //aggregate();

        // 映射
        //map();

        // reduce规约
        //reduce();

        // 归集
        //toListOrSet();

        // 统计
        //count();

        // 分组
        //group();

        // 接合
        //joining();

        // 归约 reducing
        //reducing();

        // 排序
        //sorted();

        // 提取/组合
        limit();

    }

}

class Product{
    private String name; // 商品名
    private String category; // 类别
    private int price;  // 单价
    private int num;  // 下单数量

    public Product(String name, String category, int price, int num) {
        this.name = name;
        this.category = category;
        this.price = price;
        this.num = num;
    }
    public Product() {
    }

    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public int getPrice() {
        return price;
    }
    public void setPrice(int price) {
        this.price = price;
    }
    public int getNum() {
        return num;
    }
    public void setNum(int num) {
        this.num = num;
    }
    public String getCategory() {
        return category;
    }
    public void setCategory(String category) {
        this.category = category;
    }

    @Override
    public String toString() {
        return "Product{" +
                "name='" + name + '\'' +
                ", price=" + price +
                ", num=" + num +
                ", category=" + category +
                '}';
    }

}