java8学习之Lambda表达式初步与函数式接口

对于Java8其实相比之前的的版本增加的内容是相当多的,其中有相当一大块的内容是关于Lambda表达式与Stream API,而这两部分是紧密结合而不能将其拆开来对待的,但是是可以单独使用的,所以从学习的顺序来说首先得要学好Lambda表达式,然后再学习Stream API,最后再把这两者有机的结合起来,而这两部分涉及的知识体系又非常的多,很多东西都改变了以往对java这种面向对象语言的基本认识,所以下面一步步开始对Java8进行了解,先学好Lambda表达式。

何为Lambda表达式:

先看一个非常非常抽象的一个定义,如下:

上面看完~~可能还是一头雾水,不要着急,先对其定义有个大概的认识,重点是观注Lambda表达式是如何使用的。

为何需要Lambda表达式:

  • 在Java中,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法。
    回想一下:对于java中方法的参数一定是数据类型【要么是基本数据、要么是引用数据】,同样的作为方法的返回值也只能是数据类型。但是在JDK8版本之后这些就变为可能。
  • 在JavaScript中,函数参数是一个函数,返回值是另一上函数的情况是非常常见的;JavaScript是一个非常典型的函数式语言。
    比如说举个例子:

    这个在JavaScript中是非常常见的写法。

Java匿名内部类示例:

其实上图中的这种写法是非常违背直觉的,在点击按钮的时候其实就是执行某个方法既可,但是在传统的Java编程中还得要定义一个匿名的对象实现了View.OnclickListener,可见这种写法是比较繁琐啰嗦的,而在Java8之前也只能这么去实现。

Lambda表达式初步使用:

在上面理论过后下面编写代码来一步步引出Lambda表达式,这里依然是采用Intellij IDEA来写代码,这里采用gradle的方式来编写测试代码而不用之前学习java并发的那种方式了,具体如下:

接着就进行项目的初始化,初台化完成就可以正式编写代码了,下面开始从swing代码开始,因为它有按钮的事件,跟上面说的情况类似:

编译运行:

程序比较简单,重点不是看功能,而是在于代码分析,先来仔细观察一下代码:

把鼠标放到它上面,IDE就会有提示,提示信息如下:

说明IDE都已经检测到了这个匿名内部类的写法可以改用JDK8的Lambda表达式,为什么?这个在上面也已经谈到过了,因为对于这个匿名内部类,我们所要关心的只是其回调方法中的具体实现,如下:

那改用Lambda方式来替换目前这种匿名内部类的不人性的写法是怎么样呢?照IDE的提示来做:

顺间感觉代码精简了,但是也让人对新的写法产生了各种疑问,下面写的一些代码只要感受下就行,具体Lambda表达式之后会一步步深入:

这里可以变化一下写法就能知道实际是有类型的:

而之所以直接可以将类型省略掉是由于java编译系统借助于类型推断机制能推荐出来e一定是ActionEvent类型,所以说就没有必要再去定义类型了,当然加上也没毛病,只是多此一举而已,那是不是说java8的编译系统都能够都能推荐出这个类型是什么呢?不是的,有些时候根据上下文是推断不出来的,那此时就需要显示的来指定类型的。

根据上面的Lambda表达式的写法可以总结出它的大体样式:

(param1, param2, param3...) -> {

  //执行体

}

当然关于Lambda表达式的东东不仅仅只是这点东西,之后再慢慢探究。

函数式接口(FunctionalInterface):

接下来继续举例来说明Lambda表达式,这里以集合遍历为例,从传统的方式一直演变成现在用Lambda表达式,其中会引出一个非常重要的概念---函数式接口,具体演变过程如下:

而接着在JDK1.5之后引入了增加的for循环,如下:

输出如下:

1
2
3
4
5
6
7
8
----------------------
1
2
3
4
5
6
7
8

接着到了JAVA8了,看在这个版本遍历集合有何特色:

输出:

1
2
3
4
5
6
7
8
----------------------
1
2
3
4
5
6
7
8
----------------------
1
2
3
4
5
6
7
8

