原创

JAVA8系列教程-Stream API

温馨提示:
本文最后更新于 2019年12月12日,已超过 1,600 天没有更新。若文章内的图片失效(无法正常加载),请留言反馈或直接联系我

Java中Stream可以定义为来自源的一系列元素,这些元素支持对它们的聚合操作。这里的源是指向Stream提供数据CollectionsArrays

Stream保持数据在源中的顺序。聚合操作批量操作是操作这让我们很容易和清楚地表达对流元素共同操作。

java_8_lambdas

在继续之前,重要的是要了解Java 8 Streams的设计方式使得大多数流操作仅返回流这有助于我们创建流操作链。这称为管道内衬(pipe-lining在这篇文章中,我将多次使用该术语,因此请牢记。

1. Java Stream vs. Collection

我们所有人都在youtube或其他此类网站上观看在线视频。当您开始观看视频时,首先会将一小部分文件加载到计算机中并开始播放。开始播放之前,您无需下载完整的视频。这称为流式传输。我将尝试将此概念与集合相关联,并通过Streams进行区分。

在基本级别上,集合和流之间的差异与计算事物时有关。一个Collection是一个内存数据结构,它包含所有的数据结构目前有-每在Collection元件具有被计算之前它可以被添加到集合中的值。一个Stream在概念上是固定数据结构,其中的元素都要求经过计算处理的这带来了显着的编程优势。想法是,用户将仅从Stream中提取他们需要的值,并且仅在需要时才对用户无形地生成这些元素。这是生产者-消费者关系的一种形式。

在java中,java.util.Stream表示可以在其上执行一个或多个操作的流。操作是中间操作或终端操作虽然终端操作返回一个特定类型的结果中间操作返回流本身,从而可以在一排链中的多个方法调用。流是在源上创建的,例如列表或集合(不支持映射)之类的java.util.Collection。流操作可以顺序执行,也可以并行执行。

基于以上几点,如果我们列出Stream的各种特征,它们将如下所示:

  • 不是数据结构
  • 专为lambdas设计
  • 不支持索引访问
  • 可以轻松输出为数组或列表
  • 支持延迟访问
  • 可并行化

2.创建流的不同方法

以下是从集合构建流的最流行的不同方法。

2.1。Stream.of(val1,val2,val3 ....)

public class StreamBuilders
{
     public static void main(String[] args)
     {
         Stream<Integer> stream = Stream.of(1,2,3,4,5,6,7,8,9);
         stream.forEach(p -> System.out.println(p));
     }
}

2.2。Stream.of(arrayOfElements)

public class StreamBuilders
{
     public static void main(String[] args)
     {
         Stream<Integer> stream = Stream.of( new Integer[]{1,2,3,4,5,6,7,8,9} );
         stream.forEach(p -> System.out.println(p));
     }
}

2.3。List.stream()

public class StreamBuilders
{
     public static void main(String[] args)
     {
         List<Integer> list = new ArrayList<Integer>();
 
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
 
         Stream<Integer> stream = list.stream();
         stream.forEach(p -> System.out.println(p));
     }
}

2.4。Stream.generate()或Stream.iterate()

public class StreamBuilders
{
     public static void main(String[] args)
     {
         Stream<Date> stream = Stream.generate(() -> { return new Date(); });
         stream.forEach(p -> System.out.println(p));
     }
}

2.5。字符串字符或字符串令牌

public class StreamBuilders
{
     public static void main(String[] args)
     {
        IntStream stream = "12345_abcdefg".chars();
        stream.forEach(p -> System.out.println(p));
         
        //OR
         
        Stream<String> stream = Stream.of("A$B$C".split("\\$"));
        stream.forEach(p -> System.out.println(p));
     }
}

除了上面列出的方法以外,还有其他一些方法,例如使用Stream.Buider或使用中间操作。我们将不时在单独的帖子中了解它们。

3.将流转换为集合

我应该说将流转换为其他数据结构。

请注意,这不是真正的转换。它只是将流中的元素收集到集合或数组中。

3.1。将流转换为列表– Stream.collect(Collectors.toList())

public class StreamBuilders {
     public static void main(String[] args){
         List<Integer> list = new ArrayList<Integer>();
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
         Stream<Integer> stream = list.stream();
         List<Integer> evenNumbersList = stream.filter(i -> i%2 == 0).collect(Collectors.toList());
         System.out.print(evenNumbersList);
     }
}

3.2。将Stream转换为数组– Stream.toArray(EntryType [] :: new)

public class StreamBuilders {
     public static void main(String[] args){
         List<Integer> list = new ArrayList<Integer>();
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
         Stream<Integer> stream = list.stream();
         Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
         System.out.print(evenNumbersArr);
     }
}

还有很多其他方式可以将流收集到集合,映射或多种方式中。只需完成收集器课程,并记住它们即可。

4.核心流操作

流抽象为您提供了一长串有用的功能。我不会覆盖所有内容,但是我打算在这里列出所有最重要的内容,您必须第一手知道。

在继续之前,让我们事先构建String的集合。我们将在此列表上构建示例,以便易于联系和理解。

List<String> memberNames = new ArrayList<>();
memberNames.add("Amitabh");
memberNames.add("Shekhar");
memberNames.add("Aman");
memberNames.add("Rahul");
memberNames.add("Shahrukh");
memberNames.add("Salman");
memberNames.add("Yana");
memberNames.add("Lokesh");

这些核心方法分为以下两个部分:

4.1。中级作业

中间操作返回流本身,因此您可以连续链接多个方法调用。让我们学习重要的东西。

4.1.1。Stream.filter()

Filter接受谓词以过滤流中的所有元素。此操作是中间操作,它使我们可以对结果调用另一个流操作(例如forEach)。

memberNames.stream().filter((s) -> s.startsWith("A"))
                    .forEach(System.out::println);
                                 
Output:
 
Amitabh
Aman
4.1.2。Stream.map()

中间操作映射通过给定的函数将每个元素转换为另一个对象。下面的示例将每个字符串转换为大写字符串。但是,您也可以使用map将每个对象转换为另一种类型。

memberNames.stream().filter((s) -> s.startsWith("A"))
                     .map(String::toUpperCase)
                     .forEach(System.out::println);
                             
Output:
 
AMITABH
AMAN
4.1.2。Stream.sorted()

Sorted是一个中间操作,它返回流的排序视图。除非您通过自定义比较器,否则元素将以自然顺序排序。

memberNames.stream().sorted()
                    .map(String::toUpperCase)
                    .forEach(System.out::println);
Output:
 
AMAN
AMITABH
LOKESH
RAHUL
SALMAN
SHAHRUKH
SHEKHAR
YANA

请记住,sorted只会创建流的排序视图,而不会操纵支持的集合的顺序。memberNames的顺序保持不变。

4.2。终端操作

终端操作返回某种类型的结果,而不是流。

4.2.1。Stream.forEach()

此方法有助于迭代流的所有元素,并对每个元素执行一些操作。该操作作为lambda表达式参数传递。

memberNames.forEach(System.out::println);
4.2.2。Stream.collect()

collect() 方法,用于从蒸汽中接收元素并将其存储在集合中,并在参数函数中提到。

List<String> memNamesInUppercase = memberNames.stream().sorted()
                            .map(String::toUpperCase)
                            .collect(Collectors.toList());
         
System.out.print(memNamesInUppercase);
 
Outpout: [AMAN, AMITABH, LOKESH, RAHUL, SALMAN, SHAHRUKH, SHEKHAR, YANA]
4.2.3。Stream.match()

各种匹配操作可用于检查某个谓词是否与流匹配。所有这些操作都是终端操作,并返回布尔结果。

boolean matchedResult = memberNames.stream()
                    .anyMatch((s) -> s.startsWith("A"));
 
System.out.println(matchedResult);
 
matchedResult = memberNames.stream()
                    .allMatch((s) -> s.startsWith("A"));
 
System.out.println(matchedResult);
 
matchedResult = memberNames.stream()
                    .noneMatch((s) -> s.startsWith("A"));
 
System.out.println(matchedResult);
 
Output:
 
true
false
false
4.2.4。Stream.count()

Count是一个终端操作,将流中的元素数返回为long。

long totalMatched = memberNames.stream()
                    .filter((s) -> s.startsWith("A"))
                    .count();
 
System.out.println(totalMatched);
 
Output: 2
4.2.5。Stream.reduce()

此终端操作使用给定功能对流的元素进行归约。结果是一个可选保留减少的值。

Optional<String> reduced = memberNames.stream()
                    .reduce((s1,s2) -> s1 + "#" + s2);
 
reduced.ifPresent(System.out::println);
 
Output: Amitabh#Shekhar#Aman#Rahul#Shahrukh#Salman#Yana#Lokesh

5.流短路操作

尽管对满足谓词的集合内的所有元素执行流操作,但通常希望在迭代过程中遇到匹配元素时都中断操作。在外部迭代中,将使用if-else块。在内部迭代中,可以使用某些方法来实现此目的。我们来看两个这样的方法的示例:

5.1。Stream.anyMatch()

一旦作为谓词传递的条件满足,此方法将返回true。它不会再处理任何元素。

boolean matched = memberNames.stream()
                    .anyMatch((s) -> s.startsWith("A"));
 
System.out.println(matched);
 
Output: true

5.2。Stream.findFirst()

它将从流中返回第一个元素,然后将不再处理任何元素。

String firstMatchedName = memberNames.stream()
                .filter((s) -> s.startsWith("L"))
                .findFirst().get();
 
System.out.println(firstMatchedName);
 
Output: Lokesh

6. Java Steam中的并行性

借助Java SE 7中添加的Fork / Join框架,我们有了有效的机制来在应用程序中实现并行操作。但是,实现此框架本身是一项复杂的任务。如果做得不好,是可能导致应用程序崩溃的复杂多线程错误的来源。通过引入内部迭代,我们可以并行执行操作。

要启用并行性,您要做的就是创建并行流,而不是顺序流。令您惊讶的是,这确实非常容易。在上面列出的任何流示例中,每当您想在并行内核中使用多个线程来完成特定工作时,都必须调用方法parallelStream()方法而不是stream()方法。

public class StreamBuilders {
     public static void main(String[] args){
        List<Integer> list = new ArrayList<Integer>();
         for(int i = 1; i< 10; i++){
             list.add(i);
         }
         //Here creating a parallel stream
         Stream<Integer> stream = list.parallelStream(); 
         Integer[] evenNumbersArr = stream.filter(i -> i%2 == 0).toArray(Integer[]::new);
         System.out.print(evenNumbersArr);
     }
}

这项工作的主要推动力是使并行性更易于开发人员使用。尽管Java平台已经为并发和并行性提供了强大的支持,但是开发人员在根据需要将其代码从顺序迁移到并行时会遇到不必要的障碍。因此,重要的是要鼓励顺序友好和并行友好的习语。通过将重点转移到描述应该执行什么计算,而不是应该如何执行计算,可以方便地进行操作。

同样重要的是要在使并行性变得更容易但不至于使其变得不可见之间取得平衡。使并行性透明将引入不确定性,并可能在用户可能不期望的情况下进行数据竞争。

关于Java 8中引入Stream抽象的基本知识,这就是我想要分享的所有内容我将在以后的文章中讨论与Streams相关的其他各种事情。

正文到此结束
本文目录