Java 8 Lambda学习笔记(一)

一直感觉Lambda表达式是十分“高大上”的一种技术,不管是冲着其本身可以将代码量缩短至变态的缘故,还是希望拿来当做装逼神器的工具(开玩笑!),都值得好好学习一波。

好了,言归正传!Lambda表达式的格式不外乎如下:

(parameters) -> expression    或      (parameters) ->{ statements; }

语法格式和传统的方法其实一样,箭头左边就是参数列表,当然也可以是为空;箭头右边就是表达式或者代码段了。之所以推出lambda表达式,就是允许你通过表达式来代替功能接口。

需要特别注意的是,lambda表达式使用是有前提的,那就是只有函数式接口的方法才能能用lambda作为其调用时的方法。具体什么意思呢?首先来看下什么是函数式接口?

函数式接口其实本质上还是一个接口,但是它是一种特殊的接口:SAM类型的接口(Single Abstract Method)。也就是说,该接口只有一个抽象方法,多了不行,少了也不行。可以有静态方法和默认方法,因为这两种方法都是已经实现的了。同时,这个抽象方法一定不能跟Object类中的方法同名。Java8新特性提供了函数式接口,用于更好的支持函数式编程。(具体了解函数式接口可以参考另一篇博客Java 8 函数式接口和Lambda学习笔记(二)

 

下面将从简单到复杂的过程来系统学习一下lambda表达式,希望能揭开那对我来说异常神秘的面纱!

首先,是lambda表达式简单的示例,通过这几个小栗子可以加深对其概念的理解。

// 1. 不需要参数,返回值为 5  
() -> 5  
  
// 2. 接收一个参数(数字类型),返回其2倍的值  
x -> 2 * x  
  
// 3. 接受2个参数(数字),并返回他们的差值  
(x, y) -> x – y  
  
// 4. 接收2个int型整数,返回他们的和  
(int x, int y) -> x + y  
  
// 5. 接受一个 string 对象,并在控制台打印,不返回任何值(看起来像是返回void)  
(String s) -> System.out.print(s)

以第3个例子,我们尝试实现它,代码如下:

package com.wjb.lambda;
/**
 * 函数式接口:
 * 		1.只能有一个抽象方法
		2.可以有静态方法和默认方法,因为这两种方法都是已经实现的了
		3.这个抽象方法一定不能跟Object类中的方法同名。
 * @author Administrator
 *
 */
@FunctionalInterface
interface Fun{
	//只设置一个抽象方法 ------可以正常运行
	abstract int spend(int remaining,int money);
	
	//加入静态方法和默认方法 ----可以正常运行
	static void staticMethod() {
		System.out.println("There is a static method!");
	}
	default void defaultMethod() {
		System.out.println("There is a default method!");
	}
	
	//设置Object中可以重写的抽象方法 ----编译报错
//	abstract boolean equals(Object obj);
	
}
public class FunInterface{
	public static void main(String[] args) {
		Fun fun = (a,b) -> a-b;
		System.out.println(fun.spend(100, 5));
	}
}

这样看来,lambda表达式真的很简洁。

下面,我们看看lambda表达式的常用场景:

1. 列表迭代

对一个列表的每一个元素进行操作,不使用 Lambda 表达式时如下:

List<Integer> list = Arrays.asList(1,2,3,4,5);
for (Integer integer : list) {
	System.out.println(list);
}

使用Lambda表达式的话是怎么呢?

List<Integer> list = Arrays.asList(1,2,3,4,5);
list.forEach((integer2) -> System.out.println(integer2));

一行就搞定了!但是需要注意的是,java 8提出的foreach方法其底层就是实现了Consumer接口的accept方法,所以可以用lambda表达式当做参数。

如果只需要调用单个函数对列表元素进行处理,那么可以使用更加简洁的 方法引用 代替 Lambda 表达式:

list.forEach(System.out::println);

这里的方法引用属于引申,感兴趣的可以看这篇博客 Java 8 方法引用

下面列举一些真实开发可能会遇到的场景,看看lambda表达式真的是神奇:

(1) 比如:从List<Object>对象列表中获取对象单个字段组成的新List,

当我们要从List<User> userList中获取所有用户的Id列表并组成新的List要怎么做,寻常方法可以实现但是过于复杂了

List<Long> userIdList = userList.stream.map(User::getUserId).collect(Collectors.toList());

 Stream也是java 8提出的一个非常重要的特性,详情可以看 Java 8 Stream 学习笔记 

(2)修改List<User> userList中对象的某个字段值

List<User> userList;//获取所有用户的Id列表
userList.foreach(User -> User.setUserId(User.getUserId+1));

(3)修改List<User> userList中多个参数值

userList.foreach((x) -> {x.setUserId(0L); x.setUserName(""); x.setUserSex(0);})

(4) List过滤值

List<String> list
//过滤null值
list.stream.filter(x -> x != null).collect(Collectors.toList());
//过滤特定值(包括空字符串"")
list.stream.filter(x -> !"str".equals(x)).collect(Collectors.toList());

(5)逗号分隔的字符串转List<Long>

List<Long> listIds = Arrays.asList(ids.split(",")).stream().map(s -> Long.parseLong(s.trim())).collect(Collectors.toList());

代码功能是先将字符串切割成数组后转列表,然后遍历列表时去掉空格并转成Long类型的元素,最后转成新的列表。

(6) List转Map(假设列表是List<FlowNodeTimeoutRuleUser> userList)

//1.List对象中两个字段对应
Map<Long, String> userMap1 = userList.stream().collect(Collectors.toMap(FlowNodeTimeoutRuleUser::getUserId, FlowNodeTimeoutRuleUser::getUserName));
//2.List对象中字段和对象本体对应
Map<Long, String> userMap2 = userList.stream().collect(Collectors.toMap(FlowNodeTimeoutRuleUser::getUserId, x -> x));
//本体表达式可以用lambda表达式x->x 也可以使用接口Function.identity()
Map<Long, String> userMap3 = userList.stream().collect(Collectors.toMap(FlowNodeTimeoutRuleUser::getUserId, Function.identity()));
//3. List对象中key字段重复导致错误,增加一个lambda表达式(key1, key2) -> key2,后者覆盖前者解决key重复问题
Map<Long, String> userMap4 = userList.stream().collect(Collectors.toMap(FlowNodeTimeoutRuleUser::getUserId, Function.identity(), (key1, key2) -> key2));

 具体某个方法不懂的可以查看下源码就知道了。

(7)统计List中重复数量:

List<Integer> list = Arrays.asList(1,1,2,5,2,3,4,5);
//统计List中重复数量
Map<Integer,Long> map = list.stream().collect(Collectors.groupingBy(x -> x,Collectors.counting()));

 其中的lambda表达式x->x就是list元素本身。

(8)List去除重复值:

List.stream().distinct().collect(Collectors.toList());

 (9)根据list对象属性值去除重复值

List<Person> unique = persons.stream().collect(
Collectors.collectingAndThen(
Collectors.toCollection(() -> new TreeSet<>(Comparator.comparing(Person::getName))), ArrayList::new)
);

(10)List排序 按照从小到大排序

List<User> userList;
userList.sort((User u1, User u2) -> u1.getAge().compareTo(u2.getAge()));

2. 事件监听

不使用 Lambda 表达式:

button.addActionListener(new ActionListener(){
    @Override
    public void actionPerformed(ActionEvent e) {
        //handle the event
    }
});

这里是采用的匿名内部类的方式实现的。

使用 Lambda 表达式,需要编写多条语句时用花括号包围起来:

button.addActionListener(e -> {
    //handle the event
});

3. Predicate 接口

java.util.function 包中的 Predicate 接口可以很方便地用于过滤。如果你需要对多个对象进行过滤并执行相同的处理逻辑,那么可以将这些相同的操作封装到 filter 方法中,由调用者提供过滤条件,以便重复使用。

不使用 Predicate 接口,对于每一个对象,都需要编写过滤条件和处理逻辑:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<String> words = Arrays.asList("a", "ab", "abc");

numbers.forEach(x -> {
    if (x % 2 == 0) {
        //process logic
    }
})
words.forEach(x -> {
    if (x.length() > 1) {
        //process logic
    }
})

使用 Predicate 接口,将相同的处理逻辑封装到 filter 方法中,重复调用:

public static void main(String[] args) {
    List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
    List<String> words = Arrays.asList("a", "ab", "abc");

    filter(numbers, x -> (int)x % 2 == 0);
    filter(words, x -> ((String)x).length() > 1);
}

public static void filter(List list, Predicate condition) {
    list.forEach(x -> {
        if (condition.test(x)) {
            //process logic
        }
    })
}

filter 方法也可写成:

public static void filter(List list, Predicate condition) {
    list.stream().filter(x -> condition.test(x)).forEach(x -> {
        //process logic
    })
}

 

4.Map 映射

使用 Stream 对象的 map 方法将原来的列表经由 Lambda 表达式映射为另一个列表,并通过 collect 方法转换回 List 类型:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
List<Integer> mapped = numbers.stream().map(x -> x * 2).collect(Collectors.toList());
mapped.forEach(System.out::println);

将列表的元素乘以2之后赋给一个新的列表,用Stream的方式实现,代码量少了好多。

5. Reduce聚合

reduce 操作,就是通过二元运算对所有元素进行聚合,最终得到一个结果。例如使用加法对列表进行聚合,就是将列表中所有元素累加,得到总和。

因此,我们可以为 reduce 提供一个接收两个参数的 Lambda 表达式,该表达式就相当于一个二元运算:

List<Integer> numbers = Arrays.asList(1, 2, 3, 4, 5);
int sum = numbers.stream().reduce((x, y) -> x + y).get();
System.out.println(sum);

都是用Stream的方式实现的!

6. 代替Runnable

这个也是特别见的场景,下面以创建线程为例,寻常使用 Runnable 类的代码如下:

Runnable r = new Runnable() {
    @Override
    public void run() {
        //to do something
    }
};
Thread t = new Thread(r);
t.start();

使用 Lambda 表达式:

Runnable r = () -> {
    //to do something
};
Thread t = new Thread(r);
t.start();

或者使用更加紧凑的形式:

new Thread(()->System.out.println("run thread...")).start();

参考文献

Java Lambda 表达式的常见应用场景

Java中Lambda表达式的使用

工作中经常用到的lambda表达式示例

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值