Streams允许我们以声明的方式对数据集合进行计算。在一种声明式编程方式中,您不指定如何做,而是指定要做什么。您可以创建流管道来执行计算。流管道包括:

  • 消息来源
  • 零个或多个中间操作
  • 终端操作

Java streams流是懒惰的。因此,仅当终端操作启动时才执行中间操作。

Java streams pipeline 流管道

让我们考虑一个从图书目录中查找所有java图书作者的例子。使用streams流声明性构造,您可以将代码编写为:

List<Book> books = 
  Catalog.books();

List<String> javaAuthors =
  books.stream()
  .filter(book -> book.getCategory().equals(JAVA))
  .map(Book::getAuthor)
  .collect(Collectors.toList());

让我们试着在“流管道由一个源、零个或多个中间操作和一个终端操作组成”的上下文中理解上述流管道

Stream源

在Java中,Stream的源可以是数组、集合、生成器函数、I/O通道、值范围等。在上面的示例中,Stream流是通过调用集合类的stream()方法创建的。

中间操作

中间操作将一个Stream流转换为另一个Stream流。中间操作总是懒惰的。执行中间操作(如filter())实际上并不执行任何过滤,而是创建一个新流。遍历时的新流包含与给定谓词匹配的初始流元素。在这里,您使用filter(Predicate)按类别筛选书籍。map(Function)将图书Stream流转换为作者Stream流。

终端操作

终端操作产生结果或副作用。执行终端操作后,流管道被视为已消耗,不能再使用。这里,collect是一个终端操作。这会将输入元素累积到列表中。

Java Streams:流创建及示例插图

代码示例

