【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)
this指针的指向不同:匿名类的this指针指向匿名类,而Lambda表达式的this指针指向的是包围Lambda表达式的类
编译方式不同:Lambda在编译器内部被翻译为私有方法,并使用了Java7的invokedynamic字节码指令来动态绑定这个方法,而匿名内部类经过编译后会生成对应的class文件
实现的接口限制有区别:匿名类可以为任意接口创建实例,只要实现接口所有的抽象方法即可,而Lambda表达式只能实现函数式接口(只有一个抽象方法的接口)
接口默认方法的调用权限不同:匿名类实现的抽象方法允许调用接口中的默认方法,而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表达式的好处
延迟执行:有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费
减少匿名类的创建,节省资源
依靠类型推断和适当的参数,编写简明、更富于表达且更少杂志的代码
关注我更好的学习技术