在兼顾面向对象特性的基础上,Java语言通过Lambda表达式与方法引用等,为开发者打开了函数式编程的大门。下面我们做一个初探。
Lambda的延迟执行
有些场景的代码执行后,结果不一定会被使用,从而造成性能浪费。而Lambda表达式是延迟执行的,这正好可以作为解决方案,提升性能。
一种典型的场景就是对参数进行有条件使用,例如对日志消息进行拼接后,在满足条件的情况下进行打印输出:
public class Demo01Logger { private static void log(int level, String msg) { if (level == 1) { System.out.println(msg); } } public static void main(String[] args) { String msgA = "Hello"; String msgB = "World"; String msgC = "Java"; log(1, msgA + msgB + msgC); } }
这段代码存在问题:无论级别是否满足要求,作为log
方法的第二个参数,三个字符串一定会首先被拼接并传入log方法内,然后才会进行级别判断。如果级别不符合要求,那么字符串的拼接操作就白做了,存在性能浪费。
体验Lambda的更优写法
使用Lambda必然需要一个函数式接口:
@FunctionalInterface public interface MessageBuilder { String buildMessage(String... msgs);//可变参数 }
然后对log方法进行改造:
public class Demo02LoggerLambda { private static void log(int level, MessageBuilder builder) { if (level == 1) { System.out.println(builder.buildMessage()); } } public static void main(String[] args) { String msgA = "Hello"; String msgB = "World"; String msgC = "Java"; log(1, () -> msgA + msgB + msgC ); } }
说明:
由于上述使用了Lambda表达式,Lambda表达式是在执行代码:builder.buildMessage() 时才会执行msgA + msgB + msgC。
而builder.buildMessage() 能够执行的前提是level等于1.如果level不是1,那么就不会执行builder.buildMessage(),这样一来msgA + msgB + msgC也就不会执行了。
所以,只有当级别level满足要求的时候,才会进行三个字符串的拼接;否则三个字符串将不会进行拼接。这样效率就比之前要提高了。
证明Lambda的延迟
下面的代码可以通过结果进行验证:
public class Demo03LoggerDelay { private static void log(int level, MessageBuilder builder) { if (level == 1) { System.out.println(builder.buildMessage()); } } public static void main(String[] args) { String msgA = "Hello"; String msgB = "World"; String msgC = "Java"; log(2, () -> { System.out.println("Lambda执行!"); return msgA + msgB + msgC; }); } }
从结果中可以看出,在不符合级别要求的情况下,Lambda将不会执行。从而达到节省性能的效果。
扩展:实际上使用内部类也可以达到同样的效果,只是将代码操作延迟到了另外一个对象当中通过调用方法来完成。只不过使用new会在堆中开辟一个对象空间,这样也会浪费空间。
使用Lambda作为参数
如果抛开实现原理不说,Java中的Lambda表达式可以被当作是匿名内部类的语法糖。如果方法的参数是一个函数式接口类型,那么就可以使用Lambda表达式进行替代。使用Lambda表达式作为方法参数,其实就是使用函数式接口作为方法参数。
例如java.lang.Runnable
接口就是一个函数式接口,假设有一个startThread
方法使用该接口作为参数,那么就可以使用Lambda进行传参。
public class Demo04Runnable { public static void main(String[] args) { startThread(()-> System.out.println("线程任务执行")); } //自定义方法 private static void startThread(Runnable task) { //函数式接口作为参数,task是一个任务类 new Thread(task).start(); //使用匿名函数创建线程,将任务类作为参数传入,并启动线程。 } }
使用Lambda来取代内部类有诸多好处,不仅语义更加简洁明确,而且可以减少独立的.class字节码文件的相关操作从而节省性能。
自定义Lambda参数
自定义一个函数式接口MySupplier
,含有无参数的抽象方法get
得到Object
类型的返回值。并使用该函数式接口作为方法的参数。
函数式接口MySupplier
如:
@FunctionalInterface //自定义一个函数式接口,抽象方法名为get,返回值类型为Object类型。 public interface MySupplier { Object get(); }
使用该接口作为方法的参数,并且在传递参数时将实际参数写成Lambda:
public class Demo05Supplier { public static void main(String[] args) { printParam(()->"Hello"); //在传递参数时将实际参数写成Lambda } private static void printParam(MySupplier supplier) { //使用函数式接口作为参数 System.out.println(supplier.get()); } }
使用Lambda作为返回值
类似地,如果一个方法的返回值类型是一个函数式接口,那么就可以直接返回一个Lambda表达式。当需要通过一个方法来获取一个java.util.Comparator
接口类型的对象作为排序器时:
定义一个字符串数组按照字符串的长度降序排序
//需求:定义一个字符串数组按照字符串的长度降序排序。 public class Demo { public static void main(String[] args) { //定义一个字符串数组 String[] arr={"abc","ab","abcds","adef"}; //自然排序 // Arrays.sort(arr); // //输出 // System.out.println(Arrays.toString(arr));//[ab, abc, abcds, adef] //使用自定义比较器Comparator按照字符串长度进行排序 /*Arrays.sort(arr, new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.length() - o1.length();//因为是降序,所以是o2在前面 } });*/ //使用Lambda完成上述功能 /*Arrays.sort(arr,(o1,o2)->{ return o2.length() - o1.length();//[abcds, adef, abc, ab] });*/ //简化版 // Arrays.sort(arr,(o1,o2)-> o2.length() - o1.length());//[abcds, adef, abc, ab] Arrays.sort(arr,getComparator());//[abcds, adef, abc, ab] System.out.println(Arrays.toString(arr));//[abcds, adef, abc, ab] } //定义一个方法来提供自定义比较器Comparator的对象 public static Comparator<String> getComparator() { //返回Comparator的对象 /*return new Comparator<String>() { @Override public int compare(String o1, String o2) { return o2.length() - o1.length();//因为是降序,所以是o2在前面 } };*/ //使用Lambda表达式返回Comparator的对象 return (o1,o2)-> o2.length() - o1.length(); } } ------------------------------------------------------ 运行结果: [abcds, adef, abc, ab]
其中直接return一个Lambda表达式即可
自定义Lambda返回值
请结合上文的MySupplier
接口作为方法的返回值类型,并在方法的实现中使用Lambda表达式作为返回值内容。
public class Demo07MySupplier { private static MySupplier getData() { return () -> "Hello"; } private static void printData(MySupplier supplier) { System.out.println(supplier.get()); } public static void main(String[] args) { printData(getData()); //其中main方法不再自己指定Lambda表达式,而是通过调用一个getData方法来获取Lambda的内容。 } }
其中main方法不再自己指定Lambda表达式,而是通过调用一个getData方法来获取Lambda的内容。
对于Lambda的总结:
其实可以理解为1.8之后Lambda的诞生的思想是write less do more(写的少,做的多)。间接可以将Lambda理解为是对匿名内部类的简化,Lambda可以更节省空间和代码量并完成相同的功能。