和上次文章中介绍的Lamda表达式一样,StreamAPI是Java8中添加的一个新特性,可以使我们以声明的方式非常快速的操作集合,省去非常多的无用代码,现在让我们来举个例子!
目前有一个字符串数组,记录了小组内所有人的英文名,我们需要先将字母“B”开头的名字筛选出来,全部转换为小写,清切去重复后输出!
我们先使用for循环方式实现:
public class StreamAPI {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Aaron");
names.add("Abel");
names.add("Bruce");
names.add("Bruce");
names.add("Brent");
names.add("Caleb");
List<String> newStr = new ArrayList<>();
for(String name : names) {
name = name.toLowerCase();
if(name.startsWith("b") && !newStr.contains(name))
{
newStr.add(name);
System.out.println(name);
}
}
}
}
再使用StreamAPI方式实现:
public class StreamAPI {
public static void main(String[] args) {
List<String> names = new ArrayList<>();
names.add("Aaron");
names.add("Abel");
names.add("Bruce");
names.add("Bruce");
names.add("Brent");
names.add("Caleb");
names.stream()
.map(String::toLowerCase)
.filter(n -> n.startsWith("b"))
.distinct()
.forEach(System.out::println);
}
}
可以看到,这两种方式均可以完成相同功能,但是StreamAPI可以极大地减少无用代码,且不会被一些逻辑绕晕,可以说是学了之后可以提高开发效率!
还是基于刚才的源码:
List<String> names = new ArrayList<>();
names.add("Aaron");
names.add("Abel");
names.add("Bruce");
names.add("Bruce");
names.add("Brent");
names.add("Caleb");
forEach遍历:
names.stream()
.forEach(System.out::println);
或:
names.stream()
.forEach(n -> System.out.println(n));
既可以使用Lamda表达式形式,其中参数为流中的每个元素.也可以使用函数引用形式调用静态方法!
filter过滤:
filter过滤实现了此接口
Stream<T> filter(Predicate<? super T> predicate);
用法示例:
names.stream()
.filter((n) -> n.startsWith("B"))//保留以B开头的字符串
.forEach(n -> System.out.println(n));
同时此处可以显示实现接口,这样可以保留并多次使用,并且可以调用更高级的功能!
比如:
Predicate<String> p1 = n -> n.startsWith("B");
Predicate<String> p2 = n -> n.length()==5;
names.stream()
.filter(p1.negate().and(p2))//同时符合“不以B开头”,“长度为5”两个条件的字符串才保留
.forEach(n -> System.out.println(n));
map,flatMap数据转换
names.stream()
.map(n -> n.length())//取出每一个字符串的长度
.forEach(n -> System.out.println(n));//此时输出的不再是字符串,而是长度
names.stream()
.map(n -> n.toLowerCase())//将每一个元素转换为小写
.forEach(n -> System.out.println(n));//此时输出的依然是字符串
此时我们,在进行一个操作,将这些人的名字字母后逐个输出:
names.stream()
.map(n -> n.split(""))
.forEach(n -> System.out.println(n));
结果:
我们现在想的到的是所有人名字的逐个字母,但是目前却将每个元素都转换为了字符数组,其实可以通过嵌套StreamAPI的方式来输出,但是,有没有一种方法一次性输出呢?
所以上文中,我们可以直接将数组再次转换为Stream,并且合并到原Stream中,这样可以就做到更多操作!
names.stream()
.flatMap(n -> Arrays.stream(n.split("")))
.forEach(n -> System.out.println(n));
Limit/Skip截取,Distinct去重读,Sorted排序
names.stream()
.flatMap(n -> Arrays.stream(n.split("")))
.distinct() //将上次测试结果进行去重复
.sorted() //将去重复后的结果排序
.skip(0) //从第0个元素开始截取(跳过前0个元素)
.limit(5) //截取5个元素
.forEach(n -> System.out.println(n));
parallel并行操作
我们之前所有的操作都是StreamAPI的串行操作,操作时StreamAPI会对其中每一个元素都进行顺序逐一处理,如果数据过多,会处理缓慢并阻塞线程,那么有没有办法一键进行多线程处理呢?
我们在之前案例的基础上让程序输出执行当前操作的线程:
names.stream()
.flatMap(n ->
{
System.out.println(Thread.currentThread().getName());
return Arrays.stream(n.split(""));
})
.forEach(n -> System.out.println(n));
names.stream()
.parallel()//启动多线程处理数据
.flatMap(n ->
{
System.out.println(Thread.currentThread().getName());
return Arrays.stream(n.split(""));
})
.forEach(n -> System.out.println(n));
可以看到,StreamAPI自定启动了多条线程来帮助我们处理数据,但是也导致一个问题,由于每个线程处理速度不一样,所以输出结果也从原来的顺序输出变为混乱输出,所以Stream的parallel方法启动多线程处理有好处同样也有坏处,好处是可以帮我们节省很多无用代码比如线程创建的代码,坏处是也可能造成数据混乱等多线程中会出现的问题,请大家注意!
注意:有状态操作绝对不可以使用多线程操作!
多线程状态下会因为数据操作不统一的问题造成数据混乱,而有状态操作则需要和其他元素进行比较才可以得出结果,所以不能再多线程状态下使用有状态命令,极有可能出错!
names.stream()
.parallel()
.flatMap(n ->
{
System.out.println(Thread.currentThread().getName());
return Arrays.stream(n.split(""));
})
.sorted()
.forEach(n -> System.out.println(n));
总结:StreamAPI可以极大地省去无用代码,加快我们的开发进度,提升我们的心情,并且我上方举的例子都是基础操作,而StreamAPI功能非常强大,可以对很多集合进行操作,被操作的集合也不仅仅只有字符串集合,所以,这是一个非常强大的开发技巧,希望大家掌握!