其中在遍历中用到了Consumer这个接口,乍一看貌似表面上比之前的遍历方式还麻烦,不过这是暂时滴,先点进去对Consumer这个接口接口有个了解:

要了解这个注解当然是点进去进一步看一下它的源码喽:

package java.lang;

import java.lang.annotation.*;

/**
 * An informative annotation type used to indicate that an interface
 * type declaration is intended to be a <i>functional interface</i> as
 * defined by the Java Language Specification.
 *
 * Conceptually, a functional interface has exactly one abstract
 * method.  Since {@linkplain java.lang.reflect.Method#isDefault()
 * default methods} have an implementation, they are not abstract.  If
 * an interface declares an abstract method overriding one of the
 * public methods of {@code java.lang.Object}, that also does
 * <em>not</em> count toward the interface's abstract method count
 * since any implementation of the interface will have an
 * implementation from {@code java.lang.Object} or elsewhere.
 *
 * <p>Note that instances of functional interfaces can be created with
 * lambda expressions, method references, or constructor references.
 *
 * <p>If a type is annotated with this annotation type, compilers are
 * required to generate an error message unless:
 *
 * <ul>
 * <li> The type is an interface type and not an annotation type, enum, or class.
 * <li> The annotated type satisfies the requirements of a functional interface.
 * </ul>
 *
 * <p>However, the compiler will treat any interface meeting the
 * definition of a functional interface as a functional interface
 * regardless of whether or not a {@code FunctionalInterface}
 * annotation is present on the interface declaration.
 *
 * @jls 4.3.2. The Class Object
 * @jls 9.8 Functional Interfaces
 * @jls 9.4.3 Interface Method Body
 * @since 1.8
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
public @interface FunctionalInterface {}

关于什么是函数接口在之后还会学入学习,这里先读一读它的JavaDoc,对其有一个整体的认识,那下面就来读一读对它的描述喽:

按中文的意思来理解,也就是说明凡是一个接口上标有@FunctionalInterface注解,就可以将此接口称之为函数式接口。

光看这句话对于函数式接口还是一知半解的,那就接下来继续看下面的说明:

也就是说,如果一个接口有且只有一个抽象方法,则就可以将这个接口称之为函数式接口,这个概念在Java8的Lambda表达式出来之后是一个非常重要的概念,那看到这,一个新的疑问产生了:接口里面的方法不都是抽象的么?难道接口里面能有具体实现的方法,是的~~在java8出来之后这一切就成为可能了,当然在之前版本是不可能在接口中出现非抽象方法,所以在这句描述中就对函数式接口进行了一个基本定义,接下来继续往下看说明:

这处道出了函数式接口的非常重要的点,其中Lambda表达式我们之前已经使用过了,它可以用来创建函数式接口实例:

那其它两种创建方式具体又是什么呢?这个之后会学,先不用理会。继续往下读:

最后咱们对于这个JAVADOC的说明对其函数数接口做一个总结

1、如果一个接口只有一个抽象方法,那么该接口就是一个函数式接口。

2、如果我们在某个接口上声明了FunctionalInterface注解,那么编译器就会按照函数式接口的定义来要求该接口。

3、如果某个接口只有一个抽象方法,但我们并没有给该接口声明FuncationalInterface注解,那么编译器依旧依旧会将该接口看作是函数式接口。(虽这样说,但是一般对于函数式接口建议还是加上这个注解的比较好。因为加了之后编译器会对接口增加一个强制性的保证,如果接口不满足某些条件的话是会报错提示的,就这好比子类重写父类的一个特定方法的时候,照理是应该在子类的这个方法上增加一个override方法,但是如果不加也没问题,加了的好处一是代码可读性比较好,二是如果覆写了父类中并不存在的方法那么编译器会第一时间提示出来,所以最好按照规则来:如果满足函数式接口一定要在接口上声明FuncationalInterface注解)

posted on 2017-12-25 22:39  cexo  阅读(1286)  评论(0编辑  收藏  举报

导航