Java中的Lambda表达式
Java15马上就要发布了,但是大部分的Java开发者用的还是Java8,尽管Java8已经发布了很多年了,但是很多开发者还以和老版本jdk不兼容、不好调试等理由,还没有用过Lambda。根据2020JVM生态报告,只有3%的程序用的是1.7或者更老的版本,剩下都是使用Java8或者更新的版本。所以以后写的代码大概率是不会运行到老的jdk上的。所以赶紧掌握Lambda吧,学会之前你会很排斥Lambda的写法,学会之后“真香”!
Lambda表达式是java8的最重要的新功能。Lambda 表达式赋予了 Java 程序员相较于其他函数式编程语言缺失的特性。
本篇文章会涉及Lambda相关的所有知识,包括:Lambda语法、函数式接口的使用、方法的引用、Stream流式处理。
一、Lambda初体验
Lambda表达式可以简单的理解就是只有一个抽象函数的接口实现,有了它就可以告别匿名内部类,代码看起来更简洁易懂。话不多说,先看个例子:
public class LambdaDemo { public static void main(String[] args) { List languages = Arrays.asList("java", "c", "c++", "python"); //普通的方法按照长度排序 Collections.sort(languages, new Comparator() { @Override public int compare(String o1, String o2) { return o1.length() - o2.length(); } }); for (String language : languages) { System.out.println(language); } //使用lambda表达式排序 Collections.sort(languages,(a,b)->a.length()-b.length()); languages.forEach(System.out::println); }}
上面的代码是调用Collections中的sorts方法对一个List进行排序:可以看到第二个参数传入的是一个Comparator接口。
public static void sort(List list, Comparator super T> c) {list.sort(c);}
我们再来看看接口的实现:是一个使用@FunctionalInterface注解注释的接口,这个注释代表这是一个函数式接口,即只有一个函数的接口
@FunctionalInterfacepublic interface Comparator { int compare(T o1, T o2); //其他方法都是有默认实现,或者Object中的方法}
其中第一种常规方法就是传入一个匿名内部类实现这个接口,然后使用循环去遍历这个列表。
第二种方法是将匿名内部类使用一个 Lambda表达式代替,并使用::来表示方法引用。
很明显第二种会比第一种更加简洁、易读(理解Lambda的前提)。
二、Lambda表达式的几种写法:
感受了Lambda的优势之后,我们来详细看下Lambda的几种语法:
public class LambdaDemo2 { public static void main(String[] args) { //1、普通的匿名内部类 UserService userService1 = new UserService() { @Override public int insert(User user) { return 1; } }; System.out.println(userService1.insert(new User())); //2、使用lambda表达式代替匿名内部类 UserService userService2 = (User user) -> {return 2;}; System.out.println(userService2.insert(new User())); //3、()中的入参可以不写类型,会自动匹配 UserService userService3 = (user) -> {return 3;}; System.out.println(userService3.insert(new User())); //4、如果函数体只有一行,可以去掉大括号,如果是return语句,还可以省略return UserService userService4 = (user) -> 4; System.out.println(userService4.insert(new User())); } interface UserService { int insert(User user); } static class User { int name; }}
结果
1234
Lambda表达式的语法是一个能省即省的过程,看完下面的过程就对Lambda表达式的语法有了一定的了解。
//最完整语法(type1 arg1, type2 arg2...) -> { doSomeThing();return 1;}//参数的类型可以省略,java会根据上下文推断(arg1, arg2...) -> { doSomeThing();return 1;}//没有参数的就小括号中为空() -> { doSomeThing();return 1;}//如果函数体只有一行就可以省略大括号() -> doSomeThing()//如果这一行是return语句,return也可以省略() -> 1
至此,这就是一个最简单的Lambda表达式,它代表一个没有入参,返回值为1的函数,等同于
void fun() {
return 1;
}
三、Java中提供的函数式接口
讲完了Lambda的语法,我们再来详细的看下java中的函数式接口。
函数式接口:有且只有一个函数的接口。
JDK1.8之前就出现了一些符合函数式接口定义的接口:
java.lang.Runnablejava.util.concurrent.Callablejava.security.PrivilegedActionjava.util.Comparatorjava.io.FileFilterjava.nio.file.PathMatcherjava.lang.reflect.InvocationHandlerjava.beans.PropertyChangeListenerjava.awt.event.ActionListenerjavax.swing.event.ChangeListener
JDK1.8之后,又添加了一组函数式接口:
java.util.function.*例如:java.util.function.Function(一个输入、一个输出)java.util.function.Supplier(没有输入、一个输出)java.util.function.Consumer(一个输入、没有输出)java.util.function.BiConsumer(两个输入、没有输出)...
使用例子:
public class LambdaDemo3 { /** * java中提供了一系列的函数式接口,相当于把一个函数赋值给一个变量 * @param args */ public static void main(String[] args) { //代表一个输入一个输出的函数 Function func = (s)->s.length(); System.out.println(func.apply("aaa")); //代表没有输入,一个输出的函数 Supplier supp = ()-> "aaa"; System.out.println(supp.get()); //代表一个输入,没有输出的函数 Consumer consumer = (s)-> System.out.println(s); consumer.accept("aaa"); //代表两个输入、没有输出 BiConsumer biConsumer = (s,i)-> System.out.println(s+i); biConsumer.accept("aaa",1); }}
说白了,就是将函数赋值给一个变量,使用这个变量中的方法就可以调用这个函数。
函数式接口一般都会加上@FunctionalInterface注解,
@FunctionalInterface注解是对函数式接口的标识,他的作用是对接口进行编译级别的检查,如果一个接口使用了这个注解,但是写了两个抽象方法,会出现编译错误。
四、方法的引用
方法的引用是用来直接访问类或者实例的已经存在的方法或者构造方法,方法引用提供了一种引用而不执行的方式。::双冒号作为方法引用的符号。
听起来很高大上,说白了就是我们上一个章节的再次简略写法:
public class LambdaDemo5 { public static void main(String[] args) { //Lambda表达式的写法 Function func = (s)->Test.getLength(s); System.out.println(func.apply("aaa")); //方法引用写法 Function func2 = Test::getLength; System.out.println(func2.apply("aaa")); } static class Test { public static int getLength(String s) { return s.length(); } }}
可以看到就是把Lambda表达式再做了一个简化,除了静态方法,它还可以表示一下四种:
- 静态方法引用
(args)->类名.staticMethod(args)
类名::staticMethod
- 实例方法引用
(args)->实例.instMethod(args)
实例::instMethod
- 对象方法引用
(inst,args)->类名.instMethod(args)
类名::实例方法
- 构造方法引用
(Args)->new 类名(args)
(类名::new)
五、Lambda 表达式与匿名类的区别
使用匿名类与 Lambda 表达式的一大区别在于关键词的使用。对于匿名类,关键词 this 解读为匿名类,而对于 Lambda 表达式,关键词 this 解读为写 Lambda 的外部类。
Lambda 表达式与匿名类的另一不同在于两者的编译方法。Java 编译器编译 Lambda 表达式并将他们转化为类里面的私有函数,它使用 Java 7 中新加的 invokedynamic 指令动态绑定该方法,关于 Java 如何将 Lambda 表达式编译为字节码,Tal Weiss 写了一篇很好的文章。
六、Stream API
你还在用各种for循环来遍历集合吗?Stream 是 Java 8 中集合数据处理的利器,很多本来复杂、需要写很多代码的方法,比如过滤、分组等操作,往往使用 Stream 就可以在一行代码搞定。这也是Lambda表达式用的最多的地方。这种代码更多地表达了业务逻辑的意图,而不是它的实现机制。易读的代码也易于维护、更可靠、更不容易出错。
Stream API初体验
举个例子:
public static void main(String[] args) { List nameList = new ArrayList(); nameList.add("张三"); nameList.add("李四"); nameList.add("王五"); nameList.add("王五"); //计算有几个王五 //常规做法 int count1 = 0; for (String name: nameList) { if(name.equals("仁昌居士")) count1++; } System.out.println(count1); //流式做法 long count2 = nameList.stream() .filter(name->name.equals("王五")) .count(); }
上述代码实际是三步:
第一步:nameList创建了一个Stream实例,
第二步:用fliter操作符过滤找出为“王五”的name,并转换成另外一个Stream,
第三步:把Stream的里面包含的内容按照某种算法来成型成一个值,代码中式用count操作符计算有几个这样的name。
是不是代码逻辑很清晰?再次强调了这种代码更多地表达了业务逻辑的意图,而不是它的实现机制。
附上一个很全的Stream使用实例(比较java7和java8不同写法的特点):
public class StreamDemo { public static void main(String args[]) { List strings = Arrays.asList("abc", "", "bc", "efg", "abcd", "", "jkl"); System.out.println("原始列表: " + strings); // 计算空字符串的个数 System.out.print("使用 Java 7: "); long count = getCountEmptyStringUsingJava7(strings); System.out.println("空字符数量为: " + count); System.out.print("使用 Java 8: "); count = strings.stream().filter(string -> string.isEmpty()).count(); System.out.println("空字符串数量为: " + count); // 删除空字符串 System.out.print("使用 Java 7: "); List filtered = deleteEmptyStringsUsingJava7(strings); System.out.println("筛选后的列表: " + filtered); System.out.print("使用 Java 8: "); filtered = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.toList()); System.out.println("筛选后的列表: " + filtered); // 删除空字符串,并使用逗号把它们合并起来 System.out.print("使用 Java 7: "); String mergedString = getMergedStringUsingJava7(strings, ", "); System.out.println("合并字符串: " + mergedString); System.out.print("使用 Java 8: "); mergedString = strings.stream().filter(string -> !string.isEmpty()).collect(Collectors.joining(", ")); System.out.println("合并字符串: " + mergedString); List numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5); // 获取列表元素平方数 System.out.print("使用 Java 7: "); List squaresList = getSquares(numbers); System.out.println("平方数列表: " + squaresList); System.out.print("使用 Java 8: "); squaresList = numbers.stream().map(i -> i * i).distinct().collect(Collectors.toList()); System.out.println("平方数列表: " + squaresList); //对List进行统计计算 System.out.println(" 使用 Java 7: "); List integers = Arrays.asList(1, 2, 13, 4, 15, 6, 17, 8, 19); System.out.println("列表: " + integers); System.out.println("列表中最大的数 : " + getMax(integers)); System.out.println("列表中最小的数 : " + getMin(integers)); System.out.println("所有数之和 : " + getSum(integers)); System.out.println("平均数 : " + getAverage(integers)); System.out.println("随机数: "); Random random = new Random(); for (int i = 0; i < 10; i++) { System.out.print(random.nextInt()); } System.out.println(); System.out.println(" 使用 Java 8: "); System.out.println("列表: " + integers); IntSummaryStatistics stats = integers.stream().mapToInt((x) -> x).summaryStatistics(); System.out.println("列表中最大的数 : " + stats.getMax()); System.out.println("列表中最小的数 : " + stats.getMin()); System.out.println("所有数之和 : " + stats.getSum()); System.out.println("平均数 : " + stats.getAverage()); System.out.println("随机数: "); random.ints().limit(10).sorted().forEach(System.out::print); System.out.println(); // 并行处理 count = strings.parallelStream().filter(string -> string.isEmpty()).count(); System.out.println(" 空字符串的数量为: " + count); } private static int getCountEmptyStringUsingJava7(List strings) { int count = 0; for (String string : strings) { if (string.isEmpty()) { count++; } } return count; } private static int getCountLength3UsingJava7(List strings) { int count = 0; for (String string : strings) { if (string.length() == 3) { count++; } } return count; } private static List deleteEmptyStringsUsingJava7(List strings) { List filteredList = new ArrayList(); for (String string : strings) { if (!string.isEmpty()) { filteredList.add(string); } } return filteredList; } private static String getMergedStringUsingJava7(List strings, String separator) { StringBuilder stringBuilder = new StringBuilder(); for (String string : strings) { if (!string.isEmpty()) { stringBuilder.append(string); stringBuilder.append(separator); } } String mergedString = stringBuilder.toString(); return mergedString.substring(0, mergedString.length() - 2); } private static List getSquares(List numbers) { List squaresList = new ArrayList(); for (Integer number : numbers) { Integer square = new Integer(number.intValue() * number.intValue()); if (!squaresList.contains(square)) { squaresList.add(square); } } return squaresList; } private static int getMax(List numbers) { int max = numbers.get(0); for (int i = 1; i < numbers.size(); i++) { Integer number = numbers.get(i); if (number.intValue() > max) { max = number.intValue(); } } return max; } private static int getMin(List numbers) { int min = numbers.get(0); for (int i = 1; i < numbers.size(); i++) { Integer number = numbers.get(i); if (number.intValue() < min) { min = number.intValue(); } } return min; } private static int getSum(List numbers) { int sum = (int) (numbers.get(0)); for (int i = 1; i < numbers.size(); i++) { sum += (int) numbers.get(i); } return sum; } private static int getAverage(List numbers) { return getSum(numbers) / numbers.size(); }}
运行结果:
原始列表: [abc, , bc, efg, abcd, , jkl]使用 Java 7: 空字符数量为: 2使用 Java 8: 空字符串数量为: 2使用 Java 7: 筛选后的列表: [abc, bc, efg, abcd, jkl]使用 Java 8: 筛选后的列表: [abc, bc, efg, abcd, jkl]使用 Java 7: 合并字符串: abc, bc, efg, abcd, jkl使用 Java 8: 合并字符串: abc, bc, efg, abcd, jkl使用 Java 7: 平方数列表: [9, 4, 49, 25]使用 Java 8: 平方数列表: [9, 4, 49, 25] 使用 Java 7: 列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]列表中最大的数 : 19列表中最小的数 : 1所有数之和 : 85平均数 : 9随机数: -35366133-1298526701-1266337525-20818345831538108643-5821116191889783487-8403190421768486081-155357501 使用 Java 8: 列表: [1, 2, 13, 4, 15, 6, 17, 8, 19]列表中最大的数 : 19列表中最小的数 : 1所有数之和 : 85平均数 : 9.444444444444445随机数: -2019356909-1502217945-1487693314-105281026276659503433663505727857020178489725118234876092120424373 空字符串的数量为: 2
好!至此Java8中的Lambda表达式相关的所有知识都已经介绍完了,是不是很香呢?赶紧在项目中用起来吧!
版权声明: 本文为 InfoQ 作者【MJ】的原创文章。
原文链接:【https://xie.infoq.cn/article/1a9f7780c3d7445934c099702】。