资讯专栏INFORMATION COLUMN

《java 8 实战》读书笔记 -第五章 使用流

Richard_Gao / 341人阅读

摘要:比如,你可以建立一个,选出热量超过卡路里的头三道菜请注意也可以用在无序流上,比如源是一个。跳过元素流还支持方法,返回一个扔掉了前个元素的流。一般来说,应该使用来对这种流加以限制,以避免打印无穷多个值。

一、筛选和切片 1.用谓词筛选

Streams接口支持filter方法。该操作会接受一个谓词(一个返回
boolean的函数)作为参数,并返回一个包括所有符合谓词的元素的流。例如筛选出所有素菜,创建一张素食菜单:

List vegetarianMenu = menu.stream() 
 .filter(Dish::isVegetarian) 
 .collect(toList());
2.筛选各异的元素

流还支持一个叫作distinct的方法,它会返回一个元素各异(根据流所生成元素的
hashCode和equals方法实现)的流。例如,以下代码会筛选出列表中所有的偶数,并确保没有
重复。

List numbers = Arrays.asList(1, 2, 1, 3, 3, 2, 4); 
numbers.stream() 
 .filter(i -> i % 2 == 0) 
 .distinct() 
 .forEach(System.out::println);
hashcode( )和equals( )
1.Java中的hashCode()的作用
hashCode()的作用是为了提高在散列结构存储中查找的效率,在线性表中没有作用;只有每个对象的 hash 码尽可能不同才能保证散列的存取性能,事实上 Object 类提供的默认实现确实保证每个对象的 hash 码不同(在对象的内存地址基础上经过特定算法返回一个 hash 码)。在 Java 有些集合类(HashSet)中要想保证元素不重复可以在每增加一个元素就通过对象的 equals 方法比较一次,那么当元素很多时后添加到集合中的元素比较的次数就非常多了,也就是说如果集合中现在已经有 3000 个元素则第 3001 个元素加入集合时就要调用 3000 次 equals 方法,这显然会大大降低效率,于是 Java 采用了哈希表的原理,这样当集合要添加新的元素时会先调用这个元素的 hashCode 方法就一下子能定位到它应该放置的物理位置上(实际可能并不是),如果这个位置上没有元素则它就可以直接存储在这个位置上而不用再进行任何比较了,如果这个位置上已经有元素了则就调用它的 equals 方法与新元素进行比较,相同的话就不存,不相同就散列其它的地址,这样一来实际调用 equals 方法的次数就大大降低了,几乎只需要一两次,而 hashCode 的值对于每个对象实例来说是一个固定值。
2.Java中重写equals()方法时尽量要重写hashCode()方法的原因
当 equals 方法被重写时通常有必要重写 hashCode 方法来维护 hashCode 方法的常规协定,该协定声明相等对象必须具有相等的哈希码,如果不这样做的话就会违反 hashCode 方法的常规约定,从而导致该类无法结合所有基于散列的集合一起正常运作,这样的集合包括 HashMap、HashSet、Hashtable 等

引申:HashMap实现原理及源码分析
注意:哈希冲突的解决方案有多种:开放定址法(发生冲突,继续寻找下一块未被占用的存储地址),再散列函数法,链地址法,而HashMap即是采用了链地址法,也就是数组+链表的方式

3.截短流

流支持limit(n)方法,该方法会返回一个不超过给定长度的流。所需的长度作为参数传递给limit。如果流是有序的,则最多会返回前n个元素。比如,你可以建立一个List,选出热量超过300卡路里的头三道菜:

List dishes = menu.stream() 
 .filter(d -> d.getCalories() > 300) 
 .limit(3) 
 .collect(toList()); 

请注意limit也可以用在无序流上,比如源是一个Set。这种情况下,limit的结果不会以任何顺序排列。

4.跳过元素

流还支持skip(n)方法,返回一个扔掉了前n个元素的流。如果流中元素不足n个,则返回一个空流。

