lambda表达式和Stream流 Api用法

时间:2021-01-23 19:11:30
一、lambda表达式和流Stream Api
Stream是对集合的包装,通常和lambda一起使用,使用Lambdas和Streams
使用lambdas可以支持许多操作,如 map, filter, limit, sorted, count, min, max, sum, collect,foreach 等等。

(1)lambda表达式定义:

(parameters) ->{
  statment1;
  statment2;
  //...
return statementM;}


(parameters) -> expression
当lambda表达式只包含一条语句时,可以省略大括号、return和语句结尾的分号

(2)方法引用定义(方法引用和lambda一样是Java8新语言特性):
objectName::instanceMethod
ClassName::staticMethod
ClassName::instanceMethod

前两种方式类似,等同于把lambda表达式的参数直接当成instanceMethod|staticMethod的参数来调用。比如System.out::println等同于x->System.out.println(x);Math::max等同于(x, y)->Math.max(x,y)。

最后一种方式,等同于把lambda表达式的第一个参数当成instanceMethod的目标对象,其他剩余参数当成该方法的参数。比如String::toLowerCase等同于x->x.toLowerCase()。

可以这么理解,前两种是将传入对象当参数执行方法,后一种是调用传入对象的方法。

例如:将列表中的字符串转换为全小写
List<String> proNames = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String> lowercaseNames3 = proNames.stream().map(String::toLowerCase).collect(Collectors.toList());


(3)构造器引用
构造器引用语法如下:ClassName::new,把lambda表达式的参数当成ClassName构造器的参数 。例如BigDecimal::new等同于x->new BigDecimal(x)。




(一)定义排序功能
//使用 lambda expression 排序,根据 str length  
Comparator<String> sortByNameLenght = (String s1, String s2) -> (s1.length() - s2.length());  
Arrays.sort(players, sortByNameLenght);  


(二)用lambda表达式实现Runnable
使用lambda表达式替换匿名类,而实现Runnable接口是匿名类的最好示例。

示例:
// Java 8之前:
new Thread(new Runnable() {
    @Override
    public void run() {
    System.out.println("Before Java8, too much code for too little to do");
    }
}).start();


//Java 8方式:
new Thread( () -> System.out.println("In Java8, Lambda expression rocks !!") ).start();
 
 
 (三)使用Java 8 lambda表达式进行事件处理
 如果你用过Swing API编程,你就会记得怎样写事件监听代码。这又是一个旧版本简单匿名类的经典用例,但现在可以不这样了。你可以用lambda表达式写出更好的事件监听代码,如下所示:
 
 
 // Java 8之前:
JButton show =  new JButton("Show");
show.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
    System.out.println("Event handling without lambda expression is boring");
    }
});


// Java 8方式:
show.addActionListener((e) -> {
    System.out.println("Light, Camera, Action !! Lambda expressions Rocks");
});
 


 (四)Stream流Api的使用 
(1)过滤器filter和foreach的用法


示例:  
Predicate<Person> ageFilter = (p) -> (p.getAge() > 25);  
Predicate<Person> salaryFilter = (p) -> (p.getSalary() > 1400);  
Predicate<Person> genderFilter = (p) -> ("female".equals(p.getGender()));  


phpProgrammers.stream()  
          .filter(ageFilter)  
          .filter(salaryFilter)  
          .filter(genderFilter)  
          .forEach((p) -> System.out.printf("%s %s; ", p.getFirstName(), p.getLastName()));  
  
(2)sort排序的用法


示例:
List<Person> sortedJavaProgrammers = javaProgrammers  
          .stream()  
          .sorted((p, p2) -> (p.getFirstName().compareTo(p2.getFirstName())))  
          .limit(5)  
          .collect(toList()); 


(3) Predicate有and,or,test,negate四种方法。  


// 甚至可以用and()、or()和xor()逻辑函数来合并Predicate,
// 例如要找到所有以J开始,长度为四个字母的名字,你可以合并两个Predicate并传入
Predicate<String> startsWithJ = (n) -> n.startsWith("J");
Predicate<String> fourLetterLong = (n) -> n.length() == 4;
names.stream()
    .filter(startsWithJ.and(fourLetterLong))
    .forEach((n) -> System.out.print("nName, which starts with 'J' and four letter long is : " + n));

(4) Map的用法
// 使用lambda表达式
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
costBeforeTax.stream().map((cost) -> cost + .12*cost).collect(toList()).forEach(System.out::println);




(5)flatMap的用法
 flatMap:和map类似,不同的是其每个元素转换得到的是Stream对象,会把子Stream中的元素压缩到父集合中;
 
flatMap给一段代码理解:
Stream<List<Integer>> inputStream = Stream.of(
 Arrays.asList(1),
 Arrays.asList(2, 3),
 Arrays.asList(4, 5, 6)
 );
