[Java]“语法糖”系列(二)之Lambda表达式/匿名函数(Lambda Expression)

>什么是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 before) {
        Objects.requireNonNull(before);
        return (V v) -> apply(before.apply(v));
    }

    default  Function andThen(Function after) {
        Objects.requireNonNull(after);
        return (T t) -> after.apply(apply(t));
    }

    static  Function identity() {
        return t -> t;
    }
}

    注意到,一个函数式接口有两个要素:

  • 唯一的一个public抽象方法。(注意:除了这个唯一的抽象方法还允许存在从Object继承下来的抽象方法,如toString()、equalTo())
  • @FunctionalInterface注解,这个注解可以显著增加代码的可读性,同时,当编程者不小心在该函数式接口中定义了多于一个的抽象方法时,IDE将会给出错误信息,杜绝一些低级编程错误。如下所示:
         [Java]“语法糖”系列(二)之Lambda表达式/匿名函数(Lambda Expression)_第1张图片


>Lambda表达式的写法

   我们先来想想一个函数需要哪些必须内容呢?

  • 传入的参数
  • 返回的值
    也就是 x - >f(x) 的一个映射,只需要有传入的参数(也称为状态)x和返回值(也称为映射值)f(x)。至于这个函数叫什么、返回值类型是什么,都不重要。一个匿名函数只需要以上两个必须要素。

     结合上面提到的函数接口@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().
    第一行,把一个List转化为数据流。关于stream流,这里有一篇非常棒的入门文章: https://www.ibm.......mapi/。可以暂时不看,你只需要知道它的功能是把nums里的数据挨个排队然后送往下一个函数filter():

filter(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

你可能感兴趣的:(JAVA)