彻底搞明白JDK 1.8 Lambda 表达式

Posted hanruikai

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了彻底搞明白JDK 1.8 Lambda 表达式相关的知识,希望对你有一定的参考价值。

Lambda表达式

为什么

背景

Java是面向对象语言,所以java对行为的封装都是基于对象。什么意思,简单说,我们要定义个行为,那么需要一个函数,只有函数就行了吗?在java里面不行,因为java是面向对象的语言,

所以我们需要定义对象,看下面的代码:

public interface ActionListener 
void actionPerformed(ActionEvent e);

最早接触过java swing编程的老程序员肯定不陌生,定义一个按钮触发时间,每当时间发生时,调用这个函数actionPerformed里面逻辑。

其实大家发现没有,这个类ActionListener其实完全没有必要。所以出现了匿名内部类。改进的代码如下:

button.addActionListener(new ActionListener() 
public void actionPerformed(ActionEvent e) 
ui.dazzle(e.getModifiers());

);

后来我们一直用匿名内部类定义行为,不用去定义对象了。但是仍然有问题,主要存在以下问题:

  1. 语法过于冗余
  2. 匿名类中的 this 和变量名容易使人产生误解
  3. 类型载入和实例创建语义不够灵活
  4. 无法捕获非 final 的局部变量
  5. 无法对控制流进行抽象

随着回调的大量使用和函数式编程的流行,java需要一种便捷的方式匿名内部类。

函数式接口

对于上面的,只有一个抽象方法的接口,我们定义为函数式接口。只有一个方法的接口,在jdk里面很多,比如Runnable,Compartor等。看下面的源码:



package java.lang;


@FunctionalInterface
public interface Runnable 
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();

可以通过 @FunctionalInterface 注解来显式指定一个接口是函数式接口(以避免无意声明了一个符合函数式标准的接口),加上这个注解之后,编译器就会验证该接口是否满足函数式接口的要求。

Java SE 8中增加了一个新的包:java.util.function,它里面包含了常用的函数式接口,例如:

  • Predicate<T>——接收 T 并返回 boolean
  • Consumer<T>——接收 T,不返回值
  • Function<T, R>——接收 T,返回 R
  • Supplier<T>——提供 T 对象(例如工厂),不接收值
  • UnaryOperator<T>——接收 T 对象,返回 T
  • BinaryOperator<T>——接收两个 T,返回 T

除了上面的这些基本的函数式接口,我们还提供了一些针对原始类型(Primitive type)的特化(Specialization)函数式接口,例如 IntSupplier 和 LongBinaryOperator

函数式接口与lambda表达式

看下面的代码:

    //使用com.google.guava包创建集合
    List<String> list =Lists.newArrayList("a","b","c","d");
 
    //1、正常遍历
    list.forEach(item->System.out.println(item));
    //2、根据条件遍历
    list.forEach(item->
        if("b".equals(item))
            System.out.println(item);
        

foreach里面利用的就是lamabda表达式,我们看看foreach的源代码:

public interface Iterable<T> 
 
    Iterator<T> iterator();
 
    default void forEach(Consumer<? super T> action) 
        Objects.requireNonNull(action);
        for (T t : this) 
            action.accept(t);
        
    
 
    default Spliterator<T> spliterator() 
        return Spliterators.spliteratorUnknownSize(iterator(), 0);
    

foreach的入参是Consumer类型,再看Consumer是个函数式接口,不需要指定方法,因为函数式接口只有一个方法。

函数式接口可以被隐式地转换为lamadba表达式

总结

下面的图片来自:https://www.cnblogs.com/PomeloYe/p/11807059.html

这样,我们就成功的非常优雅的把"一块代码"赋给了一个变量。而"这块代码",或者说"这个被赋给一个变量的函数",就是一个Lambda表达式

但是这里仍然有一个问题,就是变量aBlockOfCode的类型应该是什么?

在Java 8里面,所有的Lambda的类型都是一个接口,而Lambda表达式本身,也就是"那段代码",需要是这个接口的实现。这是我认为理解Lambda的一个关键所在,简而言之就是,Lambda表达式本身就是一个接口的实现。直接这样说可能还是有点让人困扰,我们继续看看例子。我们给上面的aBlockOfCode加上一个类型:

 我们看一下有返回值的情况,函数式接口返回值,其实是在函数式接口定义的,我们看compartor类:

@FunctionalInterface
public interface Comparator<T> 

使用方式如下:

 Comparator<Developer> byNameLambda =
   (Developer developer, Developer compareDeveloper)->developer.getName().compareTo(compareDeveloper.getName());

或者:

Comparator<Developer> byNameLambdaSimple = Comparator.comparing(Developer::getName);

 

以上是关于彻底搞明白JDK 1.8 Lambda 表达式的主要内容,如果未能解决你的问题,请参考以下文章

JDK 1.8 Lambda表达式集合分组条件过滤组装去重排序转换求和最值

lambda之美

JDK1.8的一些新特性

JDK1.8的一些新特性

JDK 1.8 Lambda Expression 的优点与限制

全网首发:JDK绘制文字:一绘制流程