List dishes = menu.stream() 
 .filter(d -> d.getCalories() > 300) 
 .skip(2) 
 .collect(toList());
二、映射 1.对流中每一个元素应用函数

提取流中菜肴的名称:

List dishNames = menu.stream() 
  .map(Dish::getName) 
  .collect(toList());
2.流的扁平化
List uniqueCharacters = 
 words.stream() 
 .map(w -> w.split("")) 
 .flatMap(Arrays::stream) 
 .distinct() 
 .collect(Collectors.toList()); 

使用flatMap方法的效果是,各个数组并不是分别映射成一个流,而是映射成流的内容。所有使用map(Arrays::stream)时生成的单个流都被合并起来,即扁平化为一个流。一言以蔽之,flatmap方法让你把一个流中的每个值都换成另一个流,然后把所有的流连接起来成为一个流。

三、查找和匹配 1.检查谓词是否至少匹配一个元素

anyMatch方法可以回答“流中是否有一个元素能匹配给定的谓词”。比如,你可以用它来看看菜单里面是否有素食可选择:

if(menu.stream().anyMatch(Dish::isVegetarian)){ 
 System.out.println("The menu is (somewhat) vegetarian friendly!!"); 
}
anyMatch方法返回一个boolean,是一个终端操作
2.检查谓词是否匹配所有元素

allMatch方法的工作原理和anyMatch类似,但它会看看流中的元素是否都能匹配给定的谓词

boolean isHealthy = menu.stream() 
 .allMatch(d -> d.getCalories() < 1000);

noneMatch和allMatch相对的是noneMatch。它可以确保流中没有任何元素与给定的谓词匹配。比如,
你可以用noneMatch重写前面的例子:

boolean isHealthy = menu.stream() 
 .noneMatch(d -> d.getCalories() >= 1000);
anyMatch、allMatch和noneMatch这三个操作都用到了我们所谓的短路
3.查找元素

findAny方法将返回当前流中的任意元素。它可以与其他流操作结合使用。比如,你可能想
找到一道素食菜肴。你可以结合使用filter和findAny方法来实现这个查询:

Optional dish = 
 menu.stream() 
 .filter(Dish::isVegetarian) 
 .findAny();

Optional简介
Optional类(java.util.Optional)是一个容器类,代表一个值存在或不存在。在上面的代码中,findAny可能什么元素都没找到。Java 8的库设计人员引入了Optional,这样就不用返回众所周知容易出问题的null了。
Optional里面几种可以迫使你显式地检查值是否存在或处理值不存在的情形的方法也不错。

isPresent()将在Optional包含值的时候返回true, 否则返回false。

ifPresent(Consumer block)会在值存在的时候执行给定的代码块。

T get()会在值存在时返回值,否则抛出一个NoSuchElement异常。

T orElse(T other)会在值存在时返回值,否则返回一个默认值。

例如,在前面的代码中你需要显式地检查Optional对象中是否存在一道菜可以访问其名称:

