最近再项目中用到了lambda和Stream,发现用起来代码很简洁,就是有些复杂点的可能用完后可读性不是很理想,但是简单点的还是很好理解的,因此专门试了试,感觉真的很棒~先来了解一下
lambda语法:
1.多参数
(1). lambda表达式的基本格式为(x1,x2)->{表达式...};
(2). 在上式中,lambda表达式带有两个参数,此时参数类型可以省略,但两边的括号不能省略
(3). 如果表达式只有一行,那么表达式两边的花括号可以省略
public static void test1_() {
List usersList = new ArrayList() {
{
add(new User("user1", "stu1", 10));
add(new User("user2", "stu2", 6));
add(new User("user3", "stu3", 8));
add(new User("user4", "stu4", 7));
}
};
Collections.sort(usersList, (s1, s2) -> Integer.compare(s1.getAge(), s2.getAge()));
System.out.println(usersList);
}
2.对于没有参数的情况 :
(1).参数的括号不能省略,
(2).其他语法同多参数
如:无lambda
public static void testThread(){
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello, testThread !");
}
}).start();
}
如:通过lambda写法
public static void testThreadLambda(){
new Thread(()-> System.out.println("hello, testThreadLambda!")).start();
}
可以认为Stream是一个高级版本的iterator,原始版本的iterator,用户只能一个一个的遍历元素并对其执行某些操作,而stream,只要给出需要对其包含的元素执行什么操作,比如“获取每个字符串的首字母”,“将每个字符串的字母改为大写”等,具体的这些操作如何应用到每个元素上,交给stream就好了。
List nums = new ArrayList(){
{
add(1);
add(null);
add(2);
add(3);
add(null);
}
};
Long numCount = nums.stream().filter(num -> num != null).count();
System.out.println("numCount="+numCount);
借用别人的图来分析一下~哈哈
可以看到红色区域是Strean的生命开始的地方,负责创建一个Stream实例;绿色框中的语句是赋予Stream灵魂的地方,把一个Stream转换成另一个Stream,红框中的语句生成的是包含所有nums变量的Stream,经过绿框的filter以后,重新生成了一个过滤掉原nums列表所有null以后的Stream, 蓝色框中的语句是把Stream的里面包含的内容按照某种算法来汇聚成一个值,例子中是获取Stream中包含的元素个数。
总结一下使用Stream的基本步骤:
创建Stream,常用的两种方式,1通过Stream接口的静态工厂方法;2通过Collection接口的默认方法–stream(),把一个Collection对象转换成Stream。
转换Stream其实就是把一个Stream通过某些行为转换成一个新的Stream。Stream接口中定义了几个常用的转换方法:
1. distinct: 对于Stream中包含的元素进行去重操作(去重逻辑依赖元素的equals方法),新生成的Stream中没有重复的元素;
2. filter: 对于Stream中包含的元素使用给定的过滤函数进行过滤操作,新生成的Stream只包含符合条件的元素;
3.map: 对于Stream中包含的元素使用给定的转换函数进行转换操作,新生成的Stream只包含转换生成的元素。这个方法有三个对于原始类型的变种方法,分别是:mapToInt,mapToLong和mapToDouble。这三个方法也比较好理解,比如mapToInt就是把原始Stream转换成一个新的Stream,这个新生成的Stream中的元素都是int类型。之所以会有这样三个变种方法,可以免除自动装箱/拆箱的额外消耗;
4.flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;
5.peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;
6. limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;
7. skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;
走一波试试:
List nums = new ArrayList(){
{
add(1);
add(null);
add(2);
add(3);
add(null);
add(5);
add(3);
add(3);
add(null);
add(5);
}
};
System.out.println("sum is:"+nums.stream().filter(num -> num != null).distinct().mapToInt(num -> num *2).peek(System.out::println).skip(2).limit(4).sum());
这波先将nums进行非空过滤,留下所有非空元素,然后对于留下非空元素进行去重,然后再每个元素乘以2,再每个元素被消费的时候打印自身,在跳过前两个元素,最后去前四个元素进行加和运算。当然啦,前四个元素不够就有几个算几个啦。
性能问题:
有些细心的同学可能会有这样的疑问:在对于一个Stream进行多次转换操作,每次都对Stream的每个元素进行转换,而且是执行多次,这样时间复杂度就是一个for循环里把所有操作都做掉的N(转换的次数)倍啊。其实不是这样的,转换操作都是lazy的,多个转换操作只会在汇聚操作(见下节)的时候融合起来,一次循环完成。我们可以这样简单的理解,Stream里有个操作函数的集合,每次转换操作就是把转换函数放入这个集合中,在汇聚操作的时候循环Stream对应的集合,然后对每个元素执行所有的函数。
Stream接口有一些通用的汇聚操作,比如reduce()和collect();也有一些特定用途的汇聚操作,比如sum(),max()和count()。注意:sum方法不是所有的Stream对象都有的,只有IntStream、LongStream和DoubleStream是实例才有。