Stream<Integer> outputStream = inputStream.
flatMap((childList) -> childList.stream());


flatMap 把 input Stream 中的层级结构扁平化,就是将最底层元素抽出来放到一起,最终 output 的新 Stream 里面已经没有 List 了,都是直接的数字。


(6)reduce的用法
reduce称为折叠操作,流API定义的 reduceh() 函数可以接受lambda表达式,并对所有值进行合并。
注意:
map()返回的Stream类只有count()方法
mapToInt()、mapToLong()、mapToDouble()返回IntStream、LongStream 和 DoubleStream 等流的类,包含average(),sum(),count()等折叠方法。


// 新方法:
List costBeforeTax = Arrays.asList(100, 200, 300, 400, 500);
double bill = costBeforeTax.stream().map((cost) -> cost + .12*cost).reduce((sum, cost) -> sum + cost).get();
System.out.println("Total : " + bill);




(7)对Map集合的处理操作  
  
  map()、mapToInt()、mapToLong()、mapToDouble()
  
转化为list .collect(toList())
// 创建一个字符串列表,每个字符串长度大于2
List<String> filtered = strList.stream().filter(x -> x.length()> 2).collect(Collectors.toList());


拼接字符串 .collect(Collectors.joining(", "))
// 将字符串换成大写并用逗号链接起来
List<String> G7 = Arrays.asList("USA", "Japan", "France", "Germany", "Italy", "U.K.","Canada");
String G7Countries = G7.stream().map(x -> x.toUpperCase()).collect(Collectors.joining(", "));
System.out.println(G7Countries);




(8)summaryStatistics()的用法
IntStream、LongStream 和 DoubleStream 等流的类中,有个非常有用的方法叫做 summaryStatistics() 。可以返回 IntSummaryStatistics、LongSummaryStatistics 或者 DoubleSummaryStatistics,
描述流中元素的各种摘要数据。在本例中,我们用这个方法来计算列表的最大值和最小值。它也有 getSum() 和 getAverage() 方法来获得列表的所有元素的总和及平均值。


示例:
//获取数字的个数、最小值、最大值、总和以及平均值
List<Integer> primes = Arrays.asList(2, 3, 5, 7, 11, 13, 17, 19, 23, 29);
IntSummaryStatistics stats = primes.stream().mapToInt((x) -> x).summaryStatistics();
System.out.println("Highest prime number in List : " + stats.getMax());
System.out.println("Lowest prime number in List : " + stats.getMin());
System.out.println("Sum of all prime numbers : " + stats.getSum());
System.out.println("Average of all prime numbers : " + stats.getAverage());




(9)peek: 生成一个包含原Stream的所有元素的新Stream,同时会提供一个消费函数(Consumer实例),新Stream每个元素被消费的时候都会执行给定的消费函数;


(10)limit: 对一个Stream进行截断操作,获取其前N个元素,如果原Stream中包含的元素个数小于N,那就获取其所有的元素;


(11)skip: 返回一个丢弃原Stream的前N个元素后剩下元素组成的新Stream,如果原Stream中包含的元素个数小于N,那么返回空Stream;


(五)Lambda表达式 vs 匿名类


既然lambda表达式即将正式取代Java代码中的匿名内部类,那么有必要对二者做一个比较分析。
一个关键的不同点就是关键字 this。匿名类的 this 关键字指向匿名类,而lambda表达式的 this 关键字指向包围lambda表达式的类。
另一个不同点是二者的编译方式。Java编译器将lambda表达式编译成类的私有方法。
使用了Java 7的 invokedynamic 字节码指令来动态绑定这个方法。


在lambda中,this不是指向lambda表达式产生的那个SAM对象,而是声明它的外部对象。
例如:
public class WhatThis {


     public void whatThis(){
           //转全小写
           List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
           List<String> execStrs = proStrs.stream().map(str -> {
                 System.out.println(this.getClass().getName());
                 return str.toLowerCase();
           }).collect(Collectors.toList());
           execStrs.forEach(System.out::println);
     }


     public static void main(String[] args) {
           WhatThis wt = new WhatThis();
           wt.whatThis();
     }
}


输出:
com.wzg.test.WhatThis
com.wzg.test.WhatThis
com.wzg.test.WhatThis
ni
hao
lambda


(6)Java 8 Lambda表达式要点


