Lambda表达式的优雅
1.函数式编程思想
面向对象的思想:必须通过对象的形式来做事情,过分重视过程。
函数式编程思想:强调做什么,而不重视以什么方式去做。
2.多线程的传统写法
定义Runnable
接口,并使用Thread
类来启动线程。
// 匿名内部类
Runnable task = new Runnable() {
@Override
public void run() { // 覆盖重写抽象方法
System.out.println("多线程任务执行!");
}
};
new Thread(task).start(); // 启动线程
缺点:
Thread
类过度依赖Runnable
接口作为参数- 必须覆盖
run
方法
而事实上,只有run
方法才是关键所在。
3.转换编程思维
如果我们将关注点从“怎么做”转移到”做什么“的本质上,就会发现只要达到自己的目的,过程与形式并不重要。
比如:当我们需要从北京到上海时,可以选择高铁、汽车、骑行或是徒步。我们的真正目的是到达上海,而如何才能到达上海的形式并不重要,所以我们一直在探索有没有比高铁更好的方式—搭乘飞机。
而现在这种飞机(甚至是飞船)已经诞生:2014年3月Oracle所发布的Java 8(JDK 1.8)中,加入了Lambda表达式的重量级新特性,为我们打开了新世界的大门。
4.Lambda的用法
Lambda省去面向对象的条条框框,格式由3个部分组成:
- 参数:语法与传统方法参数列表一致:无参数则留空;多个参数则用逗号分隔。
- 箭头:新引入的语法格式,代表指向动作。
- 业务代码:与传统业务代码语法一致。
Lambda表达式的标准格式为:
(参数类型 参数名) -> { 业务代码 }
借助Java 8的全新语法,上述Runnable
接口的匿名内部类写法可以通过更简单的Lambda表达式达到等效:
new Thread(() -> System.out.println("多线程任务执行!")).start(); // 启动线程
不再有“不得不创建接口对象”的束缚,不再有“抽象方法覆盖重写”的负担,就是这么简单!
Lambda无参无返回
//函数式接口
public interface Cook {
void makeFood();
}
public static void main(String[] args) {
invokeCook(() -> {
System.out.println("吃饭啦!");
});
}
//吃饭啦!
备注:小括号代表
Cook
接口makeFood
抽象方法的参数为空,大括号代表makeFood
的方法体。
Lambda有参有返回
需求:Person对象按年龄升序排序
@Data
@AllArgsConstructor
public class Person {
private String name;//姓名
private int age;//年龄
}
传统写法与Lambda写法的比较:
Person[] array = {
new Person("张三", 19),
new Person("李四", 18),
new Person("王五", 20) };
/******传统写法******/
// 匿名内部类
Comparator<Person> comp = new Comparator<Person>() {
@Override
public int compare(Person o1, Person o2) {
return o1.getAge() - o2.getAge();
}
};
// 第二个参数为排序规则,即Comparator接口实例
Arrays.sort(array, comp);
/******Lambda写法******/
Arrays.sort(array, (Person a, Person b) -> {
return a.getAge() - b.getAge();
});
5.Lambda省略格式
省略条件:
在Lambda标准格式的基础上,使用省略写法的规则为:
- 小括号内参数的类型可以省略;
- 如果小括号内有且仅有一个参数,则小括号可以省略;
- 如果大括号内有且仅有一个语句,则无论是否有返回值,都可以省略大括号、return关键字及语句分号。
仍然使用前文含有唯一makeFood
抽象方法的厨子Cook
接口,使用Lambda省略格式写法:
private static void invokeCook(Cook cook) {
cook.makeFood();
}
public static void main(String[] args) {
//省略参数类型及参数、大括号、return关键字、语句分号
invokeCook(() -> System.out.println("吃饭啦!"));
}
6.Lambda的延迟执行
性能浪费的日志案例:
private static void log(int level, String msg) {
if (level == 1) {
log.info(msg);
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
log(1, msgA + msgB );
//log(2, msgA + msgB );
}
问题:无论级别是否满足要求,作为 log
方法的第二个参数,三个字符串一定会首先被拼接并传入方法内然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
体验Lambda优雅写法
public interface MessageBuilder {
String buildMessage();
}
private static void log(int level, MessageBuilder builder) {
if (level == 1) {
log.info(builder.buildMessage());
}
}
public static void main(String[] args) {
String msgA = "Hello";
String msgB = "World";
log(2, () ‐> {
//Lambda延迟执行,如果level日志级别不满足,则Lambda不会执行
System.out.println("Lambda执行!");
return msgA + msgB;
});
}
从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。
7.方法引用
双冒号 ::
为引用运算符,而它所在的表达式被称为方法引用。如果Lambda要表达的函数方案已经存在于某个方法的实现中,那么则可以通过双冒号来引用该方法作为Lambda的替代者。
推导与省略
如果使用Lambda,那么根据“可推导就是可省略”的原则,无需指定参数类型,也无需指定的重载形式—它们都将被自动推导。而如果使用方法引用,也是同样可以根据上下文进行推导。
通过对象名引用成员方法
public class MethodRefObject {
public void printUpperCase(String str) {
System.out.println(str.toUpperCase());
}
}
public interface Printable {
void print(String str);
}
private static void printString(Printable lambda) {
lambda.print("Hello");
}
public static void main(String[] args) {
MethodRefObject obj = new MethodRefObject();
//输出Hello
printString(obj::printUpperCase);
}
通过类名引用静态方法
public interface Calculable {
int calc(int num);
}
private static void method(int num, Calcable lambda) {
System.out.println(lambda.calc(num));
}
public static void main(String[] args) {
//使用双冒号调用Math类静态方法abs()
method(‐10, Math::abs);
}
通过this、super引用子类及父类成员方法
public interface Greeted {
void greet();
}
public class Father {
public void sayHello(){
System.out.println("大家好,我是Father");
}
}
public class Son extends Father {
@Override
public void sayHello() {
System.out.println("大家好,我是Son");
}
public void method(Greeted g){
g.greet();
}
public void show(){
//调用子类sayHello()方法
method(this::sayHello);
//调用父类sayHello()方法
method(super::sayHello);
}
}
构造方法的引用
@Data
@AllArgsConstructor
public class Person {
private String name;
}
public interface PersonBuilder {
Person buildPerson(String name);
}
public static void printName(String name, PersonBuilder builder) {
System.out.println(builder.buildPerson(name));
}
public static void main(String[] args) {
//使用双冒号调用有参构造方法
printName("小明", Person::new);
}
数组构造的引用
public interface ArrayBuilder {
int[] buildArray(int length);
}
private static int[] initArray(int length, ArrayBuilder builder) {
return builder.buildArray(length);
}
public static void main(String[] args) {
//使用双冒号构造数组
int[] array = initArray(10, int[]::new);
}