【Java8】Lambda表达式及内置函数式接口学习

Lambda表达式

Lambda表达式是一个匿名函数,也可以理解为闭包。在Java8之前的版本中,Java是作为面向对象式编程,但是在Java8之后引入了函数式接口。因为Java不像JavaScript那种将函数列为一级的语言,所以采用了函数式接口依附的形式实现函数,而其中Lambda表达式作为重要的匿名函数便是其中的核心。

以遍历为例,JDK1.4中采用的是外置的迭代器进行遍历,通过迭代器对集合中的数据进行一个一个的访问及操作

        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
        for (int i = 0; i < list.size() ; i++) {
            System.out.print(list.get(i)+"\t");
        }

JDK1.5中增加了增强遍历,也就是forEach,使得迭代器看起来更加便捷

        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
        for (Integer i : list) {
            System.out.print(i+"\t");
        }

当然,以上作为我们熟悉的迭代方式,应用的应该也很多,在实际业务开发中,时长会因为业务处理的繁琐,for循环的叠加,导致整个代码看起来都特别的繁重。

在JDK1.8中,Java对JDK1.5添加的forEach进行了改进

首先是最初的实现,JDK1.8添加了一个名为Consumer的消费型函数式接口,而通过重写它的accept接口达到了遍历。

        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
		list.forEach(new Consumer<Integer>() {
            //Consumer的抽象接口accept定义为接收参数不返回结果。
            @Override
            public void accept(Integer integer) {
                System.out.print(integer+"\t");
            }
        });

而Consumer接口作为函数式接口可以直接使用Lambda表达式替代

        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
		list.forEach(i -> {System.out.print(i+"\t");});

这样看起来就简洁多了。
函数式接口还提供了方法引用的方式对函数式接口实现的方式

        List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
		list.forEach(System.out::print);

因为Lambda表达式是匿名函数,所以它传入的方式可以如下:

		//Lambda表达式会根据实现的类去推断出传入参数的类型,并不需要声明,所以是匿名函数
		() -> {}			//无参数是可以直接使用括号代替
		a -> {}				//传入单个参数可以不带括号
		(String a) -> {}	//完整的实现

源码笔记:

跟进List包我们发现List继承了Collection包,Collection包又继承了Iterable包,forEach语法来源于JDK1.5的Iterable包下,所以List拥有了forEach方法。但是forEach是JDK1.8版本才引进的,需要注意这一点。
而且,我们发现forEach这个方法所在的位置Iterable包是一个接口,我们可以看到forEach接口的定义为default默认方法,添加版本为JDK1.8,通过默认方法可以在接口里面添加接口的功能,同时也保证了Java8之前功能可以正常使用。(也就是说JDK1.8版本之后接口中可以使用default语法对接口进行默认的实现)
【Java8】Lambda表达式及内置函数式接口学习_第1张图片
【Java8】Lambda表达式及内置函数式接口学习_第2张图片
【Java8】Lambda表达式及内置函数式接口学习_第3张图片
通过JDK1.8添加的forEach接口我们可以得到很多Java8的新特性的了解。首先,我们来看一下forEach接口的完整实现。

    /**
     * Performs the given action for each element of the {@code Iterable}
     * until all elements have been processed or the action throws an
     * exception.  Unless otherwise specified by the implementing class,
     * actions are performed in the order of iteration (if an iteration order
     * is specified).  Exceptions thrown by the action are relayed to the
     * caller.
     *
     * @implSpec
     * 

The default implementation behaves as if: *

{@code
     *     for (T t : this)
     *         action.accept(t);
     * }
* * @param action The action to be performed for each element * @throws NullPointerException if the specified action is null * @since 1.8 */
default void forEach(Consumer<? super T> action) { Objects.requireNonNull(action); for (T t : this) { action.accept(t); } }

forEach的说明告诉我们,forEach传入的是一种行为,它处理的是一个动作而不是数据。这也正是Lambda的操作,这是它处理的将是行为而不再是预定义行为来操作数据的转变。
而且在附加说明中告诉我们,forEach可以指定迭代方式,如果不指定则按照顺序执行。

函数式接口

在Java8中添加的函数式接口定义为:有且只能有一个抽象方法的接口称为函数式接口。
这个定义在Java8之前肯定是多余的,因为接口的方法只能是抽象方法,而在Java8之后则是有了静态方法(static)和默认方法(default)的实现。

函数式接口会通过注解@FunctionalInterface定义。在源码中,很多之前的接口也都修改为了函数式接口,如线程的Runnable接口就是其中之一。
根据Runnable接口分析,我们可以得知,函数式接口必须满足两个条件:

  1. 必须是接口类型interface,不能是类、枚举等其他类型。
  2. 必须有且只能有一个抽象方法。

当然,需要注意的是:

  1. 没有@FunctionalInterface注解但是满足了函数式接口两大条件的接口也会被Java8定义为函数式接口。
  2. 添加了@FunctionalInterface注解后,该接口必须遵循函数式接口的规则。

定义一个测试接口

@FunctionalInterface
interface FunctionalInterfaceTest {
    void test();
    //异常实现
    //String myString();
    //非异常实现
    String toString();
}

如上,我们自定义的接口myString()会因为函数式接口的定义报错,但是toString()接口却不会。原因是,函数式接口中的抽象方法实现是要经过java.lang.Object的,所以如toString()等经由Object的接口并不在函数式接口限制范围内。

四大核心函数式接口

四大核心函数接口的泛型定义

Consumer<T>		//消费型接口,有参数,无返回值类型的接口。
Supplier<T>		//供给型接口,只有返回值,没有参数
Function<T, R>	//函数型接口,输入一个参数,返回一个值。
Predicate<T>	//断言型接口,输入一个参数,返回boolean值。
  1. Consumer类似于void方法,有参数无返回值,只进行处理不返回结果。
//如forEach接口的定义
default void forEach(Consumer<? super T> action) {
    Objects.requireNonNull(action);
    for (T t : this) {
        action.accept(t);
    }
  1. Supplier与以往版本相似的接口很常见。
test.supplierTest(10,() -> 10)
public int supplierTest(int a, Supplier<Integer> supplier){
        return a * supplier.get();
    }
  1. Function类似于List集合,传入两个参数,一个为key,获取另一个value
public int compute(int a, Function<Integer,Integer> function){
        return function.apply(a);
    }
  1. Predicate作为断言型函数接口主要还是用于进行判断
    public boolean filterInt(int a, Predicate<Integer> pre){
        boolean bool = pre.test(a);
        return bool;
    }

其他常用函数接口

BiFunction<T, U, R> 	//传入两个参数,返回一个值,抽象方法为:R apply(T t, U u);
BiConsumer<T, U>		//传入两个参数,无返回值,抽象方法为:void accept(T t, U u);
ToIntFunction<T>		//传入一个参数,返回int
ToLongFunction<T>		//传入一个参数,返回long
ToDoubleFunction<T>		//传入一个参数,返回double
IntFunction<R>			//传入int,返回给定值
LongFunction<R>			//传入long,返回给定值
DoubleFunction<R>		//传入double,返回给定值

你可能感兴趣的:(JAVA)