首先附上官方文档https://docs.oracle.com/javase/8/docs/api/java/util/stream/package-summary.html#NonInterference。英文水平还不错的看官方文档那肯定比往下看好很多。
stream咋一看和io流中的InputStream和OutputStream很像,但是它们之间并没有任何关系,被称作流操作是因为很像是流水线操作。stream分为中间操作和终止操作,怎么理解?类似做披萨,我们可以加很多东西,比如加菠萝、加榴莲、加BBQ等等,这些就是中间操作,而放进烤箱出炉,这就是终止操作。
Stream stream = Arrays.asList("a1", "a2", "b1", "c2", "c1").stream();
讲了半天,stream流怎么来,怎么创建?通常和Collection 挂钩,如list、set,调用stream()方法变可以获取流。stream有很多方法,这些方法能让我们往披萨加自己喜欢的东西。
这些是常见的一些方法,像foreach,看这个词就知道是遍历用,并且是用void修饰,返回的不是流,这无疑是一个终止操作。
Stream stream = Arrays.asList("a1", "a2", "b1", "c2", "c1").stream();
stream.forEach(s -> System.out.println(s));
这就是一个简单的遍历输出,而且这代码还可以更加简洁
Arrays.asList("a1", "a2", "b1", "c2", "c1")
.stream()
.forEach(System.out::println);
这里涉及的Lmabda表达式我不讲,怕误人子弟,其实用多了也就好了。讲到循环,这里提一下IntStream。
List list = new ArrayList<>();
IntStream
.range(0,4)
.forEach(i->list.add("Test"+i));
System.out.println(list);
// [Test0, Test1, Test2, Test3]
是不是比写for循环更加简洁方便,人总是懒的,少些一点是一点。这就是stream的终止操作之一foreach
Stream filter(Predicate super T> predicate);
过滤,也大概能理解这个方法的作用。返回值是Stream,就说明这个是中间操作,对流中数据的选取过滤。
Arrays.asList("a1", "a2", "b1", "c2", "c1")
.stream()
.filter(s -> s.startsWith("c"))
.forEach(System.out::println);
//c2
//c1
选取以c字母开头的数据,不用写for循环遍历的感觉是真的爽。
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
* 返回由将给定函数应用于此流元素的结果组成的流。
*/
Stream map(Function super T, ? extends R> mapper);
上面是百度给出的翻译,有点难懂,什么叫返回由将给定函数应用于此流元素的结果组成的流。听起来和filter过滤不是很像么?
Arrays.asList("a1", "a2", "b1", "c2", "c1")
.stream()
.filter(s -> s.startsWith("c"))
.map(s -> s.replaceAll("c","a"))
.map(s -> s.toUpperCase())
.forEach(System.out::println);
//A2
//A1
filter是对流进行过滤、筛检,而map则是对流中数据处理后返回新的流。中间操作可以接多个,第一个map将字母c换成a,第二个map将字母大写,输出的是A2 A1。简单的例子往往使方法的作用一目了然。
java代码一般执行顺序都是由上往下,遇到方法进入执行方法还是这个从上往下的顺序。那么流呢?
Arrays.asList("a1", "a2", "b1", "c2", "c1")
.stream()
.filter(s -> {
System.out.println("filter:"+s);
return true;
});
//无输出
这代码为什么没有输出?流操作只有在有终止操作的时候,中间操作才会执行
Arrays.asList("a1", "a2", "b1", "c2", "c1")
.stream()
.filter(s -> {
System.out.println("filter:"+s);
return true;
})
.forEach(s -> {
System.out.println("foreach:"+s);
});
//filter:a1
//foreach:a1
//filter:a2
//foreach:a2
//filter:b1
//foreach:b1
//filter:c2
//foreach:c2
//filter:c1
//foreach:c1
输出的顺序可以知道,并不是filter操作执行完成后,再执行foreach的终端操作。设计者是真的厉害,这个是为了性能的考虑。foreach有点难看出来效果,但是像anyMacth()和noneMatch()就很明显
/**
* 分别是流中数据一次匹配、全部匹配、一次都不匹配返回true
*
*/
boolean anyMatch(Predicate super T> predicate);
boolean allMatch(Predicate super T> predicate);
boolean noneMatch(Predicate super T> predicate);
Arrays.asList("a1", "a2", "b1", "c2", "c1")
.stream()
.filter(s -> {
System.out.println("filter:"+s);
return true;
})
.anyMatch(s -> s.startsWith("a"));
//filter:a1
这个就很明显,filter就只执行了一次。性能上很定是有提升的,获取我这例子没啥差别,可是当中间操作和流数据增加很多时,性能提升还是很可观的。中间操作执行顺序明白之后,filter自然而然应该放到最先执行,过滤掉了的数据没必要执行后面流操作
R collect(Supplier supplier,
BiConsumer accumulator,
BiConsumer combiner);
R collect(Collector super T, A, R> collector);
上面全是foreach输出,不可能咱们拿到数据就只有输出对不对。collect就是将流转为其他的对象,常见的有list、set、map集合等。上面第一个看起来参数就很复杂的暂且不管,第二参数是Collector,jdk1.8提供了Collectors,里面定义了很多Collector,经常性用到的就是toList、toSet、toMap、joining。当然Collector也可以自定义,这个稍后再说。
List list = Arrays.asList("a1", "a2", "b1", "c2", "c1")
.stream()
.filter(s -> s.startsWith("a"))
.collect(Collectors.toList());
list.forEach(System.out::println);
//a1
//a2
set集合一样,Collectors.toSet()。map稍有不同,需要指定key和value,key必须唯一,不唯一会报错。
@Data
@AllArgsConstructor
static class Hero {
String name;
int price;
}
static List list = Arrays.asList(
new Hero("魂锁典狱长", 6300),
new Hero("派克", 6300),
new Hero("布里茨", 3150),
new Hero("莫甘娜", 1350),
new Hero("风女", 1350)
);
public static void main(String[] args) {
Map map = list.stream()
.collect(Collectors.toMap(
b -> b.price,//key
b -> b.name,//value
(name1, name2) -> name1 + "----" + name2 //key相同时 value拼接方式
));
System.out.println(map);
}
//{1350=莫甘娜----风女, 6300=魂锁典狱长----派克, 3150=布里茨}
Stream flatMap(Function super T, ? extends Stream extends R>> mapper);
还是返回一个流,flatMap可以通过映射关系将流换成其他的流
@Data
@AllArgsConstructor
static class LOl {
String type;
List heroes = new ArrayList<>();
}
@Data
@AllArgsConstructor
static class Hero{
String name;
int price;
}
public static void main(String[] args) {
List list = Arrays.asList(
new LOl("sup",Arrays.asList(new Hero("魂锁典狱长", 6300))),
new LOl("top",Arrays.asList(new Hero("炼金术士", 6300))),
new LOl("mid",Arrays.asList(new Hero("刀锋之影", 6300))),
new LOl("adc",Arrays.asList(new Hero("瘟疫之源", 6300))),
new LOl("jug",Arrays.asList(new Hero("傲之追猎者", 6300)))
);
list.stream()
.flatMap(l->l.heroes.stream())
.forEach(System.out::println);
}
//Test1.Hero(name=魂锁典狱长, price=6300)
//Test1.Hero(name=炼金术士, price=6300)
//Test1.Hero(name=刀锋之影, price=6300)
//Test1.Hero(name=瘟疫之源, price=6300)
//Test1.Hero(name=傲之追猎者, price=6300)
flatMap将Lol流换成了Hero流,最终输出Hero对象。
Optional这也是jdk1.8引入的一个专门来处理空值问题的类。不作细讲,但是Optional中也有flatMap方法,特别适合多层对象空值判断问题。
static class A {
B b;
}
static class B {
C c;
}
static class C {
String test;
}
public static void main(String[] args) {
Optional.of(new A())
.flatMap(a->Optional.ofNullable(a.b))
.flatMap(b->Optional.ofNullable(b.c))
.flatMap(c -> Optional.ofNullable(c.test))
.ifPresent(System.out::println);
}
最后还剩一个自定义收集器Collector没说,这个东西我自己也才会用,怕误人子弟就不说了。有很多大佬专门讲过这个,Collectors里面提供的收集器已经很全面了,一般简单的业务逻辑都可以满足。Stream、Optional、Collectors这三个类还有其他的方法我并没有提到,有兴趣自己了解是最好的,反正现在翻译软件也挺好用,基本能自己慢慢读懂。最后,多用必定不难,多总结必定能进步