当前位置: 首页 > 编程笔记 >

浅谈JAVA8给我带了什么——流的概念和收集器

施凡
2023-03-14
本文向大家介绍浅谈JAVA8给我带了什么——流的概念和收集器,包括了浅谈JAVA8给我带了什么——流的概念和收集器的使用技巧和注意事项,需要的朋友参考一下

到现在为止,笔者不敢给流下定义,从概念来讲他应该也是一种数据元素才是。可是在我们前面的代码例子中我们可以看到他更多的好像在表示他是一组处理数据的行为组合。这让笔者很难去理解他的定义。所以笔者不表态。各位同志自行理解吧。

在没有流以前,处理集合里面的数据一般都会用到显示的迭代器。用一下前面学生的例子吧。目标是获得学分大于5的前俩位同学。

package com.aomi;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import static java.util.stream.Collectors.toList;

public class Main {

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  List<Student> stus = getSources();

  Iterator<Student> ite = stus.iterator();

  List<String> names = new ArrayList<>();
  int limit = 2;
  while (ite.hasNext() && limit > 0) {

   Student stu = ite.next();

   if (stu.getScore() > 5) {

    names.add(stu.getName());
    limit--;
   }
  }

  for (String name : names) {
   System.out.println(name);
  }

 }

 public static List<Student> getSources() {
  List<Student> students = new ArrayList<>();

  Student stu1 = new Student();

  stu1.setName("lucy");
  stu1.setSex(0);
  stu1.setPhone("13700227892");
  stu1.setScore(9);

  Student stu2 = new Student();
  stu2.setName("lin");
  stu2.setSex(1);
  stu2.setPhone("15700227122");
  stu2.setScore(9);

  Student stu3 = new Student();
  stu3.setName("lili");
  stu3.setSex(0);
  stu3.setPhone("18500227892");
  stu3.setScore(8);

  Student stu4 = new Student();

  stu4.setName("dark");
  stu4.setSex(1);
  stu4.setPhone("16700555892");
  stu4.setScore(6);

  students.add(stu1);
  students.add(stu2);
  students.add(stu3);
  students.add(stu4);

  return students;
 }

}

如果用流的话是这样子的。

public static void main(String[] args) {
  // TODO Auto-generated method stub

  List<Student> stus = getSources();

  List<String> names = stus.stream()
    .filter(st -> st.getScore() > 5)
    .limit(2)
    .map(st -> st.getName())
    .collect(toList());
  for (String name : names) {
   System.out.println(name);
  }

 }

把这俩段代码相比较主要是为了说明一个概念:以前做法都是在外部迭代,最为体现就是笔者在外面定义了一个集合names 。而流却什么也没有,现在我们应该能清楚感受到流是在内部迭代。也就是说流已经帮你做好了迭代。我们只要传入相关的函数就可以得到想要的结果。至于内部迭代的好处,笔者没有办法亲身的感受,唯一的感觉就是代码变的简单明了了。但是官方说Stream库为了我们在内部迭代里面做了很多优化和充公利用性能的操作。比如并行操作。所以笔者就听官方了。

事实上,在用流的过程中,我们用到很多方法函数。比如上面的limit方法,filter方法等。这个定义为流操作。但是不管是什么操作,你必须要有一个数据源吧。总结如下:

  1. 数据源:用于生成流的数据,比如集合。
  2. 流操作:类似于limit方法,filter方法。

流还有一种特点——部分流操作是没有执行的。一般都是在collect函数执行的时候,才开始执行个个函数。所以我们可以细分一下流操作:

  1. 数据源:用于生成流的数据,比如集合。
  2. 中间操作:类似于limit方法,filter方法。这些操作做变了一个操作链,有一点流水线的概念。
  3. 终端操作:执行上面的操作链。比如collect函数。

从上面的讲解我们就可以感觉流好像是先收集相关的目标操作,什么意思呢?就是先把要做的事情计划一下,最后一声令下执行。而下这个命令是collect函数。这一点跟.NET的Linq是很像的。同时记得他只能执行一次。也就是说这个流执行一次之后,就不可能在用了。
笔者列一下以前的用到的函数

forEach:终端
collect:终端
count:终端
limit:中间
filter:中间
map:中间
sorted:中间

到目前为止我们用到的流都是通过集合来建一个流。笔者对此从来没有讲过。现在笔者来讲些构建流的方式。
在stream库里面为我们提供了这样子一个方法——Stream.of

package com.aomi;

import java.util.Optional;
import java.util.stream.Stream;

public class Main {

	public static void main(String[] args) {
		// TODO Auto-generated method stub

		Stream stream = Stream.of("I", "am", "aomi");

		Optional<String> firstWord = stream.findFirst();
		
		if(firstWord.isPresent())
		{
			System.out.println("第一个字:"+firstWord.get());
		}
	}

}

