写Java代码的程序员,集合的遍历是常有的事,用惯了for循环、while循环、do while循环,咱们来点别的,JDK8 使用了新的forEach机制,结合streams,让你的代码看上去更加简洁、更加高端,便于后续的维护和阅读。好,不说了,"talk is cheap, show me the code",咱们直接上代码,秉承一向以来的风格。skr~skr~前端
JDK8中的forEach不只能够对集合元素进行遍历,也能根据集合元素的值搞些事情,好比作判断,好比过滤。咱们拿经常使用的List和Map来举例。java
对Map集合的遍历:程序员
/** * 对Map的遍历 */
Map<String, Integer> map = Maps.newHashMap();
map.put("天猫双11", 1024);
map.put("京东双11", 1024);
// ①简写式
map.forEach((k, v) -> System.out.println("key:" + k + ", value:" + v));
// ②判断式
map.forEach((k, v) -> {
System.out.println("key:" + k + ", value:" + v);
if (StringUtils.contains(k, "京东")) {
System.out.println("skr~");
}
});
复制代码
执行结果:算法
key:京东双11, value:1024
key:天猫双11, value:1024
key:京东双11, value:1024
skr~
key:天猫双11, value:1024
复制代码
对List集合的遍历:数据库
/** * 对List的遍历 */
List<String> list = Lists.newArrayList();
list.add("买买买");
list.add("剁剁剁");
// ①简写式
list.forEach(item -> System.out.println(item));
// ②判断式
list.forEach(item -> {
if (StringUtils.contains(item, "买")) {
System.out.println("不如再用两个肾换个iPhone XS Max Pro Plus也无妨啊~");
}
});
复制代码
执行结果以下:编程
买买买
剁剁剁
不如再用两个肾换个iPhone XS Max Pro Plus也无妨啊~
复制代码
JDK8中有双冒号的用法,就是把方法当作参数传到stream内部,使stream的每一个元素都传入到该方法里面执行一下。api
好比,上面的List<String>
的打印,咱们能够这样写:性能优化
// JDK8 双冒号的用法
list.forEach(System.out::println);
复制代码
执行结果也是同样同样的:bash
买买买
剁剁剁
复制代码
在 JDK8 中,接口Iterable 8默认实现了forEach方法,调用了 JDK8 中增长的接口Consumer内的accept方法,执行传入的方法参数。 JDK源码以下:less
/** * Performs the given action for each element of the {@code Iterable} * until all elements have been processed or the action throws an * exception. Unless otherwise specified by the implementing class, * actions are performed in the order of iteration (if an iteration order * is specified). Exceptions thrown by the action are relayed to the * caller. * * @implSpec * <p>The default implementation behaves as if: * <pre>{@code * for (T t : this) * action.accept(t); * }</pre> * * @param action The action to be performed for each element * @throws NullPointerException if the specified action is null * @since 1.8 */
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
复制代码
这个用法我以为是比较实用也比较经常使用的。咱们先定义两个POJO,一个叫Track,是一个Entity,和咱们的数据库表结构进行映射;另外一个叫TrackVo,是一个Vo,在接口层返回给前端展现用。这里为了简化代码量,咱们使用了lombok插件。好,先将它们定义出来:
Track.java
/** * @author huangzx * @date 2018/11/13 */
@AllArgsConstructor
@Data
@Builder
public class Track {
private Long id;
private String name;
private String anchor;
}
复制代码
TrackVo.java
/** * @author huangzx * @date 2018/11/13 */
@AllArgsConstructor
@Data
@Builder
public class TrackVo {
private Long trackId;
private String trackName;
}
复制代码
常常遇到的场景就是:我经过一个Dao层将数据fetch出来,是一个List<Track>
,但前端须要的是List<TrackVo>
,但Track和TrackVo的字段又不同。按照以前的作法,多是直接用一个for循环或while循环将List<Track>
遍历把里面的Entity赋值到TrackVo,你飞快地敲击键盘,伴随着屏幕的震动,十来行代码顿时被创造了出来,长舒一口气,大功告成!
却不知,JDK8 自从引入新的forEach,结合streams,可让这十来行代码浓缩为一行,实在是简练。来瞧一瞧:
/** * 对自定义类型的组装 */
List<Track> trackList = Lists.newArrayList();
Track trackFirst = Track.builder().id(1L).name("个人理想").anchor("方清平").build();
Track trackSecond = Track.builder().id(2L).name("台上台下").anchor("方清平").build();
trackList.add(trackFirst);
trackList.add(trackSecond);
List<TrackVo> trackVoList = trackList.parallelStream().map(track -> TrackVo.builder().trackId(track.getId()).trackName(track.getName()).build()).collect(Collectors.toList());
System.out.println(JSON.toJSONString(trackVoList));
复制代码
执行结果以下:
[{"trackId":1,"trackName":"个人理想"},{"trackId":2,"trackName":"台上台下"}]
复制代码
似不似和你预期的结果同样?
ok,秉承程序员认识一件事物——“知其然必知其因此然”的原则。咱们来分析一下forEach的实现原理。
首先,咱们要了解一下上面用到的流 (streams) 概念,以及被动迭代器。
Java 8 最主要的新特性就是 Lambda 表达式以及与此相关的特性,如流 (streams)、方法引用 (method references)、功能接口 (functional interfaces)。正是由于这些新特性,咱们可以使用被动迭代器而不是传统的主动迭代器,特别是 Iterable 接口提供了一个被动迭代器的缺省方法叫作 forEach()。缺省方法是 Java 8 的又一个新特性,是一个接口方法的缺省实现,在这种状况下,forEach() 方法其实是用相似于 Java 5 这样的主动迭代器方式来实现的。
实现了 Iterable 接口的集合类 (如:全部列表 List、集合 map) 如今都有一个 forEach() 方法,这个方法接收一个功能接口参数,实际上传递给 forEach() 方法的参数是一个 Lambda 表达式。咱们来编写一段使用 streams 的代码:
/** * @author huangzx * @date 2018/11/13 */
public class StreamCountsTest {
public static void main(String[] args) {
List<String> words = Arrays.asList("natural", "flow", "of", "water", "narrower");
long count = words.stream().filter(w -> w.length() > 5).count();
System.out.println(count);
}
}
复制代码
上面所示代码使用 Java 8 方式编写代码实现流管道计算,统计字符长度超过5的单词的个数。列表 words 用于建立流,而后使用过滤器对数据集进行过滤,filter() 方法只过滤出单词的字符长度,该方法的参数是一个 Lambda 表达式。最后,流的 count() 方法做为最终操做,获得应用结果。
咱们再对自定义类型的组装
那句代码做个解析,以下:
中间操做除了 filter() 以外,还有 distinct()、sorted()、map() 等,通常是对数据集的整理,返回值通常也是数据集。咱们能够大体浏览一下它有哪些方法,以下:
总的来讲,Stream 遵循 "作什么,而不是怎么去作" 的原则。在咱们的示例中,描述了须要作什么,好比得到长单词并对它们的个数进行统计。咱们没有指定按照什么顺序,或者在哪一个线程中作。相反,循环在一开始就须要指定如何进行计算。
网上许多人说:JDK8 的 Lambda 表达式的性能不如传统书写方式的性能。那为什么还要出现呢?JDK的新api和新语法有时并非为了性能而去作极致优化的。从理论上来讲,面向对象编程,性能相对面向过程确定是下降的,可是可维护性或清晰度有了很大的提高。
因此一个特性用与不用,取决于你关注什么,当公司给你3个月时间去作功能实现的时候,显然不会有人花1个月去作性能优化,这时候更清晰合理的代码就很重要了,大多数时候性能问题不是来自于算法和api的平庸表现,而是出自各类系统的bug。
总结一下上面讲了什么?首先是对于常见集合咱们怎么用forEach去操做,而且介绍了双冒号的用法;而后基于一个已存在的集合怎么利用它产生一个新的集合,以封装成咱们想要的数据;最后介绍了它的实现原理并阐释为什么要用它的的缘由。好了,下课。。。(老师~再见~~)