JDK8之Stream(一)

Stream 流

根据官方的说法是:支持一系列元素的串行或者并行等聚合操作。

流由三部分构成:1.源、2.零个或者多个中间操作、3.终止操作

流操作的分类:1.惰性操作、2.及早求值;(对于流来说可以进行一系列链式操作,然而这系列中间链式操作,也就是惰性操作,只有遇到了终止操作也就是及早求值。)

流实例的创建方式

  • 1.使用Stream中的静态方法
 Stream<String> stream  = Stream.of("hello","world");//of方法接收一个可变参数
  • 使用数组方式创建
String[] str = new String[]{"hello","world"};
Stream<String> stream1 = Arrays.stream(str);

其实上面的方式底层就是一样的,我们可以看一下of方法的源代码:

public static<T> Stream<T> of(T... values) {
        return Arrays.stream(values);
}

  • 通过集合的方式创建
List<String> list = Arrays.asList(str);
Stream<String> stream2 = list.stream();

该方式也是最常见的方式

关于流相关的例子

关于流相关的方法,采用例子的方式来了解流。

  • 1.打印3-7的序列,不包括7
public class StreamTest {
    public static void main(String[] args) {
        IntStream.range(3,7).forEach(System.out::println);
    }
}
  • 2.打印3-7的序列,包括7
public class StreamTest {
    public static void main(String[] args) {
        IntStream.rangeClosed(3,7).forEach(System.out::println);
    }
}

上面的两个例子主要讲解列IntStream的另一个实例方式


  • 3.将一个整形数组的数据进行乘以2再输出
public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4);
        list.stream().map(item->item*2).forEach(System.out::println);
        //可以理解为将集合转换为流对象然后对流中的数据进行一个映射->映射后的值,所有就用map方法
    }
}
  • 4.将一个整形数组的数据都乘以2,并求出其总和。
public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4);
        Integer result = list.stream().map(item->item*2).reduce(0,Integer::sum);
        System.out.print(result);
    }
}

reduce()方法在Stream中有三个重载方法。reduce可以叫做聚合操作。就是对流中的每个元素执行某个操作,让其输出一个聚合后的数据。当然它也是终止操作


将stream对象转换为一个数组

方式一:使用streamtoArray()方法。该方法默认转换回来的数组是Object类型的数组。

public class StreamTest {
    public static void main(String[] args) {
       Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
       Object[] str = stream.toArray();
       Arrays.asList(str).forEach(System.out::println);
    }
}

方式二:使用StreamtoArray(IntFunction<A[]> generator),该方法可以用户定义产生数组的长度。这里给出该方法的说明generator a function which produces a new array of the desired type and the provided length;

public class StreamTest {
    public static void main(String[] args) {
       Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
       //String[] strArra = stream.toArray(String[]::new);
       String[] strArra = stream.toArray(length->new String[length]);
       Arrays.asList(strArra).forEach(System.out::println);
    }
}

将流转换为一个List集合

关于将流转换为一个List集合对象,可以使用collect方法:

/**
* Performs a <a href="package-summary.html#MutableReduction">mutable
     * reduction</a> operation on the elements of this stream.  A mutable
     * reduction is one in which the reduced value is a mutable result container,
     * such as an {@code ArrayList}, and elements are incorporated by updating
     * the state of the result rather than by replacing the result.  This
     * produces a result equivalent to:
     * <pre>{@code
     *     R result = supplier.get();
     *     for (T element : this stream)
     *         accumulator.accept(result, element);
     *     return result;
     * }</pre>
     * @param <R> type of the result
     * @param supplier a function that creates a new result container. For a
     *                 parallel execution, this function may be called
     *                 multiple times and must return a fresh value each time.
     * @param accumulator an <a href="package-summary.html#Associativity">associative</a>,
     *                    <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                    <a href="package-summary.html#Statelessness">stateless</a>
     *                    function for incorporating an additional element into a result
     * @param combiner an <a href="package-summary.html#Associativity">associative</a>,
     *                    <a href="package-summary.html#NonInterference">non-interfering</a>,
     *                    <a href="package-summary.html#Statelessness">stateless</a>
     *                    function for combining two values, which must be
     *                    compatible with the accumulator function
*/
<R> R collect(Supplier<R> supplier,
                  BiConsumer<R, ? super T> accumulator,
                  BiConsumer<R, R> combiner);

