从JDK1.8开始,JAVA引入了Lambda表达式,现在看来应该算得上是老东西了。但是很多时候做代码review还是能发现不少没有必要的无用代码,影响阅读,写的时候也觉得有点麻烦,不简洁。这个时候Lambda的重要性就提现出来了。
先笼统的介绍一下Lambda的好处,毕竟知道了它能为我们带来什么价值,我们才有衡量是否采用的标准。当然这些都是网上随便搜搜都是一大把的。
1.简洁优雅
Lambda表达式使代码更加简洁和易读。它允许开发人员使用更少的代码来表达功能,特别是在处理函数式接口时,可以避免冗余的匿名内部类语法。
2.函数式编程
Lambda表达式使Java更接近函数式编程的范式。它可以使代码更具函数式风格,例如通过将函数作为参数传递、在集合上进行函数式操作等。
3.更好的集合操作
Java中的Streams API与Lambda表达式结合使用,可以更便捷地处理集合数据,进行过滤、映射、归约等操作,使集合处理代码更加清晰和简单。
4.并行处理
Lambda表达式为并行处理提供了更好的支持。通过Streams API结合Lambda表达式,可以更容易地编写并行处理的代码,从而充分利用多核处理器的优势。
千篇一律的优点介绍完了,其实缺点才是我重点想说的,因为程序员的底线是不能有bug,再低一些的底线是出了bug可以很容易的找到,从某些方面来讲,lambda在排查一些问题是确实会带来不方便。
1.学习曲线
对于习惯传统Java开发风格的开发人员,学习和理解Lambda表达式可能需要一定时间。特别是对于初学者来说,理解Lambda表达式的概念和语法可能会有些困难。
2.可读性问题
在某些情况下,Lambda表达式可能会导致代码可读性下降,尤其是在一些复杂的Lambda表达式中。如果过度使用Lambda表达式,可能会让代码变得晦涩难懂。
3.调试困难
使用Lambda表达式时,调试代码可能会比传统的方法略微困难一些。Lambda表达式是匿名的,因此在调试时可能不太容易追踪到具体的代码。 好了,优点缺点都说完了,我们可以有选择的在某些场景来使用lambda来让我们的代码更简洁高效一些。
下边开始举例应用场景和示例代码,本文中的每个例子都是可以直接复制到开发工具中执行的。
一、遍历
很基础的功能,代码中有注释,一眼懂。
public class IteratorExample {
public static void main(String[] args) {
// 先定义一个List, 注意此方法声明的列表不可变,不能进行添加或删除操作
List<Integer> numList = Arrays.asList(213,34,1,213);
// 不用lambda方式的遍历
for (Integer integer : numList) {
System.out.println(integer);
}
// 用lambda方式的遍历
numList.forEach(System.out::println);
// lambda完整调用代码。 o代表numList中的一个元素 -> {} 是lambda的标准用法
numList.forEach(o -> {
System.out.println(o);
});
// 当lambda调用的方法参数和传参一致时,就可以用上方一行的简易写法
}
}
二、过滤
例子中声明了一个UserInfo对象,填充了一些值,我们要做得是把姓王的用户都过滤出来。这是一个很典型的数据批处理场景,传统写法for循环处理用了6行,但是用Lambda的方式只用了一行就搞定。
这种场景就是没有复杂业务,用for循环不会写错,用lambda就一定也不会写错。
public class FilterExample {
static class UserInfo{
private String name;
public UserInfo(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
UserInfo a = new UserInfo("张三");
UserInfo b = new UserInfo("李四");
UserInfo c = new UserInfo("王五");
UserInfo d = new UserInfo("王六");
List<UserInfo> list = Arrays.asList(a,b,c,d);
// 需求: 过滤出姓王的用户
// 传统写法
List<UserInfo> result1 = new ArrayList<>();
for (UserInfo userInfo : list) {
if (userInfo.getName().startsWith("王")){
result1.add(userInfo);
}
}
result1.forEach(o-> System.out.println(o.getName()));
// 使用lambda写法
List<UserInfo> result2 = list.stream().filter(o->o.getName().startsWith("王")).collect(Collectors.toList());
result2.forEach(o-> System.out.println(o.getName()));
// 使用lambda过滤大于5的数字
List<Integer> numList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
List<Integer> result3 = numList.stream().filter(item-> item>5).collect(Collectors.toList());
result3.forEach(o-> System.out.println(o));
}
}
三、排序
很常见的业务场景,比如需要拉出一个订单列表,按某个字段值来进行排序。使用传统的Collections.sort会自动实现接口中的compare方法,一下子就多出来一坨代码,其中大部分对我们来说都是无用代码。传统的实现方式如果不会写错,那用Lambda就更不会出错了,只需一行代码就可以完成排序。顺带提一嘴o1.compare(o2)是升序,调换位置就是降序。
public class SortExample {
public static void main(String[] args) {
// 先定义一个List, 注意此方法声明的列表不可变,不能进行添加或删除操作
List<Integer> numList = Arrays.asList(213,34,1,213);
// 不用lambda方式的排序
Collections.sort(numList, new Comparator<Integer>() {
@Override
public int compare(Integer o1, Integer o2) {
return o1.compareTo(o2);
}
});
System.out.println(numList);
// 使用lambda方式的排序,为了看到排序变化,这里改成倒序
Collections.sort(numList, (o1, o2) -> o2.compareTo(o1));
System.out.println(numList);
}
}
四、映射
名字不知道对不对,其实是不知道如何概括这个操作,与其说是映射,不如说是从数据管道中拿到自己想要的数据。例子中同样使用List来做操作,通过Lambda的方式拿到想要的字段集合。一行代码代替了6行代码,而且可读性不错。
public class MapExample {
static class UserInfo{
private String name;
public UserInfo(String name){
this.name = name;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
UserInfo a = new UserInfo("张三");
UserInfo b = new UserInfo("李四");
UserInfo c = new UserInfo("王五");
UserInfo d = new UserInfo("王六");
List<UserInfo> list = Arrays.asList(a,b,c,d);
// 需求: 取出list中用户信息的名称
// 传统写法
List<String> nameList = new ArrayList<>();
for (UserInfo userInfo : list) {
nameList.add(userInfo.getName());
}
nameList.forEach(System.out::println);
// lambda写法
// List<?>需要定义什么类型, map(对象::属性)就返回对应的类型值
List<String> nameList2 = list.stream().map(UserInfo::getName).collect(Collectors.toList());
nameList2.forEach(System.out::println);
}
}
五、分组
就我个人而言,这个用法是我在Lambda表达式中体验最好的一个。把一个大列表按照不同的规则拆分成几个列表存起来。
该操作的效率是相对较高的,其时间复杂度通常是 O(n),如果数据量很大,那么操作的效率可能会受到影响。其次这个分组条件非常复杂或计算量较大,那么操作的效率可能会相应降低。但是通常情况下,分类条件都是简单的字段或方法引用,所以效率通常不会受到太大的影响。
冗余的一大坨代码和言简意赅的一行代码,相信你会做出自己的选择。
public class GroupExample {
static class UserInfo{
private String name;
private Integer gender;
public UserInfo(String name, Integer gender) {
this.name = name;
this.gender = gender;
}
public Integer getGender() {
return gender;
}
public void setGender(Integer gender) {
this.gender = gender;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
public static void main(String[] args) {
UserInfo a = new UserInfo("张三",1);
UserInfo b = new UserInfo("李四",2);
UserInfo c = new UserInfo("王五",1);
UserInfo d = new UserInfo("王六",0);
List<UserInfo> list = Arrays.asList(a,b,c,d);
// 需求: 按性别分组存入不同的集合
// 传统写法
Map<Integer,List<UserInfo>> result1 = new HashMap<>();
for (UserInfo userInfo : list) {
Integer key = userInfo.getGender();
if (!result1.containsKey(key)){
result1.put(key,new ArrayList<>());
}
result1.get(key).add(userInfo);
}
result1.forEach((k,v)->{
System.out.println("group:" +k);
v.forEach(GroupExample::printUserName);
});
// lambda写法
Map<Integer,List<UserInfo>> result2 = list.stream().collect(Collectors.groupingBy((UserInfo::getGender)));
result2.forEach((k,v)->{
System.out.println("group:" +k);
v.forEach(GroupExample::printUserName);
});
}
// 定义一个静态方法用于打印UserInfo对象信息
public static void printUserName(UserInfo userInfo) {
System.out.println(userInfo.getName());
}
}
六、聚合
Lambda中的聚合操作适用于对集合中的元素进行汇总、归约或合并的场景。这些操作通常将集合中的多个元素合并为一个单一的结果。Java中的Stream API提供了丰富的聚合操作,可以方便地处理集合数据。这个例子中只举例了归约和.sum求和三种不同写法。其余的操作还有计算总数(.count)、求平均值(.average)、求最大/最小值(.max/.min)、拼接字符串等,还支持一些更复杂的操作。想研究的话网上搜索“lambda聚合操作”可以慢慢研究。
public class ReduceExample {
public static void main(String[] args) {
List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
// 需求:求list中所有元素的和
// 传统写法
int sum = 0;
for (Integer integer : list) {
sum += integer;
}
System.out.println(sum);
// lambda写法1
Integer integer = list.stream().reduce(0, Integer::sum);
System.out.println(integer);
// lambda写法2 第一个参数为初始值, 后边的参数是每次循环计算得出的结果和下一个元素
Integer integer2 = list.stream().reduce(0, (currentElement, nextElement) -> currentElement + nextElement);
System.out.println(integer2);
// lambda写法3 .sum()求和
Integer integer3 = list.stream().mapToInt(Integer::intValue).sum();
System.out.println(integer3);
}
}
七、函数式接口
需要特别注意的是函数式接口必须只包含一个抽象方法。如果接口中包含多个抽象方法,将无法使用Lambda表达式来实现该接口。个人大部分使用函数式接口的时候是使用jdk或者其他类库的方法时使用,比如示例代码中的创建线程。
interface CustomInterface{
public Integer sum(Integer a, Integer b);
}
public class FunctionInterfaceExample {
public static void main(String[] args) {
// 传统调用接口方法
CustomInterface customInterface = new CustomInterface() {
@Override
public Integer sum(Integer a, Integer b) {
return a + b;
}
};
Integer result = customInterface.sum(1,2);
System.out.println(result);
//lambda调用接口方法
//lambda表达式适用于只有一个抽象方法的接口,被称为函数式接口
CustomInterface custom1 = (a, b) -> a + b;
Integer result1 = custom1.sum(1,2);
System.out.println(result1);
// ===实用比如创建线程===
// 传统写法
Thread thread1 = new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
};
thread1.start();
// lambda写法
Thread thread2 = new Thread(() -> System.out.println(Thread.currentThread().getName()));
thread2.start();
}
}
发表评论