运行结果:

去看一下of方法的代码。如下

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

说明我们可能指定一个类型来建一个流。上面可以修改为

Stream<String> stream = Stream.of("I", "am", "aomi");

findFirst函数用于表示返回第一个值。那就是可能数据源是一个空呢?所以他有可以会返回null。所以就是用一个叫Optional类的表示可以为空。这样子我们就可以用Optional类的方法进一步做安全性的操作。比如判断有没有值(isPresent())
笔者想要建一个int类型的数组流玩玩。为了方便笔者便试给一下上面的代码。却发现报错了。

如果我把int改为Integer呢?没有问题了。所以注意要用引用类型的。int类型对应为Integer类型。

package com.aomi;

import java.util.Optional;
import java.util.stream.Stream;

public class Main {

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  Stream<Integer> stream = Stream.of(1, 2, 9);

  Optional<Integer> firstWord = stream.findFirst();
  
  if(firstWord.isPresent())
  {
   System.out.println("第一个字:"+firstWord.get());
  }
 
 }

}

运行结果:

那想要用int类型呢?什么办呢?改改

package com.aomi;

import java.util.OptionalInt;
import java.util.stream.IntStream;

public class Main {

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  IntStream stream = IntStream.of(1, 2, 9);

  OptionalInt firstWord = stream.findFirst();
  
  if(firstWord.isPresent())
  {
   System.out.println("第一个字:"+firstWord.getAsInt());
  }
 
 }

}

运行结果:

我们以上面的例子来一个猜测:是不是Double类型,只要修改为DoubleStream就行呢?试试。

package com.aomi;

import java.util.OptionalDouble;
import java.util.stream.DoubleStream;

public class Main {

 public static void main(String[] args) {
  // TODO Auto-generated method stub

  DoubleStream stream = DoubleStream.of(1.3, 2.3, 9.5);

  OptionalDouble firstWord = stream.findFirst();
  
  if(firstWord.isPresent())
  {
   System.out.println("第一个字:"+firstWord.getAsDouble());
  }
 
 }

}

运行结果:

结果很明显,我们的猜测是对的。所以见意如果你操作的流是一个int或是double的话,请进可能的用XxxStream 来建流。这样子在流的过程中不用进行拆装和封装了。必竟这是要性能的。在看一下如果数据源是一个数组的情况我们如何生成流呢?

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);
 }

在看一个叫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);
 }

我们发现他会共同的返回一个Collector类型。从上面我们就可以知道他的任务就是用去处理最后数据。我们把他定为收集器。让我们看一下收集器的接口代码吧;

public interface Collector<T, A, R> {

 Supplier<A> supplier();

 BiConsumer<A, T> accumulator();

 BinaryOperator<A> combiner();

 Function<A, R> finisher();

 Set<Characteristics> characteristics();
}

光看前面四个方法是不是有一点熟悉的感觉。想要说明这个五个方法的作用。就必须明白一个概念——并行归约。前面笔者讲过流是一个内部迭代,也说Stream库为我们做一个很多优化的事情。其中一个就是并行。他用到了JAVA 7引入的功能——分支/合并框架。也就是说流会以递归的方式拆分成很多子流,然后子流可以并行执行。最后在俩俩的子流的结果合并成一个最终结果。而这俩俩合并的行为就叫归约 。如图下。引用于《JAVA8实战》

我们必须根据图上的意思来走。子流的图里面会调用到Collector类的三个方法。

  1. supplier方法:用创建数据存储的地方。
  2. accumulator方法:用于子流执行过程的迭代工作。即是遍历每一项都会执行。所以可以这里做一些工作。
  3. finisher方法:返回最后的结果,你可以在这里进一步处理结果。

每一个子流结束这之后,就是俩俩合并。这个时候就要看流的机制图了。

  1. combiner方法:会传入每一个子流的结果过来,我们就可以在这里在做一些工作。
  2. finisher方法:返回最后的结果。同上面子流的一样子。

好像没有characteristics什么事情。不是这样子的。这个方法是用来说明当前这个流具备哪些优化。这样子执行流的时候,就可以很清楚的知道要以什么样子的方式执行了。比如并行。
他是一个enum类。值如下

  1. UNORDERED:这个表示执行过程中结果不受归约和遍历的影响
  2. CONCURRENT:表示可以多个线和调用accumulator方法。并且可以执行并行。当然前无序数据的才并行。除非收集器标了UNORDERED。
  3. IDENTITY_FINISH:表示这是一个恒等函数,就是做了结果也一样子。不用做了可以跳过了。

由了上面的讲说明,我们在来写一个自己的收集器吧——去除相同的单词
DistinctWordCollector类:

