jdk8新特性-亮瞎眼的lambda表达式

时间:2021-08-12 00:36:34

jdk8之前,尤其是在写GUI程序的事件监听的时候,各种的匿名内部类,大把大把拖沓的代码,程序毫无美感可言!既然Java中一切皆为对象,那么,就类似于某些动态语言一样,函数也可以当成是对象啊!代码块也可以当成是对象啊!随着函数式编程的概念越来越深入人心,java中CODE=OBJECT的这一天终于到来了!如果你认为lambda表达式仅仅是为了从语法上简化匿名内部类,那就太小看jdk8的lambda了!

下面我们就来看下lambda表达式是如何亮瞎你的眼的!

lambda的定义
Funda-men-tally, a lambda expression is just a shorter way of writing an implementation of a method for later execution. 
(1)lambda是方法的实现
(2)lambda是延迟执行的

首先看一个用匿名内部类的例子:

  1. public class Test1{
  2. public static void main(String args[]){
  3. Runnable r = new Runnable(){
  4. public void run(){
  5. System.out.println("hello,lambda!");
  6. }
  7. };
  8. r.run();
  9. }
  10. }

要换成lambda是什么样的呢?

  1. public class Test2{
  2. public static void main(String args[]){
  3. Runnable r = ()->System.out.println("hello,lambda");
  4. r.run();
  5. }
  6. }

原先要5行代码,现在换成了仅仅1行!
这他妈的得省多少代码啊!
有木有很兴奋啊!
下面还有更刺激的!

lambda是如何做到的呢?看一下反编译之后的字节码:

  1. public static void main(java.lang.String[]);
  2. descriptor: ([Ljava/lang/String;)V
  3. flags: ACC_PUBLIC, ACC_STATIC
  4. Code:
  5. stack=1, locals=2, args_size=1
  6. 0: invokedynamic #2,  0              // InvokeDynamic #0:run:()Ljava/lang/Runnable;
  7. 5: astore_1
  8. 6: aload_1
  9. 7: invokeinterface #3,  1            // InterfaceMethod java/lang/Runnable.run:()V
  10. 12: return
  11. LineNumberTable:
  12. line 3: 0
  13. line 4: 6
  14. line 5: 12
  15. }

注意:上面有一个叫做invokedynamic的指令。invokedynamic是从jdk7开始引入的,jdk8开始落地。
可以看出来lambda并不是语法糖,它不是像匿名内部类那样生成那种带有$的匿名类。
简单的说,这里只是定义了一个方法调用点,具体调用那个方法要到运行时才能决定,这就是前面所说的:延迟执行。
具体的细节请google:invokedynamic。

为了配合lambda,jdk8引入了一个新的定义叫做:函数式接口(Functional interfaces)
函数式接口:
it is an interface that requires exactly one method to be implemented in order to satisfy the requirements of the interface. 
(1)是一个接口
(2)只有一个待实现的方法
因为jdk8开始,接口可以有default方法,所以,函数式接口也是可以有default方法的,但是,只能有一个未实现的方法。

与此对应,新引入了一个注解: @FunctionalInterface
这个注解只是起文档的作用,说明这个接口是函数式接口,编译器并不会使用这个注解来决定一个接口是不是函数式接口。
不管加不加@FunctionalInterface这个注解,下面的接口都是函数式接口:
interface Something {
  public String doit(Integer i);
}

lambda的语法
A lambda in Java essentially consists of three parts: a parenthesized set of parameters, an arrow, and then a body, 
which can either be a single expression or a block of Java code.
lambda包含3个部分:
(1)括弧包起来的参数
(2)一个箭头
(3)方法体,可以是单个语句,也可以是语句块
参数可以写类型,也可以不写,jvm很智能的,它能自己推算出来

  1. public class Test3{
  2. public static void main(String... args) {
  3. Comparator<String> c = (String lhs, String rhs) -> lhs.compareTo(rhs);
  4. int result = c.compare("Hello", "World");
  5. System.out.println(result);
  6. }
  7. }

