Java8由于引入了Lamdba表达式这种非常方便的表示方式,使用Lambda可以简化整个集合类的遍历操作,所以也就为Stream提供了基础,Stream非常类似MongoDB中的集合处理,通过管道的形式来完成数据的遍历和聚合,读这一篇文章的前提条件是了解Java8的Lamdba表达式。
Stream预览
先看一个简单的例子。
public class BaseStream {
public static void main(String[] args) {
List<Student> stuList = Arrays.asList(
new Student("张三","001",19),
new Student("李四","005",22),
new Student("王五","010",14),
new Student("赵六","004",18),
new Student("何琦","006",12)
);
stuList.stream()
.filter(s->s.getAge()>16)
.forEach(s->System.out.println(s.getName()));
}
}
首先创建了一个list的列表,通过stream()方法可以将列表转换为一个Stream对象,Stream就具备了管道的功能,代码中首先执行了filter,首先看filter的源码
Stream<T> filter(Predicate<? super T> predicate);
该方法传入了Predicate这个Function接口,这个函数接口可以接受一个对象,返回一个boolean的值,所以我们返回了age大于16的人,此时就等于管道中只有年龄大于16岁的所有student,之后将这些数据向后提交,之后执行了forEach管道,forEach上一讲已经介绍过了,可以传入一个对象,没有返回值,在代码中我们通过输出的方式输出了这个对象。通过这个简单的例子我们可以快速了解java8的Stream,相信Stream会在未来成为java的一种重要的集合处理手段,下面我们将主要介绍Stream中常用的几个方法。
Stream的创建方式
只要是集合类都可以创建Stream,其中数组也可以支持,下面的代码显示了基于List的集合类和基于map的集合类操作,所有的Collection都有stream()方法直接将集合类转换为Stream
public class FirstStream {
public static void main(String[] args) {
List<String> strs = Arrays.asList("a1","a2","a3","a4","a5");
strs.stream().forEach(System.out::println);
Map<String,String> maps = new HashMap<String,String>();
maps.put("001","str1");
maps.put("002","str2");
maps.put("003","str3");
maps.put("004","str4");
maps.entrySet().stream().forEach(System.out::println);
maps.keySet().stream().forEach(System.out::println);
maps.values().stream().forEach(System.out::println);
}
}
处理List和Map之外,还可以直接将字符串,字符数组等类型直接转换为Stream,主要使用Stream.of方法
public class SecondStream {
public static void main(String[] args) {
//转换为字符的stream,转换后的值是int类型,stream的类型是IntStream
"abcdefg0".chars().forEach(System.out::println);
//基于字符的方式输出
"abcdefg0".chars().forEach(c-> System.out.println((char)c));
//转换为数组
int[] nums = {1,2,3,4,5,6};
Stream.of(nums).forEach(System.out::println);
//将数组转换为stream,使用Stream.of
Stream.of("hello,world,good,how,are,you".split(","))
.forEach(System.out::println);
//直接使用of的参数创建Stream
Stream.of("a","b","c").forEach(System.out::println);
}
}
还可以直接使用Lamdba来创建Stream,使用Stream的generate方法,该方法会生成一个无限的Stream流,参数是一个Supplier的函数表达式,Supplier函数接口中的方法是获取一个对象,此时只要配合limit即可创建一些满足要求的随机数流。
Stream.generate(()->(int)(Math.random()*100))
.limit(20).forEach(System.out::println);
通过iterate可以生成某种序列的流,iterate的第一个参数是种子,种子等于第一个数,之后按照第二个参数类推出去,例子如下,生成2的倍数的流
Stream.iterate(1,i->i*2)
.limit(10).forEach(System.out::println);
Stream可以通过limit、distinct、sorted、filter等方法对流进行处理,这些处理完成之后都返回了流对象
List<String> s = Arrays.asList("1","4","2","1","5","4","2","8","5");
s.stream().distinct().sorted().limit(4)
.forEach(System.out::println);
distinct表示取唯一值,sorted表示排序,limit表示取几个值。
Map、filter和toArray方法
首先看filter的源代码
Stream<T> filter(Predicate<? super T> predicate);
里面的参数是Predicate,这个函数接口中的函数是test,传入一个参数返回一个boolean类型的值,filter此处就是用来进行条件过滤,然后看一下map函数的代码
<R> Stream<R> map(Function<? super T, ? extends R> mapper);
参数是Function,Function中的apply方法传入一个参数,返回另一个参数,这个函数用来重组对象
public class FilterAndMap {
public static void main(String[] args) {
List<Student> list = Arrays.asList(
new Student("001","zhangsan",20),
new Student("002","lisi",30),
new Student("003","wangwu",40),
new Student("004","jake",23),
new Student("005","Leon",21)
);
list.stream().filter((t)->t.getName().startsWith("z")).forEach(System.out::println);
list.stream().filter(t->t.getAge()>25)
.map((t)->t.getName()+"-"+t.getAge()).forEach(System.out::println);
}
}
filter设置了条件过滤,而map把student转换为了字符串(name+age)的方式。看一下运行的结果
Student{no='001', name='zhangsan', age=20}
//map的结果
lisi-30
wangwu-40
Stream中提供了toArray方法来将Stream转换为数组,转换的数组类型是Object[],接下来看看两个操作
Object[] objs = list.stream().toArray();
//对单个对象进行转换
Stream.of(objs).forEach((obj)-> System.out.println(((Student)obj).getName()));
//直接转换为学生数组
Student[] stus = list.stream().toArray(Student[]::new);
Stream.of(stus).forEach(System.out::println);
收集和分组操作
Stream提供了分组操作,类似于数据库的groupby等操作,在介绍这些知识之前,我们首先需要给大家介绍一下Collectors,这是收集器,可以将stream中的东西收集到一个List、Set或者Map中。
Map<String,Integer> maps = list.stream()
.collect(Collectors.toMap(Student::getName,Student::getAge));
System.out.println(maps);
List<Student> list1 = list.stream().filter(t->t.getAge()>22)
.sorted((t1,t2)->(t1.getAge()-t2.getAge()))
.collect(Collectors.toList());
System.out.println(list1);
大家看一下结果
{jake=23, lisi=30, zhangsan=20, wangwu=40, Leon=21}
[Student{no='004', name='jake', age=23}, Student{no='002', name='lisi', age=30}, Student{no='003', name='wangwu', age=40}]
Collectors可以完成数据的收集工作,这是配合groupby的基础,接下来看一个groupby的实例,我们建一个Person的对象,看一下分组的实例
System.out.println(persons.stream().collect(Collectors.groupingBy(p->p.getCountry())));
//另外一种写法
System.out.println(persons.stream().collect(
Collectors.groupingBy(Person::getCountry,
Collectors.counting())));
Collectors中还有一个partitioningBy,这个方法的参数是一个Precidate的函数接口,它用来统计是否满足要求的数据,返回的map中有一个是boolean,另一个是List或者Array,也可以是Collectors的集合数据(counting,max)等
System.out.println(persons.stream()
.collect(Collectors.partitioningBy(t->t.getCountry().equals("USA"))));
System.out.println(persons.stream()
.collect(Collectors.partitioningBy(t->t.getCountry().equals("USA"),Collectors.counting())));
看看这四个的结果
{USA=[org.konghao.stream.Person@15aeb7ab, org.konghao.stream.Person@7b23ec81], China=[org.konghao.stream.Person@6acbcfc0, org.konghao.stream.Person@5f184fc6], Germany=[org.konghao.stream.Person@3feba861]}
{USA=2, China=2, Germany=1}
{false=[org.konghao.stream.Person@6acbcfc0, org.konghao.stream.Person@5f184fc6, org.konghao.stream.Person@3feba861], true=[org.konghao.stream.Person@15aeb7ab, org.konghao.stream.Person@7b23ec81]}
{false=3, true=2}
已上就是Stream的统计查询,这个功能非常的实用,虽然使用原来的方式可以实现,但工作量要大得多。
match操作
Stream提供了几种匹配操作,AllMatch表示要全部满足要求才返回true,noneMatch要所有不满足才返回真,anyMatch表示要有一个满足要求就返回true,看如下代码:
public class TestAllMatch {
static int i = 0;
public static void main(String[] args) {
List<Student> list = Arrays.asList(
new Student("001","zhangsan",20),
new Student("002","lisi",30),
new Student("003","wangwu",40),
new Student("004","jake",23),
new Student("005","Leon",21)
);
//只要有一个不满足要求就马上退出函数,并且返回false,AllMatch需要所有满足才为true
boolean b1 = list.stream().allMatch(p->{
boolean flag = p.getAge()>20;
System.out.println("#"+(i++));
return flag;
});
System.out.println(b1);
//所有人的年龄都大于等于20,所以返回true
boolean b2 = list.stream().allMatch(p->p.getAge()>=20);
System.out.println(b2);
//有一个满足就返回true
System.out.println(list.stream().anyMatch(p->p.getName().startsWith("z")));
//所有不满足才是true
System.out.println(list.stream().noneMatch(p->p.getName().startsWith("k")));
}
}
看看结果
#0
false
true
true
true
第一个才执行了一次就退出函数了,就表示只要满足条件就不会再做任何的判断了。
Lamdba的系列已经讲解完了,里面应该已经把入门的所有知识都讲解了,特别是Stream,目前可能很多人还不习惯这种操作方式,但是相信将来一定是一种主流的操作方式,因为它的确提供了非常便利的操作。希望这三部分的内容能够对大家有所帮助。