让我们看一些流创建示例。您可以在GitHub上(https://github.com/techdozo/articles/tree/master/stream-creation)找到本文的代码。

创建stream流

有很多方法可以在Java中创建stream。让我们看看其中的一些。

来自单个元素的流

Stream of(T… values)–返回元素为指定值的顺序流。

Stream<String> numbers = 
  Stream.of("One", "Two", "Three");

所有流操作都可以串行或并行执行。除非明确请求并行性,否则JDK中的流实现将创建串行流。例如,Collection有Collection.stream()Collection.parallelStream()方法,它们分别生成顺序流和并行流;其他流承载方法,如IntStream.range(int,int)会生成顺序流,但可以通过调用它们的BaseStream.parallel()方法有效地并行化这些流。

ofNullable(T t)–返回包含单个元素的顺序流,如果非空,则返回空流。

Stream<String> numbers = 
  Stream.ofNullable("One");

Stream<String> empty = 
  Stream.empty(); 

方法Stream.ofNullable提供了一个简化的空检查。例如,如果需要将Collection<T>转换为Stream<T>,可以执行以下操作:

static Stream<Integer> toStream(Collection<Integer> numbers) {
   return Stream.ofNullable(numbers)
  .flatMap(Collection::stream);
}

Java9中添加了nullable(T)方法。

另一种方法是使用可选方法,如:

return Optional
  .ofNullable(numbers)
  .stream()
  .flatMap(Collection::stream);

来自数组的流

您可以使用指定的数组作为源创建顺序流,如下所示:

String[] names = 
  new String[] {"Jack","Jill"};

Stream<String> stream1 = 
  Stream.of(names);
 
Stream<String> stream2 = 
  Arrays.stream(names);

来自集合的流

可以通过调用集合的默认流方法来创建顺序流。

List<String> namesList = 
  List.of("Jack", "Jill");

Stream<String> stream = 
  namesList.stream();

来自Builder的流

使用生成器,您可以通过调用addbuild方法来构建有序流。

Stream<String> stream = 
  Stream.<String>builder()
  .add("Jack")
  .add("Jill")
  .build();

流使用Generate

您可以使用generate方法创建一个无限顺序无序流,其中每个元素都由提供的Supplier生成。这适用于创建恒定流、随机元素流等。

Stream<Integer> randomNumbers = 
  Stream
  .generate(new Random()::nextInt)
  .limit(10);

使用迭代的流

您可以通过调用静态工厂方法iterate(streamiterate(final T seed,final UnaryOperator f))来创建无限顺序有序流。流的第一个元素由seed确定,对于n>0,位置n处的元素将是将函数f应用于n-1的结果。

Stream<Integer> even = 
  Stream
  .iterate(0, n -> n + 2)
  .limit(10);

原始流

专门的原语接口IntStreamDoubleStreamLongStream作为Java8的一部分添加,以高效地处理原语操作。

让我们通过一个例子来理解原语专门化的必要性。考虑一本书课

@Setter
@Getter
public class Book {
  private String name;
  private Category category;
  private double price;
  private String author;
  private String publisher;
}

要查找所有书籍的累计价格,您可以执行以下操作:

static double priceOfAllBooks(List<Book> books) {                              
  Double allBooksPrice = 
  books.stream()
  .map(Book::getPrice)
  .reduce(0d, Double::sum);

  return allBooksPrice;                                                                     
}                                                                                           

在上面的示例中,首先通过调用map(book::getPrice)将book的流转换为Double的流,然后通过调用terminal操作reduce(0d,Double::sum)计算总和。

通过将Stream<Book>转换为DoubleStream,可以简化此示例:

static double priceOfAllBooks(List<Book> books) {                   
  double allBooksPrice = 
  books.stream()
  .mapToDouble(Book::getPrice)
  .sum();  

  return allBooksPrice;                                                     
}       

在上面的示例中,mapToDouble是一个中间操作,它返回一个DoubleStreamDoubleStream是一个double流,它定义了方便的方法,如summinmax等。

Java有三个基本流–IntStreamDoubleStreamLongStream来执行流操作intdoublelong基本类型。

创建原始流

您可以使用stream类中的方法从常规流创建基本流。例如,mapToDouble方法将流转换为DoubleStream。类似地,Stream类也有mapToIntmapToLong方法。

让我们看看创建基本流的示例。

Of

您可以使用以下静态工厂方法创建顺序有序流:

IntStream intOne = 
  IntStream.of(1);

IntStream intOneTwo = 
  IntStream.of(1,2);

DoubleStream doubleOne = 
  DoubleStream.of(1);

LongStream longOneTwo = 
  LongStream.of(1,2); 

范围

您可以使用原语专门化的range方法来创建顺序流。

IntStream oneToNine = 
  IntStream.range(1, 10);

// Range inclusive
IntStream oneToTen = 
  IntStream.rangeClosed(1, 10);

Generate

您可以使用primitive specialization streamgenerate方法创建无限顺序无序的原语流。这适用于生成常量流、随机数流等。

IntStream tenOnes = 
  IntStream.generate(() -> 1).limit(10);

DoubleStream tenRandomDouble = 
  DoubleStream
  .generate(() -> new Random()
  .nextDouble()).limit(10);

斐波那契数的生成

您可以对一些有趣的用例使用generate方法。例如,斐波那契数可以生成为:

public class Fibonacci {
  private int prev = 0;
  private int curr = 1;

  private int next() {
    int temp = prev + curr;
    prev = curr;
    curr = temp;
    return curr;
  }

  public IntStream stream() {
    return IntStream
    .generate(this::next);
  }
}

//Caller
Fibonacci fibonacci = 
  new Fibonacci();

IntStream fibStream = 
  fibonacci.stream().limit(10);

迭代

您可以使用迭代方法创建无限顺序流。在IntStream类中,iterate方法被定义为IntStream iterate(final int seed,final IntUnaryOperator f)

迭代方法通过将函数f应用于初始种子来生成无限流。这将生成一个由seedf(seed)f(f(seed))等组成的流。

IntStream evenNumbers = 
  IntStream
  .iterate(0, n -> n + 2)
  .limit(10);

创建流的其他方法

创建流还有许多其他方法。例如,您可以组合两个流来创建一个新流。让我们看几个例子。

连接两个字符串

您可以使用流的concat操作来创建新流。新流的元素是第一个流的所有元素,然后是第二个流的所有元素。如果两个输入流都已排序,则新流已排序;如果其中一个输入流是并行的,则新流已并行。如果结果流已关闭,则两个输入流也将关闭。

Stream<Integer> prime = 
  Stream
  .concat(Stream.of(2, 3), 
    Stream.of(5, 7, 11));

我们需要对嵌套连接、Stream.concat(Stream.of(..)Stream(Stream.of(..)Stream.of(..)Stream.of(..)进行谨慎处理,因为它可能导致深层调用链或更糟糕的堆栈溢出错误。

String

Java8添加了实用程序方法来从String对象创建流。

Lines

您可以创建由行终止符分隔的行流,如下所示:

static Stream<String> lines() {                       
  return "Line separated by newline.\nAnother line."
  .lines(); 
}     

上面的示例创建了一个由两个元素组成的流:

1. 由换行符分隔的行

2. 另一行

Chars

您可以使用String类的chars方法来创建字符的IntStream,如下所示:

static IntStream chars() { 
  return "XYZ".chars();            
} 

Random

Random类有两个有用的方法来生成原始专门化类型的随机数流。

// Infinite Stream                                  
IntStream randomInts = 
  new Random().ints();
         
// Fixed Size Stream                                
IntStream tenRandomInts = 
  new Random().ints(10);   
 
// Infinite Stream                                  
DoubleStream randomDoubles = 
  new Random().doubles();

// Fixed Size Stream                                
DoubleStream tenRandomDoubles = 
  new Random().doubles(10);

// Infinite Stream                                  
LongStream randomLongs = 
  new Random().longs();      

// Fixed Size Stream                                
LongStream tenRandomLongs = 
  new Random().longs(10); 

总结

Streams允许我们以声明的方式对数据集合进行计算。您可以创建流管道来执行计算。流管道包括:

  • 消息来源
  • 零个或多个中间操作
  • 终端操作

创建流的方法有很多种。例如,您可以从数组、集合或单个元素创建steam。您甚至可以使用stream类的generateiteratebuilder方法创建流。

Java8还添加了专门的原语流。例如,IntStreamDoubleStreamLongStream都是原始流。

可以从单个元素创建基元流,并使用基元流类的range方法。类似地,可以使用生成器、迭代或生成方法创建基本流。

原文地址:https://techdozo.dev/java-stream-creation/