方法可以有返回,也可以无返回,如果有多个语句,还要返回值,需要加上return

  1. public class Test4{
  2. public static void main(String... args) {
  3. Comparator<String> c =(lhs, rhs) ->{
  4. System.out.println("I am comparing " +lhs + " to " + rhs);
  5. return lhs.compareTo(rhs);
  6. };
  7. int result = c.compare("Hello", "World");
  8. System.out.println(result);
  9. }
  10. }

一个很有意思的事情:
之前我们说Object是一切类的父类,然而在加入了lambda以后,这种大一统的局面将不复存在:

  1. public class Test5{
  2. public static void main(String args[]){
  3. Object r = ()->System.out.println("hello,lambda");
  4. }
  5. }

编译报错:
Test5.java:3: error: incompatible types: Object is not a functional interface
                Object r = ()->System.out.println("hello,lambda");
                           ^
1 error

很显然,编译器会检查变量的引用类型里面是否真的是一个函数式接口。那么如何让这段代码通过编译呢?
只需要加一个强制类型转换就可以了:

  1. public class Test6{
  2. public static void main(String args[]){
  3. Object r = (Runnable)()->System.out.println("hello,lambda");
  4. }
  5. }

lambda的词法作用域

我们知道,在匿名内部类中:

  1. class Hello {
  2. public Runnable r = new Runnable() {
  3. public void run() {
  4. System.out.println(this);
  5. System.out.println(toString());
  6. }
  7. };
  8. public String toString() {
  9. return "Hello's custom toString()";
  10. }
  11. }
  12. public class InnerClassExamples {
  13. public static void main(String... args) {
  14. Hello h = new Hello();
  15. h.r.run();
  16. }
  17. }

System.out.println(this);这里的this指的是匿名类,而非Hello类。
想要引用Hello类需要Hello.this这样:

  1. class Hello {
  2. public Runnable r = new Runnable() {
  3. public void run() {
  4. System.out.println(Hello.this);
  5. System.out.println(Hello.this.toString());
  6. }
  7. };
  8. }

这种做法非常的反人类反直觉!看上去很恶心!
下面我们就来看一下伟大的lambda是什么样子的:

  1. class Hello{
  2. public Runnable r = () -> {
  3. System.out.println(this);
  4. System.out.println(toString());
  5. };
  6. public String toString() {
  7. return "Hello's custom toString()";
  8. }
  9. }
  10. public class Test7{
  11. public static void main(String args[]){
  12. Hello h = new Hello();
  13. h.r.run();
  14. }
  15. }

输出:
Hello's custom toString()
Hello's custom toString()
System.out.println(this);这里的this指的是Hello,而非lambda表达式!
但是,如果我们想在lambda表达式中返回lambda本身该怎么做呢?

变量捕获
匿名内部类只能引用作用域外面的final的变量,在lambda中对这个限制做了削弱,只需要是“等价final”就可以,没必要用final关键字来标识。

  1. public class Test8{
  2. public static void main(String args[]){
  3. String message = "Howdy, world!";//不需要是final的
  4. Runnable r = () -> System.out.println(message);//这里也能访问
  5. r.run();
  6. }
  7. }

“等效final”的意思是:事实上的final,所以,一旦赋值也是不可以改变的!比如:

  1. public class Test9{
  2. public static void main(String args[]){
  3. String message = "Howdy, world!";
  4. Runnable r = () -> System.out.println(message);
  5. r.run();
  6. message = "change it";
  7. }
  8. }

Test9.java:4: error: local variables referenced from a lambda expression must be final or effectively final
                Runnable r = () -> System.out.println(message);
                                                      ^
1 error

如果上面的内容看上去平淡无奇的话,真正的大杀器出现了:方法引用
我们有一个这样的类:

  1. class Person {
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. }

