zoukankan      html  css  js  c++  java
  • Java8(1)之Lambda表达式初步与函数式接口

    Lambda表达式初步

    介绍

    什么是Lambda表达式?

    在如 Lisp、Python、Ruby 编程语言中,Lambda 是一个用于表示匿名函数或闭包的运算符

    为何需要lambda表达式?

    • 在 Java 中,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法。
    • 在 JavaScript 中,函数参数是一个函数,返回值是另一个函数的情况是非常常见的;JavaScript 是一门非常典型的函数式语言。

    看如下匿名内部类实例:

     1 import javax.swing.*;
     2 import java.awt.event.ActionEvent;
     3 import java.awt.event.ActionListener;
     4 
     5 public class SwingTest {
     6     public static void main(String[] args) {
     7         JFrame jFrame = new JFrame("My JFrame");
     8         JButton jButton = new JButton("My Button");
     9         // 匿名内部类
    10         jButton.addActionListener(new ActionListener() {
    11             @Override
    12             public void actionPerformed(ActionEvent e) {
    13                 System.out.println("Button Pressed");
    14             }
    15         });
    16         jFrame.add(jButton);
    17         jFrame.pack();
    18         jFrame.setVisible(true);
    19         jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    20     }
    21 }
    例 1:

    如上代码很简单,通过第 10 行给第 8 行的按钮注册了一个监听器。而我们知道,当事件触发真正起作用的实际上是执行第 12 行的 actionPerformed 方法,为此我们创建了 ActionListener 实例。

    Lambda 表达式可以让我们只专注于方法的实现,在注册监听器时我们可以忽略具体需要创建哪个类型的实例。其实在 Idea 中本身就已经给我们提示了,如下:

    即匿名内部类的写法是可以用 Lambda 表达式替换的,如下:

     1 import javax.swing.*;
     2 
     3 public class SwingTest {
     4     public static void main(String[] args) {
     5         JFrame jFrame = new JFrame("My JFrame");
     6         JButton jButton = new JButton("My Button");
     7         // Lambda 表达式
     8         jButton.addActionListener(event -> System.out.println("jButton Pressed"));
     9         jFrame.add(jButton);
    10         jFrame.pack();
    11         jFrame.setVisible(true);
    12         jFrame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    13     }
    14 }
    例 2:

    在例 2 中通过第 8 行的 Lambda 表达式替换了例 1 中 10-15 行的匿名内部类写法,不仅代码更简洁,而且含义更直观明了。

    基本结构

    在 Java 8 中 Lambda 表达式最原始的写法如下:

    (类型 prarm1,类型 param2,类型 param3, ...) -> {
    // 方法体
    }

    可以将参数类型省略,编译器可自动推断:

    (prarm1, param2, param3, ...) -> {
    // 方法体
    }

    如果只有一个参数,还可以省略小括号:

    prarm1 -> {
    // 方法体
    }

    如果方法体只有一行代码,那么大括号也可以省略:

    prarm1 -> <方法实现>

    用例 2 中的 Lambda 为例演变过程如下:

    // 原始写法
    jButton.addActionListener((ActionEvent event) -> {
        System.out.println("jButton Pressed");
    });
    // 省略参数类型
    jButton.addActionListener((event) -> {
        System.out.println("jButton Pressed");
    });
    // 省略小括号
    jButton.addActionListener(event -> {
        System.out.println("jButton Pressed");
    });
    // 省略大括号
    jButton.addActionListener(event -> System.out.println("jButton Pressed"));

    函数式接口

    介绍

    在 Java 8 中, Iterable 接口中新增了一个默认函数 forEach 让我们可以更方便的遍历它:

     1 package java.lang;
     2 
     3 import java.util.Iterator;
     4 import java.util.Objects;
     5 import java.util.Spliterator;
     6 import java.util.Spliterators;
     7 import java.util.function.Consumer;
     8 
     9 public interface Iterable<T> {
    10     Iterator<T> iterator();
    11 
    12     default void forEach(Consumer<? super T> action) {
    13         Objects.requireNonNull(action);
    14         for (T t : this) {
    15             action.accept(t);
    16         }
    17     }
    18 
    19     default Spliterator<T> spliterator() {
    20         return Spliterators.spliteratorUnknownSize(iterator(), 0);
    21     }
    22 }
    java.lang.Iterable

    可以看到它接收一个 java.util.function.Consumer 类型参数,查看 JavaDoc:

    该类上标注了一个 @FunctionalInterface 注解,并且注释中还说明了这个接口是一个函数式接口。查看该注解的 JavaDoc:

    可以获得如下信息:

    • 这是一个信息性注解,用于标注在一个接口上使一个接口成为函数式接口。
    • 从概念上来说,一个函数式接口有且只能有一个抽象方法。
    • 如果在接口中仅定义了一个抽象方法并且它覆盖了 java.lang.Object 类中公开的方法,那么这个接口就不是函数式接口。
    • 函数式接口的实例可以通过 Lambda 表达式、方法引用或构造方法引用来创建。
    • 如果被该注解标注的类型不满足下面两个条件,那么编译器将生成错误信息。
      1. 该类型是一个接口,不可以是注解类型、枚举、类。
      2. 被注解类型必须满足函数式注解的要求。
    • 如果一个接口满足了函数式接口的要求,但它没有标注该注解,编译器也会将它看作是函数式接口。

    Lambda表达式的作用

    看如下示例:

    package zze.java8;
    
    @FunctionalInterface
    interface MyInterface {
        void test();
    }
    
    public class Test2 {
        public void test(MyInterface myInterface) {
            System.out.println("Test2.test");
            myInterface.test();
        }
    
        public static void main(String[] args) {
            Test2 test2 = new Test2();
            test2.test(() ->System.out.println("MyInterface.test"));
            /*
             * Test2.test
             * MyInterface.test
             */
        }
    }

    可以看到, Test2 的实例方法 test 实际上是接收一个 MyInterface 对象,而 MyInterface 是一个函数式接口,所以我们可以通过传入 Lambda 表达式作为 myInterface 参数,即:此时传入的 Lambda 表达式就是对函数函数式接口 MyInterface 的实现对象。所以上面代码其实也可以修改为如下:

    Test2 test2 = new Test2();
    MyInterface myInterface = () -> System.out.println("MyInterface.test");
    test2.test(myInterface);
    System.out.println(myInterface.getClass());
    System.out.println(myInterface.getClass().getSuperclass());
    System.out.println(myInterface.getClass().getInterfaces()[0]);
    /*
    Test2.test
    MyInterface.test
    class zze.java8.Test2$$Lambda$1/990368553
    class java.lang.Object
    interface zze.java8.MyInterface
    */

    总结:

    • Lambda 表达式为 Java 添加了缺失的函数式编程特性,使我们能将函数当做一等公民看待。
    • 在将函数作为一等公民的语言中(如 Python、JavaScript),Lambda 表达式的类型是函数。但在 Java 中,Lambda 表达式是一个对象,它们必须依附于一类特别的对象类型——函数式接口(functional interface)
  • 相关阅读:
    鼠标事件大全
    jpa仓库接口
    mysql从一个表中拷贝数据到另一个表中sql语句
    mybatis中为sql中传值#{}和${}的区别
    @Autowired与@Resource的区别
    sping配置文件中引入properties文件方式
    Mybatis中实体类中的字段跟对应表的字段不一致时解决办法
    eclispe输入@注解时提示所有注解的设置
    eclipse中如何设置tomcat启动时间
    log4j的使用
  • 原文地址:https://www.cnblogs.com/zze46/p/10731769.html
Copyright © 2011-2022 走看看