半年前开始试着使用 Java 的新特性,给我印象最深的就是 Stream 流和 Optional。其中 Stream 提高了看法效率,让代码看起来十分清爽。
为什么要使用流?
摘要中已经说明了,为了提高开发效率。流可以帮助我们高效操作 集合
,流帮助我们通过 流水线
的方式对集合进行 删减
、 合并
、 排序
、 修改
,并最终返回我们想要的元素数据或统计数据。 流水线
的意思是说,一批元素不需要等待全部元素都完成某步操作,才进行下步操作,而是可以尽早进行下步操作,就好像流水线工厂一样,这为流高效运作提供了基础。流还有一个 内部迭代
的概念,就是把 for 循环显示迭代隐藏起来了,这样可以更方便的 并行开发
。
流的使用
准备
在使用流之前需要做些准备,首先说明两个概念:中间操作
和 终端操作
。 中间操作
就好比生产车间的一步一步元素处理,每步处理之后,里面的元素样式或者数据结构可能发生改变了,但是它还是流。终端操作
就好比产品生产完了,要打包装箱,变成最终消费者可见的最终形态,此时,它已经是产品,不再是流了。
接着为了演示操作,先模拟几条数据
List<JSONObject> menu = new ArrayList<>();
menu.add(new JSONObject().putOpt("name","宫保鸡丁").putOpt("price","28"));
menu.add(new JSONObject().putOpt("name","鱼香肉丝").putOpt("price","30"));
menu.add(new JSONObject().putOpt("name","肉夹馍").putOpt("price","6"));
menu.add(new JSONObject().putOpt("name","煎饼").putOpt("price","6"));
常用的中间操作
filter
filter 应该是 Stream 操作里面最常见的了,过滤器顾名思义就是过滤数据用的,filter 的参数可以是 lambda 表达式。
// 比如下面这句话,就是得到所有价格小于 10 的食物,得到的还是流。//stream()方法将集合转成流
menu.stream().filter(jsonObject -> jsonObject.getInt("price")<10);
distinct、limit 和 skip
distinct 是去重,limit 是截取前几个元素,skip 是跳过前多少个元素。
List<Integer> integerList = new ArrayList<>();
integerList.add(1);
integerList.add(1);
integerList.add(2);
integerList.add(3);
integerList.stream()
.distinct()// 经过去重,流还剩 1、2、3
.skip(1)// 跳过第一个元素,流中还有 2、3
.limit(1);// 截取第一个元素,流中还剩 2
map
map 映射,上面的 filter 是将元素筛选,map 则是改变元素的样式。比如,我们想要知道所有小于 10 块食物的名字。
menu.stream()
.filter(jsonObject -> jsonObject.getInt("price")<10)// 此时还是 jsonObject
.map(jsonObject -> jsonObject.getStr("name"));// 此时变成了 String
flatMap
流的合并,可以将多个数组合并操作,这样返回元素不是流,而是具体元素本身了。
Stream.of(menu,foreignMenu)// 此时元素是流 List<Stream>
.flatMap(x -> x.stream())// 此时元素是 jsonObject List<jsonObject>
.map(jsonObject -> jsonObject.getStr("name"))
.distinct();
常用的终端方法
allMatch、anyMatch、noneMatch、findFirst 和 findAny
方法 | 含义 |
---|---|
allMatch | 检查谓词是否匹配所有元素 |
anyMatch | 检查谓词是否至少匹配一个元素 |
noneMatch | 确保流中没有任何元素与给定的谓词匹配 |
findFirst | 查找第一个符合条件的元素 |
findAny | 将返回当前流中的任意元素 |
// 前三个方法都是返回 boolean 类型
boolean allMatchBool = menu.stream()
.allMatch(jsonObject -> jsonObject.getInt("price") < 10);
boolean noneMatchBool = menu.stream()
.noneMatch(jsonObject -> jsonObject.getInt("price") < 10);
boolean anyMatchBool = menu.stream()
.anyMatch(jsonObject -> jsonObject.getInt("price") < 10);
上面个方法返回的都是 boolean 类型,findFirst、findAny 返回的都是元素。
// 关于 Optional,先不关心,总之是元素就对了
Optional<JSONObject> first = menu.stream().findFirst();
Optional<JSONObject> any = menu.stream().findAny();
System.out.println(first.get().toString());
System.out.println(any.get().toString());
// 输出
//{"price":"28","name":"宫保鸡丁"}
//{"price":"28","name":"宫保鸡丁"}
以上两个方法,只要找到符合条件的数据,流就提前结束了。为什么都是输出第一个元素,却要实现有两个方法呢?因为 并行
,findAny 在并行方面限制会少一些。
reduce
最开始的时候说了,最终的返回值可以是元素集合,也可以是统计数据 (或者说归纳),比如说元素求和。假设我们需要menu
中所有食品各要一份需要花多少钱。
Optional<Integer> price = menu.stream()//List<JsonObject>
.map(jsonObject -> jsonObject.getInt("price"))// 先将元素转成数字 List<Integer>
.reduce((x, y) -> x + y);
System.out.println(price.get());
max 和 min
这个好理解,就是最大值和最小值嘛。效果类似于
.reduce(Integer::max)
.reduce(Integer::min)
常用流汇总
其中没有展示 sorted、count 这个都好理解。至于 collect 这个后面讲,用的比较多。
流的转化
除了对象流(Stream)以外,还有一些类型流,比如说 IntStream(以 IntStream 举例,其他类似)上面求和返回的是 Optional 对象,那可以直接返回 Integer 类型吗?
// 使用映射方法 mapToInt()就 ok 了
int price = menu.stream()//Stream
.mapToInt(jsonObject -> jsonObject.getInt("price"))//IntStream
.sum();
// 类型流转化回对象流,可以使用 boxed()
IntStream intStream = menu.stream()
.mapToInt(jsonObject -> jsonObject.getInt("price"));
Stream<Integer> boxed = intStream.boxed();
// 当然了 IntStream 中有很多 int 类型操作的方法,就不一一举例了,源码打开一看,见名知意
收集器
前面讲的常用的中间操作,返回值都是流,还有一些中断操作,返回值都是 Optional 或者数值。可别忘了 Stream 最开始的初衷是为了解决集合操作问题。最终转化成集合使用的中断操作 collect,参数是接口 Collector
,里面有众多转化方法。
转换成集合
最常用的莫非 toList() 这个方法了,将返回结果变成 List。
List<JSONObject> list = menu.stream()
.filter(jsonObject -> jsonObject.getInt("price") < 10)
.collect(Collectors.toList());
// 当然还有 toSet()等等,触类旁通
字符串拼接
比较常用,就是字符串链接了。使用 joining()方法
String s = menu.stream()
.filter(jsonObject -> jsonObject.getInt("price") < 10)
.map(jsonObject -> jsonObject.getStr("name"))
.collect(Collectors.joining(","));
分组
根据提供的属性分组,使用 groupingBy(),为了方便说明,给上面各种食品一个 type 值:
List<JSONObject> menu = new ArrayList<>();
menu.add(new JSONObject().putOpt("name","宫保鸡丁").putOpt("price","28").putOpt("type","good"));
menu.add(new JSONObject().putOpt("name","鱼香肉丝").putOpt("price","30").putOpt("type","good"));
menu.add(new JSONObject().putOpt("name","肉夹馍").putOpt("price","6").putOpt("type","normal"));
menu.add(new JSONObject().putOpt("name","煎饼").putOpt("price","6").putOpt("type","normal"));
Map<String, List<JSONObject>> type = menu.stream()
.collect(Collectors.groupingBy(jsonObject -> jsonObject.getStr("type")));
System.out.println(type);
// 输出
//{normal=[{"price":"6","name":"肉夹馍","type":"normal"}, {"price":"6","name":"煎饼","type":"normal"}], good=[{"price":"28","name":"宫保鸡丁","type":"good"}, {"price":"30","name":"鱼香肉丝","type":"good"}]}
与分组类似的还有一个方法 partitioningBy (), 分区,不过它的参数位于是 boolean
类型。
我的公众号
我的公众号用于博客同步