对于该方法的理解:第一个参数:转换后的集合类型(也就是说是一个什么类型的集合),第二个参数:就是对该集合执行的哪些操作。第三个参数:将两个操作结起来

下面举一个例子:

public class StreamTest {
    public static void main(String[] args) {
       Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
       //List<String> list1 = stream.collect(ArrayList::new,ArrayList::add,ArrayList::addAll);
        List<String> list1 = stream.collect(()->new ArrayList(),(list,item)->list.add(item),(list,list2)->list.addAll(list2));
       list1.forEach(System.out::println);
    }
}

总结:supplier:提供容器,accumulator:向容器中添加数据,combiner:把多个容器实例拼接成一个有完整数据的容器。


上面的方式是jdk8的一种原始方式,所以比较复杂。所以,因此jdk8也提供了一种更方便的方式。就是collect方法接收一个Collertors类型的参数。该参数类型是一个接口,接口中有许多相关的方法。

首先:给出该方式的例子:

public class StreamTest {
    public static void main(String[] args) {
       Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
       List<String> list = stream.collect(Collectors.toList());
       list.forEach(System.out::println);
    }
}

我们查看关于toList()方法源码就可以发现,其实他就是对原始的方式进行了一次封装:

public static <T>
    Collector<T, ?, List<T>> toList() {
        return new CollectorImpl<>((Supplier<List<T>>) ArrayList::new, List::add,
                                   (left, right) -> { left.addAll(right); return left; },
                                   CH_ID);
    }

但是通过这种方式,可以发现,转换后的集合就只能是ArrayList类型的集合。那么如果我不想使用ArrayList类型的集合怎么办呢?

答案是:可以使用Collertors封装好的另一个方法toCollection方法。

public static <T, C extends Collection<T>>
    Collector<T, ?, C> toCollection(Supplier<C> collectionFactory) {
        return new CollectorImpl<>(collectionFactory, Collection<T>::add,
                                   (r1, r2) -> { r1.addAll(r2); return r1; },
                                   CH_ID);
    }

collectionFactory:就是用户自己决定使用什么容器来接收流中的元素。

例子:

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
        List<String> list = stream.collect(Collectors.toCollection(LinkedList::new));
        list.forEach(System.out::println);
    }
}

其实Collectors中除了可以指定把流转换为对应的集合,他还可以把流转换为字符串

  • 第一种:没有分割符
public static Collector<CharSequence, ?, String> joining() {
        return new CollectorImpl<CharSequence, StringBuilder, String>(
                StringBuilder::new, StringBuilder::append,
                (r1, r2) -> { r1.append(r2); return r1; },
                StringBuilder::toString, CH_NOID);
}

例子:

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
        String result = stream.collect(Collectors.joining());
        System.out.print(result);
    }
}
  • 第二种:有分隔符
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter) {
        return joining(delimiter, "", "");
}

例子:

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
        String result = stream.collect(Collectors.joining(";"));
        System.out.print(result);
    }
}
  • 第三种:有分隔符,同时也有前缀和后缀
public static Collector<CharSequence, ?, String> joining(CharSequence delimiter,
                                                             CharSequence prefix,
                                                             CharSequence suffix) {
        return new CollectorImpl<>(
                () -> new StringJoiner(delimiter, prefix, suffix),
                StringJoiner::add, StringJoiner::merge,
                StringJoiner::toString, CH_NOID);
}

例子:

public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
        String result = stream.collect(Collectors.joining(";","&","$"));
        System.out.print(result);
    }
}

流实例剖析

  • 1.将一个字符流中的每个元素转为大写输出
public class StreamTest {
    public static void main(String[] args) {
        Stream<String> stream = Stream.of("nihao","wohao","dajiaohao");
        stream.map(String::toUpperCase).collect(Collectors.toList()).forEach(System.out::println);
    }
}

这里使用的map方法属于中间操作,map的作用上面也介绍了,其实他就是把流中的元素映射为其他类型的数据,然后再返回为一个新的流。(注意:流所有的中间操作都会返回一个全新的流对象)。所以:map一般是在我们需要对流中的数据进行转换的时候就会使用到。

