λ表达式

匿名函数

啥是匿名函数

在写这篇文章之前我去查了一遍λ表达式,然后就发现了这个东西——匿名函数,至于为啥要说匿名函数,我们以后再说,下面是wiki对匿名函数的定义:

在计算机编程中,匿名函数(英语:anonymous function)是指一类无需定义标识符(函数名)的函数或子程序,普遍存在于多种编程语言中。

相信你们看完后对匿名函数有一个大概印象了。顾名思义,匿名函数就是没名字的函数,你可能会问,一个函数没名字我们咋知道它就是它,是的,我们当然不知道,但是这都建立在多次调用这个函数的前提下,如果一个函数我们只用一次就不需要它了,它还需要名字吗?类似下面的例子:这个异常我们仅仅抛出一次,它还需要名字吗?

1
throw new NullPointerException();

没错这里的NullPointerException()实例是没名字的,但这样很方便不是吗?new NullPointerException()就是一个匿名的对象,匿名函数也是一样的道理。

匿名函数咋写

知道匿名函数是啥后,匿名函数咋写呢(以java为例)?

先来看一个普通函数:

1
2
3
4
5
void sum(int a,int b){
int x = a*a;
int y = b*b;
return x+y;
}

普通函数(方法)有以下几个部分:

  • 返回值类型
  • 函数名
  • 参数列表
  • 函数体
  • 返回值
    再来看看这个函数转换成匿名函数后的样子:
    1
    2
    3
    4
    5
    (int a, int b) -> {
    int x = a*a;
    int y = b*b;
    return x+y;
    }
    上面的普通函数和匿名函数的差别还是很明显的,和普通函数相比,匿名函数少了返回值类型,少了函数名,多了一个特殊的符号->,没错这就是匿名函数的一般写法:
    1
    2
    (参数列表)->{执行逻辑}
    ()->{执行逻辑}//没返参数的时候前面的括号不能省略
    当然如果执行逻辑只有一行的话可以省去大括号,->后面只写返回值:
    1
    (int a, int b) -> a*b+b*b

下面还有几个常见的匿名函数的例子:

1
2
3
(String s)->System.out.print(s)
(a,b,c)->a+b+C //有的时候传入参数列表也可以省
a->a*a //一个参数可以省略括号

叨叨完匿名函数可以告诉你们了,匿名函数就是λ表达式

java中的λ表达式

下面的内容主要是对《java 和谐技术I》一书中λ表达式部分的总结

函数式接口

java中的某些接口仅仅由一个抽象方法构成,比如线程中常用的Runnable接口里面只有一个抽象方法:

1
2
3
public interface Runnable {
public abstract void run();
}

唯一目的就是给Thread类来实现,以定义线程在运行过程中的具体实现(也就是run()方法)。下面是一个线程的创建,实现的run()方法就是线程启动时候执行的动作:

1
2
3
4
5
6
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("hello java");
}
});

为了定义线程的动作又是传入接口,又是实现抽象函数,这样是不是麻烦了一点,能不能直接把run()里面的代码块也就是”动作“当参数传如呢?答案当然是肯定的,这就要借助λ表达式的力量:

1
2
Runnable r = ()->System.out.println("hello java");
Thread thread = new Thread(r);

这样是不是方便多了,上面这段代码最引人注目的地方就是Runnable r = ()->System.out.println("hello java");把一个匿名函数(λ表达式)赋给一个接口,匿名函数就是接口内唯一抽象函数的实现,然后把实现函数后的接口传给Thread,真是不得不佩服java设计者的思想(其实其他语言早就有了,java8才支持这个)。上面的代码还能继续简写:

1
Thread thread = new Thread(()->System.out.println("hello java"));

上面就是λ表达式的神奇作用了,我们把这样的接口实现叫函数式的接口。顺便一说,函数式接口的传入参数类型已经在要实现的抽象函数里面定义了,因此我们的参数列表不用再写类型,java编译器会自动判断,如:

1
Demo d = new Demo(s->System.out.println(s));

方法引用

出乎意料的是,上面的Demo d = new Demo(s->System.out.println(s));还能继续简化,如果匿名函数调用的是现成的方法,可以用class::method的方式进一步简化λ表达式表达式(莫名想到C++),如:

1
2
s->System.out.println(s)//简化成下面的class::method,甚至参数都不要了
System.out::println

上面两行代码的效果完全是一模一样的,你可能会问编译器怎么知道要打印啥呢?这个其实不用担心,java编译器可以自动识别,再比如:

1
2
(x,y)->Math.pow(x,y)
Math::pow

下面两行是一模一样的,我们需要指名的是”干什么“,至于参数处理问题交给编译器自己去分析就好了。除了class::method外,我们还能通过相对路径(class是绝对路径)来进行方法引用,主要就是通过this::methodsuper::method来引用本类和父类的方法。

方法引用之外还能用class::new来进行构造器引用,由于这样的用法很不常见,这里就不细说了。

λ表达式的作用域问题

λ表达式作为一个独立的”动作“,它虽然可以访问到其外部的变量,但无法修改,因此个人不建议在λ表达式中访问外部变量,让它保持独立是最好的选择。

终于完了,可把我累死了。。