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<String> languages = Arrays.asList("java", "c", "c++", "python");
//普通的方法按照长度排序
Collections.sort(languages, new Comparator<String>() {
@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 <T> void sort(List<T> list, Comparator<? super T> c) {
list.sort(c);
}
复制代码
我们再来看看接口的实现:是一个使用 @FunctionalInterface 注解注释的接口,这个注释代表这是一个函数式接口,即只有一个函数的接口
@FunctionalInterface
public interface Comparator<T> {
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;
}
}
复制代码
结果
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.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.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<String ,Integer> func = (s)->s.length();
System.out.println(func.apply("aaa"));
//代表没有输入,一个输出的函数
Supplier<String> supp = ()-> "aaa";
System.out.println(supp.get());
//代表一个输入,没有输出的函数
Consumer<String> consumer = (s)-> System.out.println(s);
consumer.accept("aaa");
//代表两个输入、没有输出
BiConsumer<String,Integer> 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<String ,Integer> func = (s)->Test.getLength(s);
System.out.println(func.apply("aaa"));
//方法引用写法
Function<String ,Integer> 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<String> nameList = new ArrayList<String>();
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<String> 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<String> 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<Integer> numbers = Arrays.asList(3, 2, 2, 3, 7, 3, 5);
// 获取列表元素平方数
System.out.print("使用 Java 7: ");
List<Integer> 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("\n 使用 Java 7: ");
List<Integer> 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("\n 使用 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("\n 空字符串的数量为: " + count);
}
private static int getCountEmptyStringUsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (string.isEmpty()) {
count++;
}
}
return count;
}
private static int getCountLength3UsingJava7(List<String> strings) {
int count = 0;
for (String string : strings) {
if (string.length() == 3) {
count++;
}
}
return count;
}
private static List<String> deleteEmptyStringsUsingJava7(List<String> strings) {
List<String> filteredList = new ArrayList<String>();
for (String string : strings) {
if (!string.isEmpty()) {
filteredList.add(string);
}
}
return filteredList;
}
private static String getMergedStringUsingJava7(List<String> 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<Integer> getSquares(List<Integer> numbers) {
List<Integer> squaresList = new ArrayList<Integer>();
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<Integer> 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<Integer> 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<Integer> 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 表达式相关的所有知识都已经介绍完了,是不是很香呢?赶紧在项目中用起来吧!
评论 (3 条评论)