<R> Stream<R> map(Function<? super T, ? extends R> mapper);
//可以看到map方法接收一个Function类型的参数,也就是说用户自定义一个操作,把流中的值
//映射为其他值。Function的输出类型就是流中每个元素的类型。
  • 2.把一个类似于List<List>集合中的每个元素乘以2输出
public class StreamTest {
    public static void main(String[] args) {
       List<List<Integer>> list = Arrays.asList(Arrays.asList(1),Arrays.asList(2,3),Arrays.asList(4,5));
       list.stream().flatMap(item->item.stream()).map(item->item*2).collect(Collectors.toList()).forEach(System.out::println);
    }
}

这里的flatMap方法也同样属于中间操作:flatMap会把流对象中的每个元素转换为流,然后把每个元素的流再映射成一个包含所有元素的流对象。(也就是把List集合中的每个List类型的元素转换为流。最后把每个元素的流合并成一个新的流,因此新的流中就有List集合中的所有元素。–该方法也有一个中文名称叫做打平,意思就是将集合中的List类型的元素打平)。因此,flatMap应用场景就是如果集合中的元素还是集合/数组的元素还是数组,想要把元素里面的元素打平成为只包含所有元素的新集合/数组。

Returns a stream consisting of the results of replacing each element of
     * this stream with the contents of a mapped stream produced by applying
     * the provided mapping function to each element.
     <R> Stream<R> flatMap(Function<? super T, ? extends Stream<? extends R>> mapper);

关于flatMap的方法再来一个例子:把一个String流中的每个元素中的单词进行去重打印出来

public class StreamTest {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("hello welcome","hello world","world hello","hello hello welcome","welcome hello");
        list.stream().map(item->item.split(" ")).flatMap(Arrays::stream).distinct().forEach(System.out::println);

    }
}
//输出结果:
hello
welcome
world
  • 3.通过流的方式生成1 2 4 6 … 256类型的等差数列。
public class StreamTest {
    public static void main(String[] args) {
        Stream.iterate(1,item->item*2).limit(9).forEach(System.out::println);
    }
}
  • 4.在3的基础上找出流中大于2的元素,然后将每个元素乘以2,然后忽略流中的前两个元素,最后取出流中前两个元素的总和。
public class StreamTest {
    public static void main(String[] args) {
        int result = Stream.iterate(1,item->item*2).limit(9).filter(item->item>2).mapToInt(item->item*2).skip(2).limit(2).sum();
        System.out.println(result);

    }
}

流相关的陷阱

首先我们通过一段程序运行的结果来看:

public class StreamTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.iterate(1,item->item*2).limit(6);
        System.out.println(stream);
        System.out.println(stream.map(item->item + 2));
    }
}
//结果:
//java.util.stream.SliceOps$1@6d311334
//java.util.stream.ReferencePipeline$3@214c265e

通过这段程序我们可以发现,流(Stream) 每次执行一次操作后就会生成一个新的流对象。那么原先的流对象就会关闭,最后被Java虚拟机垃圾回收掉。下面的一段程序就可以证明已经使用的流就不能再使用啦,因为使用过后的流对象就关闭啦!就不能再被使用了:

public class StreamTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.iterate(1,item->item*2).limit(6);
        System.out.println(stream.filter(item -> item>2));
        System.out.println(stream.map(item->item + 2));
    }
}
//下面给出报错信息:
//Exception in thread "main" java.lang.IllegalStateException: stream has already been operated upon or closed

接下来再看一段程序:

public class StreamTest {
    public static void main(String[] args) {
        Stream<Integer> stream = Stream.iterate(1,item->item*2).limit(6);
        stream.mapToInt(item -> {
            item = item +2;
            System.out.println(item);
            return item;
        });
    }
}

运行这段程序控制台并不会输出任何东西。原因是,map操作属于中间操作,流的所有中间操作都是属于惰性的。

接下来再看一段程序:

public class StreamTest {
    public static void main(String[] args) {
        List<String> list = Arrays.asList("hello","world","hello world");
        list.stream().mapToInt(item -> {
            int length = item.length();
            System.out.println(item);
            return length;
        }).filter(length -> length >= 5).findFirst().ifPresent(System.out::println);
    }
}