menu.stream() 
.filter(Dish::isVegetarian) 
.findAny() 
.ifPresent(d -> System.out.println(d.getName());
4.查找第一个元素

使用findFirst()方法

List someNumbers = Arrays.asList(1, 2, 3, 4, 5); 
Optional firstSquareDivisibleByThree = 
 someNumbers.stream() 
 .map(x -> x * x) 
 .filter(x -> x % 3 == 0) 
 .findFirst(); // 9
四、归约 1.元素求和(多归一)

有初始值

int sum = numbers.stream().reduce(0, (a, b) -> a + b); 

reduce接受两个参数:
(1) 一个初始值,这里是0;
(2) 一个BinaryOperator来将两个元素结合起来产生一个新值,这里我们用的是lambda (a, b) -> a + b。

无初始值
reduce还有一个重载的变体,它不接受初始值,但是会返回一个Optional对象:

Optional sum = numbers.stream().reduce((a, b) -> (a + b)); 
为什么它返回一个Optional呢?
考虑流中没有任何元素的情况。reduce操作无返回其和,因为它没有初始值。这就是为什么结果被包裹在一个Optional对象里,以表明和可能不存在。

2.最大值和最小值

eg:

Optional max = numbers.stream().reduce(Integer::max);//最大值
Optional min = numbers.stream().reduce(Integer::min);//最小值
map和reduce的连接通常称为map-reduce模式,因Google用它来进行网络搜索而出名,因为它很容易并行化。
诸如map或filter等操作会从输入流中获取每一个元素,并在输出流中得到0或1个结果,这些操作一般都是无状态的;但诸如reduce、sum、max等操作需要内部状态来累积结果,我们把这些操作叫作有状态操作
五、数值流 1.原始类型流特化 1.1 映射到数值流

Java 8引入了三个原始类型特化流接口来解决这个问题:IntStream、DoubleStream和LongStream,分别将流中的元素特化为int、long和double,从而避免了暗含的装箱成本。个接口都带来了进行常用数值归约的新方法,比如对数值流求和的sum,找到最大元素的max,以及min、average等。
可以像下面这样用mapToInt对menu中的卡路里求和:

int calories = menu.stream() 
 .mapToInt(Dish::getCalories) 
 .sum();
请注意,如果流是空的,sum默认返回0。
1.2 转换回对象流

要把原始流转换成一般流(每个int都会装箱成一个Integer),可以使用boxed方法,如下所示:

IntStream intStream = menu.stream().mapToInt(Dish::getCalories); 
Stream stream = intStream.boxed();
1.3 默认值OptionalInt

求和的那个例子很容易,因为它有一个默认值:0。但是,如果你要计算IntStream中的最大元素,就得换个法子了,因为0是错误的结果。对于三种原始流特化,分别有一个Optional原始类型特化版本:OptionalInt、OptionalDouble和OptionalLong。

例如,要找到IntStream中的最大元素,可以调用max方法,它会返回一个OptionalInt:

OptionalInt maxCalories = menu.stream() 
 .mapToInt(Dish::getCalories) 
 .max(); 

现在,如果没有最大值的话,你就可以显式处理OptionalInt去定义一个默认值了:

int max = maxCalories.orElse(1);
2.数值范围

假设你想要生成1和100之间的所有数字。Java 8引入了两个可以用于IntStream和LongStream的静态方法,帮助生成这种范围:
range和rangeClosed。这两个方法都是第一个参数接受起始值,第二个参数接受结束值。但
range是不包含结束值的,而rangeClosed则包含结束值。

IntStream evenNumbers = IntStream.rangeClosed(1, 100) 
 .filter(n -> n % 2 == 0); 
System.out.println(evenNumbers.count());
六、构建流 1.由值创建流

你可以使用静态方法Stream.of,通过显式值创建一个流。它可以接受任意数量的参数。例如,以下代码直接使用Stream.of创建了一个字符串流。然后,你可以将字符串转换为大写,再一个个打印出来:

Stream stream = Stream.of("Java 8 ", "Lambdas ", "In ", "Action"); 
stream.map(String::toUpperCase).forEach(System.out::println); 

你可以使用empty得到一个空流,如下所示:

Stream emptyStream = Stream.empty();
2.由数组创建流
int[] numbers = {2, 3, 5, 7, 11, 13}; 
int sum = Arrays.stream(numbers).sum();
3.由文件生成流
Files.lines,它会返回一个由指定文件中的各行构成的字符串流。

用这个方法看看一个文件中有多少各不相同的词:

long uniqueWords = 0; 
try(Stream lines = 
 Files.lines(Paths.get("data.txt"), Charset.defaultCharset())){ 
uniqueWords = lines.flatMap(line -> Arrays.stream(line.split(" ")))
 .distinct() 
 .count(); 
} catch(IOException e){ 
 //如果打开文件时出现异常则加以处理
}
4.由函数生成流:创建无限流

Stream API提供了两个静态方法来从函数生成流:Stream.iterate和Stream.generate。一般来说,应该使用limit(n)来对这种流加以限制,以避免打印无穷多个值。

4.1 迭代
Stream.iterate(0, n -> n + 2) 
 .limit(10) 
 .forEach(System.out::println); 
iterate方法接受一个初始值(在这里是0),还有一个依次应用在每个产生的新值上的Lambda(UnaryOperator类型)。这里,我们使用Lambda n -> n + 2,返回的是前一个元素加上2。
4.2 生成

与iterate方法类似,generate方法也可让你按需生成一个无限流。但generate不是依次对每个新生成的值应用函数的。它接受一Supplier类型的Lambda提供新的值。我们先来
看一个简单的用法:

Stream.generate(Math::random) 
 .limit(5) 
 .forEach(System.out::println); 

这段代码将生成一个流,其中有五个0到1之间的随机双精度数。

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/74193.html

相关文章

  • java 8 实战读书笔记 -第七章 并行数据处理与性能

    摘要:正确使用并行流错用并行流而产生错误的首要原因,就是使用的算法改变了某些共享状态。高效使用并行流留意装箱有些操作本身在并行流上的性能就比顺序流差还要考虑流的操作流水线的总计算成本。 一、并行流 1.将顺序流转换为并行流 对顺序流调用parallel方法: public static long parallelSum(long n) { return Stream.iterate(1L...

    刘福 评论0 收藏0
  • java 8 实战读书笔记 -第四章 引入

    摘要:第四章引入流一什么是流流是的新成员,它允许你以声明性方式处理数据集合通过查询语句来表达,而不是临时编写一个实现。 第四章 引入流 一、什么是流 流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码。 下面两段代码都是用来返回低...

    jeyhan 评论0 收藏0
  • Java8实战》-读书笔记第一章(02)

    摘要:实战读书笔记第一章从方法传递到接着上次的,继续来了解一下,如果继续简化代码。去掉并且生成的数字是万,所消耗的时间循序流并行流至于为什么有时候并行流效率比循序流还低,这个以后的文章会解释。 《Java8实战》-读书笔记第一章(02) 从方法传递到Lambda 接着上次的Predicate,继续来了解一下,如果继续简化代码。 把方法作为值来传递虽然很有用,但是要是有很多类似与isHeavy...

    lushan 评论0 收藏0
  • java 8 实战读书笔记 -第六章 用收集数据

    摘要:分区函数返回一个布尔值,这意味着得到的分组的键类型是,于是它最多可以分为两组是一组,是一组。当遍历到流中第个元素时,这个函数执行时会有两个参数保存归约结果的累加器已收集了流中的前个项目,还有第个元素本身。 一、收集器简介 把列表中的交易按货币分组: Map transactionsByCurrencies = transactions.stream().collect(groupi...

    Airy 评论0 收藏0
  • Java8实战》-第四章读书笔记(引入Stream)

    摘要:内部迭代与使用迭代器显式迭代的集合不同,流的迭代操作是在背后进行的。流只能遍历一次请注意,和迭代器类似,流只能遍历一次。 流(Stream) 流是什么 流是Java API的新成员,它允许你以声明性方式处理数据集合(通过查询语句来表达,而不是临时编写一个实现)。就现在来说,你可以把它们看成遍历数据集的高级迭代器。此外,流还可以透明地并行处理,你无需写任何多线程代码了!我会在后面的笔记中...

    _ivan 评论0 收藏0
  • Java8实战》-第六章读书笔记(用收集数据-01)

    摘要:收集器用作高级归约刚刚的结论又引出了优秀的函数式设计的另一个好处更易复合和重用。更具体地说,对流调用方法将对流中的元素触发一个归约操作由来参数化。另一个常见的返回单个值的归约操作是对流中对象的一个数值字段求和。 用流收集数据 我们在前一章中学到,流可以用类似于数据库的操作帮助你处理集合。你可以把Java 8的流看作花哨又懒惰的数据集迭代器。它们支持两种类型的操作:中间操作(如 filt...

    EscapedDog 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<