vlambda博客
学习文章列表

【01期】Lambda表达式及函数式接口原理


Lambda


可以把Lambda表达式理解为简洁的表示可传递的匿名函数的一种方式,它没有名称,但它有参数列表,函数主体,返回类型,可能还有一个可以抛出的异常列表

语法

  (参数列表) -> {方法体;}

  • 可选的声明: 无需声明参数类型,编译器即可自动识别

  • 可选的参数括号 : 仅有一个参数时小括号可以省略

  • 可选的大括号 : 方法体只包含一条语句时可以省略大括

  • 可选的return关键字 : 方法体只包含一个表达式返回值语句时,可以省略return 和 大括号,有大括号时需要手动返回示例

// 无参数,返回值1() -> 1 // 无参数,无返回值() -> System.out.print("Java8 lambda.");// 1个参数,参数类型为数字,返回值为其值的5倍x -> 5 * x // 2个参数,参数类型均为数字,返回值为其差值(x, y) -> x - y// 2个参数,指定参数类型均为int型,返回值为其差值 (int x, int y) -> x - y // 1个参数,指定参数类型为String ,无返回值(String str) -> System.out.print(str)


Lambda表达式与匿名内部类的区别



语法上的区别:创建线程

匿名内部类

new Thread(new Runnable() { @Override public void run() { System.out.println("匿名内部类形式"); }}).start();

Lambda

new Thread(() -> System.out.println("Lambda")).start();

注 : Lambda表达式代替匿名内部类的关键在于实现的接口为函数式接口(@FunctionalInterface)


  1. this指针的指向不同:匿名类的this指针指向匿名类,而Lambda表达式的this指针指向的是包围Lambda表达式的类

  2. 编译方式不同:Lambda在编译器内部被翻译为私有方法,并使用了Java7的invokedynamic字节码指令来动态绑定这个方法,而匿名内部类经过编译后会生成对应的class文件

  3. 实现的接口限制有区别:匿名类可以为任意接口创建实例,只要实现接口所有的抽象方法即可,而Lambda表达式只能实现函数式接口(只有一个抽象方法的接口)

  4. 接口默认方法的调用权限不同:匿名类实现的抽象方法允许调用接口中的默认方法,而Lambda表达式不能调用接口中的默认方法


函数式接口



函数式接口指一个接口中只有一个抽象方法(可以有其他默认方法和静态方法),只有确保接口中有且仅有一个抽象方法,Lambda才能顺利地进行推导


@FunctionalInterface

    可以使用注解标注该接口为函数式接口,一旦使用,编译器会强制检查该接口是否仅有一个抽象方法,如果存在多个编译器会报错提醒


注 : 即使不使用该注解,只要满足函数式接口的定义,仍然是一个函数式接口,使用起来都一样(该接口只是一个标记接口)


类型推断



类型推断对性能是否有影响

  我们可以把Java分成编译和运行两部分。类型推断是在编译期间做的事情,可能使用类型推断会延长编译的时间,但是对运行时的效率是没有影响的


  一般来说,我们关注程序的性能问题是在运行时而不是编译时,所以类型推断对性能没有影响的类型推断的限制

例1:

Java虽然有类型推断,但是这推断是有一定的限制的,但是也已经足够智能了。

public static Comparator<User> createComparator1() { return (User user1, User user2) -> user1.getAge() - user2.getAge();}

上面例子如果不指定CustUser类型可以吗?

public static Comparator<User> createComparator2() {return (user1, user2) -> user1.getAge() - user2.getAge();}

是可以的。这个例子中我们并没有指定类型Java是怎么找到user1和user2的类型呢?

注意: 上面例子中,我们定义了返回类型是User的,Java通过这个返回类型来推断出传入的实际类型就是User。


如果将上面案例的return语句拆成两条语句执行会怎么样

Comparator comparator=(user1, user2) -> user1.getAge() - user2.getAge();

这时候编译就会报错,说找不到getAge方法。这是因为我们返回的Comparator并没有指明类型,所以默认情况下是Object类型,Object类并没有getAge方法。

可以改成下面的形式

Comparator<CustUser> comparator=(user1, user2) -> user1.getAge() - user2.getAge();

例2:

public static Comparator<User> createComparator3() { return Comparator.comparing(user -> user.getRegistration()).reversed();}

我们将传递comparable的结果作为调用reversed()的目标。comparable返回Comparable<T>,reversed()没有展示任何有关T的可能含义的额外信息。根据此信息,编译器推断T的类型肯定是Object。遗憾的是,此信息对于该代码而言并不足够,因为Object缺少我们在lambda表达式中调用的getRegistration()方法。


类型推断在这一刻失败了。在这种情况下,编译器实际上需要一些信息。类型推断会分析参数、返回元素或赋值元素来确定类型,但在上下文提供的细节不足时,编译器就会达到其能力极限


  尝试方法引用

public static Comparator<User> createComparator3() { return Comparator.comparing(User::getRegistration).reversed();}

    由于直接说明了User类型,编译通过

    注:下一节我们将详细讲解方法引用

使用Lambda表达式的好处



  1. 延迟执行:有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费

  2. 减少匿名类的创建,节省资源

  3. 依靠类型推断和适当的参数,编写简明、更富于表达且更少杂志的代码


关注我更好的学习技术