lambda表达式

lambda表达式

作 | 少鹏(cherry)

1 初识lambda表达式

List<String> list = Arrays.asList("one", "two", "three", "four");
List<String> list2 = list.stream()
	.filter(e -> e.length() > 3)
	.peek(e -> System.out.println("first: " + e))
	.filter(e -> e.length() > 4)
	.peek(e -> System.out.println("second: " + e))
	.collect(Collectors.toList()).forEach(System.out::println);

上面这一串代码就是利用lambda表达式,以及Stream流(不同于IO流)完成对集合的过滤(filter)、遍历输出(forEach)等操作。

第一次看到lambda表达式可能会比较萌比,下面咱们慢慢聊。

2 理解lambda表达式

上面那串代码中,出现的filter(...)、peek(...)、forEach(...)似乎都没见过呢,这样还能接受,可是e -> e.length() > 3是个什么鬼?System.out::println这东西也没见过哇?其实这些就是所谓的lambda表达式,也就是大家常说的函数式编程。

接下来咱们聊聊Java8中的新特性,函数接口

2.1 函数接口

通俗的话讲:函数接口是指只含有一个抽象方法的接口,在Java8中,接口里面可以存在已实现的方法,如:default默认方法、static静态方法。

函数接口长这样:

@FunctionalInterface
public interface Supplier<T> {
    T get();
}

注意:@FunctionalInterface注解的作用,当函数接口中存在两个或两个以上的抽象方法时,编译会报错。

前言中谈到的e -> e.length() > 3 其实就是函数接口类型的实例。它是一个实例,那么问题来了,它的类型是什么?答案是:它的类型是由上下文推导而来

下面看一个简单的例子:

List<String> list = Arrays.asList("one", "two", "three", "four");
list.forEach(e -> System.out.println(e));

通过查看源码,发现forEach方法接收的参数类型为:Consumer<? super T> action,因此可推断lambda表达式的类型:

Consumer<String> action = e -> System.out.println(e);

如何理解这句代码呢?先给出java.util.function.Consumer的源码:

@FunctionalInterface
public interface Consumer<T> {
    void accept(T t);
    ...(省略default方法)
}

可以这样理解:e -> System.out.println(e),就是实现了Consumer接口中的accept方法的实例->前面部分e表示 accept方法的入参->后面部分表示 accept方法的实现部分

注意:

  • 如果方法的实现部分由多行代码组成,就用{} 括起来。
  • 如果方法需要返回值,而且方法体只有一行代码,可以省略return。如果方法体有多行代码,则不能省略return

OK!带有->的lambda表达式,相信大家都基本理解了吧,那么这个System.out::println又是个什么鬼呢?这个是方法引用,也是lambda表达式的一种形式。

2.2 方法引用

之前说过,lambda表达式就是函数接口类型的实例,而创建该实例的过程,需要实现函数接口中的抽象方法。因此,可以采用引用的方式,即引用已定义好的方法去实现函数接口中的方法。

OK!那么System.out::println该如何理解?同样根据上下文推断lambda表达式的类型:

Consumer action = System.out::println;

System.out::println的函数接口类型为java.util.function.Consumer,即在产生该实例对象时,引用了System.out对象中的println方法,去实现Consumer接口中的accept抽象方法。简而言之,即:引用println方法去实现接口中的抽象方法。

方法引用有很多种,下面主要讲述以下几种:

  • 构造方法引用:Class::new
  • 静态方法引用:ClassName::methodName
  • 实例上的实例方法引用:instanceReference::methodName
  • 类型上的实例方法引用:ClassName::methodName
2.2.1 构造方法引用

格式:类名::new

public interface OneFunction {
	Man create(String name);
}

public class Man {
	private String name;
	public Man(){
		this.name = "小婴儿";
	}
	public Man(String name){
		this.name = name;
	}
	public String say(){
		return "my name is: "+ name;
	}
}

public class TestMan {
	public static void main(String[] args){
		//利用lambda表达式,创建函数接口的实例对象
		//函数接口中的create抽象方法引用Man中的构造器来实现
		OneFunction manLambda= Man::new;
		//函数接口实例对象,调用函数,产生man对象。
		Man man = manLambda.create("小屁孩");
		//man对象调用方法
		System.out.println(man.say());
	}
}

关于构造器引用需要注意的是:函数接口中的抽象方法,具体引用哪一个构造方法去实现,是根据抽象方法的形参来判断的。 OneFunction接口中的create方法是有参数的,因此引用有参构造去实现。

2.2.2 静态方法引用

格式:类名::静态方法名

public interface TwoFunction {
	String action(String name);
}

public class Student {
	public static String study(String subject){
		return "I am studying : "+ name;
	}
}

public class TestMan {
	public static void main(String[] args){
		TwoFunction lambda = Student::study;
		System.out.println(lambda.action("English"));
	}
}

引用静态方法的条件是:函数接口中抽象方法的形参、返回值类型与静态方法中的形参、返回值类型一致。

2.2.3 实例上的实例方法引用

格式:实例名::成员方法名

public class Teacher {
    public Teacher(){}
    public String teach(Integer days){
        return "the teach has " + days + " day's course";
    }
}

public interface ThreeFunction{
	void apply(Integer num);
}

public class Test {
	public static void main(String[] args){
		Teacher teacher = new Teacher();
		ThreeFunction lambda = teacher::teach;
		System.out.println(lambda.teach(5));
	}
}

实例对象的成员方法引用的条件:和静态方法引用的条件一样,函数接口中抽象方法的形参、返回值类型与静态方法中的形参、返回值类型一致。

2.2.4 类型上的实例方法引用

格式:类名::成员方法名

由于cherry只是一位资深的平庸开发者,在学习该用法的时候遇到了许多问题。下面贴出问题给大家看看,还望各位大牛们别取笑我,毕竟我只是个小菜啦。

public class Teacher {
	public Teacher(){}
	public int teach(){
	    return 0;
	}
	public static int teach1(String days){
	    return 0;
	}
}

public interface ThreeFunction{
	int action();
}

public class Test {
	public static void main(String[] args){
		ThreeFunction lambda = Teacher::teach;//这个代码会报错
	}
}

注意上面测试代码,ThreeFunction lambda = Teacher::teach编译不能通过。报错信息如下:
这里写图片描述
提示为:非静态方法不能被引用在静态上下文中。

该如何解决呢?

下面引用一篇博客中的解释:

String::toString 等价于lambda表达式 (s) -> s.toString() ;
这里不太容易理解,实例方法要通过对象来调用,方法引用对应Lambda,Lambda的第一个参数会成为调用实例方法的对象

解决办法:
ThreeFunction函数接口的抽象方法action中添加一个形式参数,且类型为Teacher

在此本文对类型上的实例方法引用需要满足的条件进行如下猜想

  1. 引用的成员方法不能有形式参数
  2. 函数接口的抽象方法的需要有一个形式参数
  3. 函数接口的抽象方法的形式参数的类型与引用的成员方法的所属类型相匹配

其实还有人提到了,函数接口中抽象方法的形参个数为2个,成员方法的形参省略第一个保留后一个时,也能成功引用。

3 lambda表达式常用的接口

(未完待续)请看下一片博客

4 参考博客

深入理解Java 8 Lambda(语言篇——lambda,方法引用,目标类型和默认方法)

lambda方法引用总结——烧脑吃透

Java 8 新特性:Lambda 表达式之方法引用(Lambda 表达式补充版)

java8新特性 lambda Stream map(函数式编程)

Java 8 Lambda 表达式

Java 8的新特性—终极版

  • 3
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值