package com.aomi;

import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;

public class DistinctWordCollector implements Collector<String, List<String>, List<String>> {

 @Override
 public Supplier<List<String>> supplier() {
  // TODO Auto-generated method stub
  return () -> new ArrayList<String>();
 }

 /**
  * 子流的处理项的过程
  */
 @Override
 public BiConsumer<List<String>, String> accumulator() {
  // TODO Auto-generated method stub
  return (List<String> src, String val) -> {

   if (!src.contains(val)) {
    src.add(val);
   }
  };
 }

 /**
  * 俩俩并合的执行函数
  */
 @Override
 public BinaryOperator<List<String>> combiner() {
  // TODO Auto-generated method stub
  return (List<String> src1, List<String> src2) -> {
   for (String val : src2) {
    if (!src1.contains(val)) {
     src1.add(val);
    }
   }
   return src1;
  };
 }

 @Override
 public Function<List<String>, List<String>> finisher() {
  // TODO Auto-generated method stub
  return Function.identity();
 }

 @Override
 public Set<Characteristics> characteristics() {
  // TODO Auto-generated method stub
  return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH, Characteristics.CONCURRENT));
 }

}

 Main:

public static void main(String[] args) {
  // TODO Auto-generated method stub

  List<String> words = Arrays.asList("aomi","lili","lucy","aomi","Nono");

  List<String> vals = words.stream().collect(new DistinctWordCollector());
  
  for (String val : vals) {
   System.out.println(val);
  }
 }

运行结果

结果确定就是我们想要的——去掉了重复的aomi

以上所述是小编给大家介绍的JAVA8流的概念和收集器详解整合,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对小牛知识库网站的支持!

 类似资料:
  • 本文向大家介绍浅谈关于Java的GC垃圾回收器的一些基本概念,包括了浅谈关于Java的GC垃圾回收器的一些基本概念的使用技巧和注意事项,需要的朋友参考一下 一、基本回收算法 1. 引用计数(Reference Counting) 比较古老的回收算法。原理是此对象有一个引用,即增加一个计数,删除一个引用则减少一个计数。垃圾回收时,只用收集计数为0的对象。此算法最致命的是无法处理循环引用的问题。 2.

  • 本文向大家介绍浅谈java8 stream flatMap流的扁平化操作,包括了浅谈java8 stream flatMap流的扁平化操作的使用技巧和注意事项,需要的朋友参考一下 概念: Steam 是Java8 提出的一个新概念,不是输入输出的 Stream 流,而是一种用函数式编程方式在集合类上进行复杂操作的工具。简而言之,是以内部迭代的方式处理集合数据的操作,内部迭代可以将更多的控制权交给集

  • 问题内容: 我试图理解模型上的“胖”与控制器上的“瘦”的概念,根据我一直在讨论的内容,我有以下示例(取自freenode的讨论): 问:在MVC范例中,其所说的Fat模型是瘦控制器。我在这里思考的是,如果我有很多方法(在控制器上)仅对CRUD使用了一些抽象方法(在模型上),那么我是在创建胖控制器而不是模型吗?还是他们说,胖模子,退回了什么却没有输入?这是我从未理解过的东西=)任何评论表示赞赏!非常

  • 主要内容:什么是输入/输出流,输入流,输出流在 Java 中所有数据都是使用流读写的。 流是一组有序的数据序列,将数据从一个地方带到另一个地方。 根据数据流向的不同,可以分为输入(Input)流和输出(Output)流两种。 在学习输入和输出流之前,我们要明白为什么应用程序需要输入和输出流。例如,我们平时用的 Office 软件,对于 Word、Excel 和 PPT 文件,我们需要打开文件并读取这些文本,和编辑输入一些文本,这都需要利用输

  • 本文向大家介绍浅谈java泛型的作用及其基本概念,包括了浅谈java泛型的作用及其基本概念的使用技巧和注意事项,需要的朋友参考一下 一、泛型的基本概念 java与c#一样,都存在泛型的概念,及类型的参数化。java中的泛型是在jdk5.0后出现的,但是java中的泛型与C#中的泛型是有本质区别的,首先从集合类型上来说,java 中的ArrayList<Integer>和ArrayList<Stri

  • 在Java1.7之前,我很高兴地使用接口来实现OOPs概念封装。意思是如果我想对最终用户隐藏实现,那么我将只与他们共享接口,他们可以使用该接口调用我的API,例如EJB。 在Java1.7之前,上面的都是有效的,现在在Java8中,我可以用和关键字在接口中编写方法的实现,例如: 这在Java1.8中是完全有效的。 现在我的问题是: 顺便说一句,我读过Java文档,其中指出: 默认方法使您可以向库的