这段程序,可以猜测一下输出的结果如何,其实输出的结果是:

hello
5

那么问题来了,为什么输出的结果是这样呢?而不是下面这样的呢?

hello 
5
world
5
hello world
11

其实关于这个问题是这样的:首先需要纠正一点的是,流并不是把流中的所有元素拿去执行一个中间操作过后,再去执行下一个中间操作的。而是一个个元素一次性的去执行流的所有操作。(下面会说到:流是内部迭代的,而是把流中的每个元素像通过管道一样的方式执行下去的)。最后一点就是:流是存在短路的,当找到自己所需要的结果时,就不会再继续往下走啦!

同过上面的程序就可以证明关于流的几点特征:

  1. 流在执行每次操作后都会生成一个新的流对象
  2. 已经被使用过的流对象就不能再次被使用,因为已经使用的流对象已经被关闭啦!
  3. 流的中间操作都是惰性的。只有遇到终止操作才会去执行中间操作。
  4. 流是存在短路的。

内部迭代与外部迭代

内部迭代:就是关于所有操作都是在内部进行的。比如:对一个int类型流的所有元素都加1的操作,就是进行内部迭代的。

外部迭代:就是所有操作,都是在外部进行的。比如:使用for循环遍历数组/集合中的每个元素,就是使用的外部迭代。

public void outer(){
    List<Integer> list = Arrays.asList(1,2,3,4,5,6);
    for(Integer i : list){
        System.out.println(i);
    }
}

这段程序就属于外部迭代。就像一个指针一样,首先指针指向第一个元素,每次操作后,指针都向后移一个位置指向下一个元素。

public void inner(){
    List<Integer> list = Arrays.asList(1,2,3,4,5,6);
    list.stream().forEach(System.out::println);
}

这段程序就是属于内部迭代的方式。首先将list集合转换为一种流,再通过流依次输出每个元素。这种方式就好像管道一样,数组中的每个元素以流的方式通过管道输出到控制台。这样,我们并没有给list集合外加一个迭代器,让该迭代器去执行list集合中的每个元素。而是将list集合转换为流的形式通过管道依次输出。

通过上面解释什么是内部迭代和外部迭代过后,我们也说了内部迭代就好比管道一样。其实我们完全可以把流的每个中间操作当当做一节管道,将集合或者数组当做源,将终止操作当做管道出口后到达的位置。就以下面的程序来详细的描述下:

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5,6);
        list.stream().map(item->item * 2).filter(item -> item > 3).skip(1).forEach(System.out::println);
    }
}

仔细阅读这段程序:list可以作为流的源,map、filter、skip就相当于三节具有不同功能的管道。源首先通过map管道过后就变成map对源操作后的结果源。然后结果源再通过filter管道,然而filter管道需要过滤小于3的元素,因此小于三的元素就被过滤掉了,就不能进入skip管道。接下来就是进入skip管道,然而skip管道会把第一个进入该管道的元素给过滤掉,其他元素就能顺利的通过skip管道。通过skip管道过后的元素就会离开管道,然后就被输出到控制台。

这一系列的操作都是通过不同的管道过后的结果。所以流是属于内部迭代的。

如果要通过外部迭代的方式执行和上面相同的list集合,并输出相同的结果就是这样的:

public class StreamTest {
    public static void main(String[] args) {
        List<Integer> list = Arrays.asList(1,2,3,4,5,6);
        int temp = 0;
        List<Integer> tempList = new ArrayList<>();
        for(int i : list){
            temp = i * 2;
            if(temp > 3){
                tempList.add(temp);
            }
        }
        for(int j = 0;j<tempList.size();j++){
            if(j == 0){
                continue;
            }
            System.out.println(tempList.get(j));
        }
    }
}

首先通过这段程序第一反应就是,使用外部迭代的方式让上面只需要一行就能搞定的事情,但是现在就需要如此多操作。 这种外部迭代的方式就是通过Iterator(迭代器)来执行集合中的每个元素的。 显然不用我来进行比较了吧!

该文章也算把我学习jdk8关于流stream的使用 给记录下来啦

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值