Java8新特性之Lambda表达式解析及其常见用法

版权声明:谁不是在与生活苦战。狼性的年级,就不要做个俗人 https://blog.csdn.net/Axela30W/article/details/82288555

Lambda表达式是Java8更新的一大新特性,与同期更新的Stream是此版本的最大亮点。
“这是你从来没有玩过的全新版本” —– 扎扎辉


-> 举个栗子 : 创建一个线程

//old
Thread thread1 = new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("thread1");
    }
});

//lambda  
Thread thread2 = new Thread(() -> {
    System.out.println("thread2");
});

结构:(参数1,参数2) -> {代码块}

  • “->” 符号前面是参数,可以有0个、1个、多个。0个参数时就写个(),1个参数时可以不写括号,多个参数时要写括号。参数类型最好写上,方便阅读,可不写,同样可以通过编译,不写的时候就涉及到”类型推断“这个新特性了。
  • “->” 符号后面就是代码块,只有一行代码时可不写大括号”{}”,多行代码时要写,这里的代码块和普通方法中的代码块没有区别,也可以通过返回或者抛出异常来退出。

提下这里的new Runnable(),其实这里并不是实例化了一个接口,大家也知道接口不能实例化,参考内部类和匿名内部类的知识,这里应该是这样的:

  • ①首先构造了一个”implements Runnable “的无名内部类(方法内的内部类)
  • ②然后构造了这个无名local内部类的一个实例,(把①中构造的内部类实例化了)
  • ③然后用Runnable来表示这个无名local内部类的type

此处new Thread 的方法,接收的是一个Runnable目标对象
这里写图片描述
回到Lambda,为什么可以不声明参数类型?

  • 所有Lambda表达式中的参数类型都是由编译器推断得出的。
  • 将Lambda表达式赋值给一个局部变量或者传递给一个方法作为参数,局部变量或方法参数的类型就是Lambda表达式的目标类型。

看几段代码:

JButton button = new JButton();
//old写法
button.addActionListener(new ActionListener() {
    @Override
    public void actionPerformed(ActionEvent e) {
        System.out.println("button clicked");
    }
});
//第一层:Lambda表达式写法
button.addActionListener(e -> System.out.println("button clicked"));

第二层:这是addActionListener()方法及签名
/**
 * Adds an <code>ActionListener</code> to the button.
 * @param l the <code>ActionListener</code> to be added
 */
public void addActionListener(ActionListener l) {
    listenerList.add(ActionListener.class, l);
}

第三层:这是接口ActionListener
public interface ActionListener extends EventListener {
    /**
     * Invoked when an action occurs.
     */
    public void actionPerformed(ActionEvent e);

}

这里我标了3层,一层一层看,

  • addActionListener(ActionListener l)方法接收一个ActionListener类型参数
  • ActionListener接口**有且只有一个方法**actionPerformed(ActionEvent e)
  • actionPerformed(ActionEvent e)有个参数,类型是ActionEvent
    第一层声明Lambda表达式的时候,就是这一段代码:”e -> System.out.println(“button clicked”)”,可以断定,编译器通过上下文推断出:这里需要一个ActionListener()类型的匿名内部类,所以就把old写法简化成了这一行代码,这个接口唯一的方法接收的类型也可以知道就是ActionEvent,所以参数类型也省略不写了。编译成class文件的时候会加上。

这里就牵扯出另一个概念–函数式接口,有没有发现一个共同点,文中提到的Runnable接口、ActionListener接口都只有一个方法(或许是这样才能直接推断出参数的类型,如果有多个方法那不是不好确定是哪个方法,什么参数类型了)。
* - 可用作Lambda表达式类型的接口都有且只有一个方法。*
查看新版本(1.8开始)Runnable接口源码会发现多了一个注解:@FunctionalInterface,查看此注解源码,里面有一段注释这样说:

/**
*
* <p>Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
* 翻译过来大概就是:可以创建功能接口的实例。lambda表达式、方法引用或构造函数引用。
* @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 {}

最后提一个点,Lambda表达式中需要引用它所在方法里的变量时,变量只能是final或者既成事实上的final变量。换句话说,Lambda表达式引用的是值,是在使用赋给该变量的一个特定的值。

String name = "Mistra";
name = "A";
Runnable runnable2 = () -> {
    System.out.println("runnable1" + name);
};

这样写的话 System.out.println(“runnable1” + name);处会报错:Variable used in lambda expression should be final or effectively final
如果试图多次改变变量的值,并在Lambda中引用它就会报错
什么是既成事实上的final变量?比如说这里name我就声明并赋值了,没有第二次改变它的值,它的值就是原始值并不会改变了,就是既成事实上的final变量。在这里值不会改变了,相当于跟用final修饰是一样的了。


最后贴代码,Lambda表达式的常见用法:

package mistra.com.jdk8.lambda;


import javax.swing.*;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.Arrays;
import java.util.List;
import java.util.function.BinaryOperator;

/**
 * @Author: Mistra.WangRui
 * @Time 2018-09-01 15:04
 * @Description: Lambda表达式的常见用法
 **/
public class LambdaTest1 {

    public static void main(String[] args) {
        /**
         * 1.遍历集合
         */
        String[] strings = {"A", "B", "C", "D", "E"};
        List<String> names = Arrays.asList(strings);
        //"->"符号的前面就是参数,后面就是代码块,没有参数时前面就写空括号(),
        // 代码块跟普通方法的代码块一样可以用返回或者异常来退出
        names.forEach(name -> {
            System.out.println(name);
        });
        //Java8双冒号操作符
        names.forEach(System.out::println);

        /**
         * 2.代替匿名内部类
         */
        JButton button = new JButton();
        //old
        button.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("button clicked");
            }
        });
        //lambda  只有一个参数时可以省略括号,代码块只有一行时可以不写大括号{}
        button.addActionListener(e -> System.out.println("button clicked"));
        //old
        Thread thread1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread1");
            }
        });
        thread1.start();
        //lambda  没有参数,所以是空括号()
        Thread thread2 = new Thread(() -> {
            System.out.println("thread2");
        });
        thread2.start();

        Runnable runnable1 = () -> {
            System.out.println("runnable1");
        };
        runnable1.run();

        /**
         * 3.创建函数 变量add不是两个数字之和,而是将两个数字相加的那行代码
         */
        BinaryOperator<Long> add = (x, y) -> x + y + 1;
        Long result = add.apply(5l,5l);
        System.out.println(result);//result=11

        String name = "Mistra";
        //name = "A";
        Runnable runnable2 = () -> {
            System.out.println("runnable1" + name);
        };
        runnable1.run();
    }

}

这里写图片描述

猜你喜欢

转载自blog.csdn.net/Axela30W/article/details/82288555