>什么是Lambda表达式
简短的说,Lambda表达式是一种用于取代匿名类,把函数行为表述为函数式编程风格的一种匿名函数。
匿名类大家自然熟悉,上面那句话提到了“函数行为”,那么什么是函数行为?
>函数行为
假设有这样一个应用场景:计算机要通过给定的某工人的薪水计算出该工人要交纳多少税。我们有以下代码:
@FunctionalInterface
interface TaxFunction{
double tax(double salary);
}
class Computer{
public double taxing(double salary,TaxFunction func){
return func.tax(salary);
}
}
计算机计算税收,应当有一个计算方式,这个计算方式就是TaxFunction接口类中给出的抽象方法tax。之所以要设计成抽象的方法,因为针对不同的收入阶层或是企业类型,可能会有不同的税收方法。假设对于工人A,税收为其总收入salary的10%:
Computer computer = new Computer();
double hisTax = computer.taxing(251, new TaxFunction() {
@Override
public double tax(double salary) {
return salary*0.1;
}
});
这其中我们使用了一个匿名类TaxFunction并直接重写了tax方法。这个tax方法就是一种函数行为,其描述了这样一种行为:x->f(x)=x*0.1,也就是传入一个状态值x,返回经过函数f映射后的值0.1*x。
这是一种函数式编程的思想。关于函数式编程,还可以查看>这篇文章<中第一个例子辅助理解。
>短小精悍的Lambda表达式
在上面那个例子中,很容易注意到我们只为了完成 x->f(x)=x*0.1 的映射,就要new 一个匿名类 TaxFunction写上足足至少四行代码来完成这个函数行为。Java本来就以冗余被人所诟病,这样的写法虽然看上去没什么问题,但是也暴露了Java代码“又臭又长”的缺点。
而Java中的Lambda表达式就是JDK8中为了避免这种冗余而出现的一种新特性。借由Lambda表达式,上面那段代码要怎么写呢?
Computer computer = new Computer();
hisTax = computer.taxing(251, x -> x*0.1);
一行就搞定了。要想知道Lambda表达式究竟是怎么完成这一个操作的,我们需要先了解两个东西:
Lambda表达式的写法和
函数式接口@FunctionalInterface。
>函数式接口@FunctionalInterface
在JDK8中提供了一个新包java.util.function,在这个包里给出了很多常用的函数式接口。我们这里以public interface Function
@FunctionalInterface//1.函数接口注解
public interface Function {
//2.唯一的一个抽象方法
R apply(T t);
//下面这些方法不用管
default Function compose(Function super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
default Function andThen(Function super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
static Function identity() {
return t -> t;
}
}
注意到,一个函数式接口有两个要素:
>Lambda表达式的写法
我们先来想想一个函数需要哪些必须内容呢?
结合上面提到的函数接口@FunctionalInterface,之所以要求必须是【唯一】的一个抽象方法,就是为了方便在用Lambda表达式替代匿名类中的函数式时能够方便地找到要替换的匿名函数。
那么Lambda表达式应该怎么书写呢?
在Lambda表达式中,有两种类型的短句:表达式(expression)和语句(statement)。表达式总是有返回值的,如3+2和3>2,一个返回int一个返回布尔值;语句总是没有返回值的,如System.out.println("身披白袍's博客")。表达式和语句可以被一对花括号 { } 裹住,被称作一个语句块,语句块可以类比于一般函数中public void func(int x){ $内容 } 中的{ $内容 }。
一个Lambda表达式总是以以下的形式出现:
* 当且仅当$短句只有一句时,可以省略花括号 { };若此时唯一的短句为一个表达式,可以省略return关键词。当左侧参数只有一个时,可以省略一对圆括号();
* 左侧就是刚刚提到的两个函数要素中的“传入的参数”,-> 右侧的内容就是“返回的值”(包括void)。
我们来举个例子,若在上文的收税模型中,我们要先打印这个工人的工资再求税收,需要这么写:
hisTax = computer.taxing(251, x -> { // 语句块,用花括号括起来
System.out.println("salary = " + x); // 打印工人的工资
return x * 0.1; // 此时这里多了一个return,因为已经不是唯一的表达式了
});
看完这些内容,再回到我们上面计算税收的例子:
//没用Lambda表达式前
double hisTax = computer.taxing(251, new TaxFunction() {
@Override
//唯一的抽象函数,可被视为一个匿名函数
public double tax(double salary) {
return salary*0.1;
}
});
//用Lambda表达式替代了接口中唯一的抽象函数tax()
hisTax = computer.taxing(251, x -> x*0.1);
是不是就能明白Lambda表达式x -> x*0.1是怎么完成匿名函数tax的功能了呢?
如果还没有体会到Lambda表达式的魔力,我们再举个例子:
List nums = Arrays.asList(1, 2, 3, 4, 4);
LinkedList list = nums.stream().
filter(x -> x>1).
map(n -> n+1 ).
collect(Collectors.toCollection(LinkedList::new));
这段代码的功能是,
过滤出大于1的数字,然后把这些大于1的数字加一,再包装成一个LinkedList。
我们一行一行来看:
LinkedList list = nums.stream().
第一行,把一个Listfilter(x -> x>1).
第二行,这是一个Lambda表达式,直接给出了函数行为:输入一个x状态,然后判断这个状态是否大于一;如果大于1,送往下一个函数map:
map(n -> n+1 ).
第三行,这是一个映射函数。在steam中很重要的概念就是映射/折叠(map/reduce或fold)。它的功能可以理解成把数据流中的每一个状态x进行一个映射操作,比如这里就是把每个状态加一后送往下一个函数collect:
collect(Collectors.toCollection(LinkedList::new));
第四行,这是一个收集函数,是stream中的Terminal操作(终点操作)。所有数据被送到这里后会被收集,并不再往下流动。Collectors.toCollection(LinkedList::new)是一个方法引用,目的是进一步简写Lambda函数,可以到这里查看相关的内容: http://blog.csdn.net/Shenpibaipao/article/details/78614280。此处只需要知道它把所有经过过滤、加一后的状态收集起来并封装成一个LinkedList就行了。
如果我们不用Lambda表达式来书写这段代码,会是怎么样的呢:
LinkedList list2 = nums.stream().
filter(new Predicate() {
@Override
public boolean test(Integer integer) {
if(integer>1)return true;
return false;
}
}).
map(new Function() {
@Override
public Integer apply(Integer integer) {
return integer+1;
}
}).
collect(Collectors.toCollection(LinkedList::new));
是不是非常非常长且非常不直观?可以说,用了Lambda表达式来书写函数行为,生产效率和代码美观度将会得到极大的提高。
看到这里基本上对Lambda表达式有了基础的了解。当然了解还是不够的,多看一些实战用例可以更好地理解Lambda表达式:http://www.importnew.com/16436.html