现在我们要把多个Person对象进行排序,有时候是按照firstName来排,有时候是按照lastName或者是age来排,使用lambda可以这样来做:

  1. class Person{
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Person(String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public String toString(){
  11. return firstName+","+lastName+","+age;
  12. }
  13. }
  14. public class Test10{
  15. public static void main(String args[]){
  16. Person people[] = new Person[]{
  17. new Person("Ted", "Neward", 41),
  18. new Person("Charlotte", "Neward", 41),
  19. new Person("Michael", "Neward", 19),
  20. new Person("Matthew", "Neward", 13)
  21. };
  22. //sort by firstName
  23. Arrays.sort(people, (lhs,rhs)->lhs.firstName.compareTo(rhs.firstName));
  24. for(Person p : people){
  25. System.out.println(p);
  26. }
  27. }
  28. }

我们可以把Comparator抽取出来,变成是Person对象的成员变量:

  1. class Person{
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Person(String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public String toString(){
  11. return firstName+","+lastName+","+age;
  12. }
  13. public final static Comparator<Person> compareFirstName =
  14. (lhs, rhs) -> lhs.firstName.compareTo(rhs.firstName);
  15. public final static Comparator<Person> compareLastName =
  16. (lhs, rhs) -> lhs.lastName.compareTo(rhs.lastName);
  17. public final static Comparator<Person> compareAge =
  18. (lhs, rhs) -> lhs.age - rhs.age;
  19. }
  20. public class Test11{
  21. public static void main(String args[]){
  22. Person people[] = new Person[]{
  23. new Person("Ted", "Neward", 41),
  24. new Person("Charlotte", "Neward", 41),
  25. new Person("Michael", "Neward", 19),
  26. new Person("Matthew", "Neward", 13)
  27. };
  28. Arrays.sort(people, Person.compareFirstName);//这里直接引用lambda
  29. for(Person p : people){
  30. System.out.println(p);
  31. }
  32. }
  33. }

能起到同样的作用,但是语法看上去很奇怪,因为之前我们都是创建一个满足Comparator签名的方法,然后直接调用,而非定义一个变量,
然后引用这个变量!所以,还有这么一种调用方法:

  1. class Person{
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Person(String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public String toString(){
  11. return firstName+","+lastName+","+age;
  12. }
  13. public static int compareFirstName(Person lhs, Person rhs){
  14. return lhs.firstName.compareTo(rhs.firstName);
  15. }
  16. public static int compareLastName(Person lhs, Person rhs){
  17. return lhs.lastName.compareTo(rhs.lastName);
  18. }
  19. }
  20. public class Test12{
  21. public static void main(String args[]){
  22. Person people[] = new Person[]{
  23. new Person("Ted", "Neward", 41),
  24. new Person("Charlotte", "Neward", 41),
  25. new Person("Michael", "Neward", 19),
  26. new Person("Matthew", "Neward", 13)
  27. };
  28. Arrays.sort(people, Person::compareFirstName);
  29. for(Person p : people){
  30. System.out.println(p);
  31. }
  32. }
  33. }

看Person::compareFirstName这种调用方式,
如果是static方法使用:类名::方法名
如果是instance方法:instance::方法名

但是,上面的代码还是不是很美观,因为Person只是一个数据对象,它不应该的对外提供compareFirstName或者是compareLastName这样的方法,
我们需要的仅仅是根据某个字段排序而已!很幸运的是jdk的api帮我们做了这件事:

  1. import java.util.*;
  2. class Person{
  3. public String firstName;
  4. public String lastName;
  5. public int age;
  6. public Person(String firstName, String lastName, int age){
  7. this.firstName = firstName;
  8. this.lastName = lastName;
  9. this.age = age;
  10. }
  11. public String getFirstName(){
  12. return this.firstName;
  13. }
  14. public String getLastName(){
  15. return this.lastName;
  16. }
  17. public String toString(){
  18. return firstName+","+lastName+","+age;
  19. }
  20. }
  21. public class Test13{
  22. public static void main(String args[]){
  23. Person people[] = new Person[]{
  24. new Person("Ted", "Neward", 41),
  25. new Person("Charlotte", "Neward", 41),
  26. new Person("Michael", "Neward", 19),
  27. new Person("Matthew", "Neward", 13)
  28. };
  29. Arrays.sort(people, Comparator.comparing(Person::getFirstName));
  30. for(Person p : people){
  31. System.out.println(p);
  32. }
  33. }
  34. }

Arrays.sort(people, Comparator.comparing(Person::getFirstName));这里调用了Comparator.comparing方法,
但是注意这里的Person::getFirstName,很显然getFirstName()并不是static的,这是jdk做了封装的缘故!
这样做就非常完美了!

假如我们的排序算法改为:先按照lastName,然后按照age排序呢?

  1. public class Test15{
  2. public static void main(String args[]){
  3. Person people[] = new Person[]{
  4. new Person("Ted", "Neward", 10),
  5. new Person("Charlotte", "Neward", 41),
  6. new Person("Michael", "Naward", 19),
  7. new Person("Matthew", "Nmward", 13)
  8. };
  9. Collections.sort(Arrays.asList(people), (lhs, rhs)->{
  10. if(lhs.getLastName().equals(rhs.getLastName())){
  11. return lhs.getAge()-rhs.getAge();
  12. }else{
  13. return lhs.getLastName().compareTo(rhs.getLastName());
  14. }
  15. });
  16. for(Person p : people){
  17. System.out.println(p);
  18. }
  19. }
  20. }

很显然,应该还有更好的实现方式:

  1. public class Test16{
  2. public static void main(String args[]){
  3. Person people[] = new Person[]{
  4. new Person("Ted", "Neward", 10),
  5. new Person("Charlotte", "Neward", 41),
  6. new Person("Michael", "Naward", 19),
  7. new Person("Matthew", "Nmward", 13)
  8. };
  9. Collections.sort(Arrays.asList(people),Comparator.comparing(Person::getLastName).thenComparing(Person::getAge));
  10. for(Person p : people){
  11. System.out.println(p);
  12. }
  13. }
  14. }

Comparator.comparing(Person::getLastName).thenComparing(Person::getAge):简直帅呆了!
还有更多的诸如:andThen()这样的方法,可以查api。

虚方法扩展
因为接口可以有default方法,所以很多类库都重写了,加入了一些default的方法,比如:
interface Iterator<T> {
  boolean hasNext();
  T next();
  void remove();
  void skip(int i) default {
    for (; i > 0 && hasNext(); i--) next();
  }

skip(i)就是一个default方法,这样所有的Iterator的子类都具有了一个叫skip的方法!
但是,大家对default方法的争议还是比较大的,比如:

  1. interface I1 {
  2. public default void print(){
  3. System.out.println("I1");
  4. }
  5. public void hello();
  6. }
  7. interface I2{
  8. public default void print(){
  9. System.out.println("I2");
  10. }
  11. public void world();
  12. }
  13. class Impl implements I1,I2{
  14. public void hello(){
  15. }
  16. public void world(){
  17. }
  18. }

如果在Impl上调用print会怎样呢?这不就是传说中的多继承么?想知道结果的话,自己试一下就可以了,哈哈

Stream:
之前的文章已经有介绍,下面只据一些使用的例子:
过滤age>12的元素:

  1. people
  2. .stream()
  3. .filter(it -> it.getAge() >= 21) ;

过滤age>12的元素,并输出:

  1. people.stream()
  2. .filter((it) -> it.getAge() >= 21)
  3. .forEach((it) ->
  4. System.out.println("Have a beer, " + it.getFirstName()));

jdk预定义的Predicate:

  1. Predicate<Person> drinkingAge = (it) -> it.getAge() >= 21;
  2. Predicate<Person> brown = (it) -> it.getLastName().equals("Brown");
  3. people.stream()
  4. .filter(drinkingAge.and(brown))
  5. .forEach((it) ->System.out.println("Have a beer, " + it.getFirstName()));

map:

  1. IntStream ages =
  2. people.stream()
  3. .mapToInt((it) -> it.getAge());
  4. //sum:
  5. int sum = people.stream()
  6. .mapToInt(Person::getAge)
  7. .sum();

重点说下reduce:

  1. public class Test17{
  2. public static void main(String args[]){
  3. List<Integer> values = Arrays.asList(1,2,3,4,5);
  4. int sum = values.stream().reduce(0, (l,r)->l+r);
  5. System.out.println(sum);
  6. }
  7. }

reduce(0, (l,r)->l+r)的工作原理是:第一个参数0作为后面lambda表达式的左操作数,然后从stream中取出一个元素作为右操作数,
二者运算的结果作为下一次运算的左操作数,依次循环。
最后看一个好玩的例子:

  1. class Person{
  2. public String firstName;
  3. public String lastName;
  4. public int age;
  5. public Person(String firstName, String lastName, int age){
  6. this.firstName = firstName;
  7. this.lastName = lastName;
  8. this.age = age;
  9. }
  10. public String getFirstName(){
  11. return this.firstName;
  12. }
  13. public String getLastName(){
  14. return this.lastName;
  15. }
  16. public int getAge(){
  17. return this.age;
  18. }
  19. public String toString(){
  20. return firstName+","+lastName+","+age;
  21. }
  22. public String toJson(){
  23. return "{"+
  24. "firstName:\""+firstName+"\","+
  25. "lastName:\""+lastName+"\","+
  26. "age:"+age +
  27. "}";
  28. }
  29. }
  30. public class Test18{
  31. public static void main(String args[]){
  32. Person people[] = new Person[]{
  33. new Person("Ted", "Neward", 10),
  34. new Person("Charlotte", "Neward", 41),
  35. new Person("Michael", "Naward", 19),
  36. new Person("Matthew", "Nmward", 13)
  37. };
  38. String json = Arrays.asList(people).stream().map(Person::toJson).reduce("[",(l,r)->l + (l.equals("[")?"":",") + r)+"]";
  39. System.out.println(json);
  40. }
  41. }

输出结果:
[{firstName:"Ted",lastName:"Neward",age:10},{firstName:"Charlotte",lastName:"Neward",age:41},{firstName:"Michael",lastName:"Naward",age:19},{firstName:"Matthew",lastName:"Nmward",age:13}]
还可以这样:

  1. public class Test19{
  2. public static void main(String args[]){
  3. Person people[] = new Person[]{
  4. new Person("Ted", "Neward", 10),
  5. new Person("Charlotte", "Neward", 41),
  6. new Person("Michael", "Naward", 19),
  7. new Person("Matthew", "Nmward", 13)
  8. };
  9. String joined = Arrays.asList(people).stream().map(Person::toJson).collect(Collectors.joining(", "));
  10. System.out.println("[" + joined + "]");
  11. }
  12. }
    1. 更进一步:
    2. public class Test20{
    3. public static void main(String args[]){
    4. Person people[] = new Person[]{
    5. new Person("Ted", "Neward", 10),
    6. new Person("Charlotte", "Neward", 41),
    7. new Person("Michael", "Naward", 19),
    8. new Person("Matthew", "Nmward", 13)
    9. };
    10. String json = Arrays.asList(people).stream().map(Person::toJson).collect(Collectors.joining(", ", "[", "]"));
    11. System.out.println(json);
    12. }
    13. }
    14. 如果只能用一个字来形容,那就是perfect!

      参考:

    15. http://www.oracle.com/technetwork/articles/java/architect-lambdas-part1-2080972.html
    16. http://www.oracle.com/technetwork/articles/java/architect-lambdas-part2-2081439.html

jdk8新特性-亮瞎眼的lambda表达式的更多相关文章

  1. Java1&period;8新特性——接口改动和Lambda表达式

    Java1.8新特性——接口改动和Lambda表达式 摘要:本文主要学习了Java1.8的新特性中有关接口和Lambda表达式的部分. 部分内容来自以下博客: https://www.cnblogs. ...

  2. Java8新特性(一)——Lambda表达式与函数式接口

    一.Java8新特性概述 1.Lambda 表达式 2. 函数式接口 3. 方法引用与构造器引用 4. Stream API 5. 接口中的默认方法与静态方法 6. 新时间日期 API 7. 其他新特 ...

  3. Java8新特性第1章&lpar;Lambda表达式&rpar;

    在介绍Lambda表达式之前,我们先来看只有单个方法的Interface(通常我们称之为回调接口): public interface OnClickListener { void onClick(V ...

  4. Java8新特性学习笔记&lpar;一&rpar; Lambda表达式

    没有用Lambda表达式的写法: Comparator<Transaction> byYear = new Comparator<Transaction>() { @Overr ...

  5. Java8新特性 利用流和Lambda表达式对List集合进行处理

    Lambda表达式处理List 最近在做项目的过程中经常会接触到 lambda 表达式,随后发现它基本上可以替代所有 for 循环,包括增强for循环.也就是我认为,绝大部分的for循环都可以用 la ...

  6. JDK8 新特性

    JDK8 新特性目录导航: Lambda 表达式 函数式接口 方法引用.构造器引用和数组引用 接口支持默认方法和静态方法 Stream API 增强类型推断 新的日期时间 API Optional 类 ...

  7. JDK8新特性之一Lambda

    JDK8的新特性之一Lambda能将函数作为方法里面的参数使用. /** * JDK8新特性Lambda */ public class Test { public static void main( ...

  8. JDK8新特性一览

    转载自:http://blog.csdn.net/qiubabin/article/details/70256683 官方新特性说明地址 Jdk8新特性.png 下面对几个常用的特性做下重点说明. 一 ...

  9. 一次电话Java面试的问题总结(JDK8新特性、哈希冲突、HashMap原理、线程安全、Linux查询命令、Hadoop节点)

    面试涉及问题含有: Java JDK8新特性 集合(哈希冲突.HashMap的原理.自动排序的集合TreeSet) 多线程安全问题 String和StringBuffer JVM 原理.运行流程.内部 ...

随机推荐

  1. 时间服务器:NTP 服务器

    15.1 关于时区与网络校时的通讯协议   使得每一部主机的时间同步化.   DHCP 客户端/服务器端所需要的租约时间限制. 网络侦测时所需要注意的时间点.刚刚谈到的登录文件分析功能.具有相关性的主 ...

  2. C&num;的System&period;ICloneable接口说明

    System.ICloneable接口支持克隆,即用与现有实例相同的值创建类的新实例.msdn上的解释很简单,主要就是clone方法的实行,介绍深拷贝和浅拷贝,搞的很糊涂,那么到底是什么意思呢?看看下 ...

  3. OpenCV码源笔记——Decision Tree决策树

    来自OpenCV2.3.1 sample/c/mushroom.cpp 1.首先读入agaricus-lepiota.data的训练样本. 样本中第一项是e或p代表有毒或无毒的标志位:其他是特征,可以 ...

  4. ASP&period;NET - 使用 Eval&lpar;&rpar; 绑定数据时使用 三元运算符

    ASP.NET邦定数据“<%#Eval("Sex")%>”运用三元运算符: <%#(Eval("Sex", "{0}") ...

  5. twrp 2&period;7&period;0 ui&period;xml简单分析,布局讲解,第一章

    twrp 的ui.xml文件在bootable/recovery/gui/devices/$(DEVICE_RESOLUTION)/res目录里面 下面我主要分析的是720x1280分辨率的界面布局及 ...

  6. 记AOP概念理解

    OOD/OOP面向名词领域,AOP面向动词领域. 应用举例 假设有在一个应用系统中,有一个共享的数据必须被并发同时访问,首先,将这个数据封装在数据对象中,称为Data Class,同时,将有多个访问类 ...

  7. Pycharm&plus;Python3&plus;python工程打包成exe&plus;在windows下自动定时运行

    python3打包成exe---pyinstaller方法:https://www.cnblogs.com/mufenglin/p/7479281.html 按照如上方式打包后,执行dist文件夹(新 ...

  8. 【30集iCore3&lowbar;ADP出厂源代码&lpar;ARM部分&rpar;讲解视频】30-13 emWin底层驱动接口介绍

    视频简介:该视频介绍emWin底层驱动接口. 源视频包下载地址:链接:http://pan.baidu.com/s/1nvPpC2d 密码:cbb7 银杏科技优酷视频发布区:http://i.youk ...

  9. GIS矢量数据化简:一种改进的道格拉斯-普克算法以及C&plus;&plus;实现

    GIS领域的同志都知道,传统的道格拉斯-普克算法都是递归实现.然而有时候递归的层次太深的话会出现栈溢出的情况.在此,介绍一种非递归的算法. 要将递归算法改为非递归算法,一般情况下分为两种场景.第一种是 ...

  10. free 释放内存

    http://www.cplusplus.com/reference/cstdlib/free/ free void free (void* ptr); Deallocate memory block ...