10个Java lambda表达式、流API示例
到目前为止我们看到了Java 8的10个lambda表达式,这对于新手来说是个合适的任务量,你可能需要亲自运行示例程序以便掌握。试着修改要求创建自己的例子,达到快速学习的目的。我还想建议大家使用Netbeans IDE来练习lambda表达式,它对Java 8支持良好。当把代码转换成函数式的时候,Netbeans会及时给你提示。只需跟着Netbeans的提示,就能很容易地把匿名类转换成lambda表达式。此外,如果你喜欢阅读,那么记得看一下Java 8的lambdas,实用函数式编程这本书(Java 8 Lambdas, pragmatic functional programming),作者是Richard Warburton,或者也可以看看Manning的Java 8实战(Java 8 in Action),这本书虽然还没出版,但我猜线上有第一章的免费pdf。不过,在你开始忙其它事情之前,先回顾一下Java 8的lambda表达式、默认方法和函数式接口的重点知识。


1)lambda表达式仅能放入如下代码:预定义使用了 @Functional 注释的函数式接口,自带一个抽象函数的方法,或者SAM(Single Abstract Method 单个抽象方法)类型。这些称为lambda表达式的目标类型,可以用作返回类型,或lambda目标代码的参数。例如,若一个方法接收Runnable、Comparable或者 Callable 接口,都有单个抽象方法,可以传入lambda表达式。类似的,如果一个方法接受声明于 java.util.function 包内的接口,例如 Predicate、Function、Consumer 或 Supplier,那么可以向其传lambda表达式。


2)lambda表达式内可以使用方法引用,仅当该方法不修改lambda表达式提供的参数。本例中的lambda表达式可以换为方法引用,因为这仅是一个参数相同的简单方法调用。


list.forEach(n -> System.out.println(n)); 
list.forEach(System.out::println);  // 使用方法引用
然而,若对参数有任何修改,则不能使用方法引用,而需键入完整地lambda表达式,如下所示:


list.forEach((String s) -> System.out.println("*" + s + "*"));
事实上,可以省略这里的lambda参数的类型声明,编译器可以从列表的类属性推测出来。


3)lambda内部可以使用静态、非静态和局部变量,这称为lambda内的变量捕获。


4)Lambda表达式在Java中又称为闭包或匿名函数,所以如果有同事把它叫闭包的时候,不用惊讶。


5)Lambda方法在编译器内部被翻译成私有方法,并派发 invokedynamic 字节码指令来进行调用。可以使用JDK中的 javap 工具来反编译class文件。使用 javap -p 或 javap -c -v 命令来看一看lambda表达式生成的字节码。大致应该长这样:


private static java.lang.Object lambda$0(java.lang.String);
6)lambda表达式有个限制,那就是只能引用 final 或 final 局部变量,这就是说不能在lambda内部修改定义在域外的变量。


先举例:


//将为列表中的字符串添加前缀字符串
String waibu = "lambda :";
List<String> proStrs = Arrays.asList(new String[]{"Ni","Hao","Lambda"});
List<String>execStrs = proStrs.stream().map(chuandi -> {
Long zidingyi = System.currentTimeMillis();
return waibu + chuandi + " -----:" + zidingyi;
}).collect(Collectors.toList());
execStrs.forEach(System.out::println);


输出:


lambda :Ni -----:1474622341604
lambda :Hao -----:1474622341604
lambda :Lambda -----:1474622341604




变量waibu :外部变量


变量chuandi :传递变量


变量zidingyi :内部自定义变量




lambda表达式可以访问给它传递的变量,访问自己内部定义的变量,同时也能访问它外部的变量。


不过lambda表达式访问外部变量有一个非常重要的限制:变量不可变(只是引用不可变,而不是真正的不可变)。


当在表达式内部修改waibu = waibu + " ";时,IDE就会提示你:


Local variable waibu defined in an enclosing scope must be final or effectively final


编译时会报错。因为变量waibu被lambda表达式引用,所以编译器会隐式的把其当成final来处理。


以前Java的匿名内部类在访问外部变量的时候,外部变量必须用final修饰。现在java8对这个限制做了优化,可以不用显示使用final修饰,但是编译器隐式当成final来处理。




示例2:
List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { factor++; });


Compile time error : "local variables referenced from a lambda expression must be final or effectively final"
另外,只是访问它而不作修改是可以的,如下所示:


List<Integer> primes = Arrays.asList(new Integer[]{2, 3,5,7});
int factor = 2;
primes.forEach(element -> { System.out.println(factor*element); });
输出:


4
6
10
14
因此,它看起来更像不可变闭包,类似于Python。


以上就是Java 8的lambda表达式的全部10个例子。此次修改将成为Java史上最大的一次,将深远影响未来Java开发者使用集合框架的方式。我想规模最相似的一次修改就是Java 5的发布了,它带来了很多优点,提升了代码质量,例如:泛型、枚举、自动装箱(Autoboxing)、静态导入、并发API和变量参数。上述特性使得Java代码更加清晰,我想lambda表达式也将进一步改进它。我在期待着开发并行第三方库,这可以使高性能应用变得更容易写。





  

相关文章