JDK8新特性01:Lambda表达式与函数式接口

Lambda表达式

为什么需要Lambda表达式

在Java8之前,我们无法将函数作为参数传递给一个方法,也无法声明返回一个函数的方法.这与Javascript等函数式语言大为不同.

在很多时候,我们想要传递的是行为,而非数据,这就需要Lambda表达式了,下面以两个Lambda表达式典型应用场景为例,演示Lambda表达式的使用.

Lambda表达式使用场景1:代替匿名内部类

在GUI编程中大量使用匿名内部类,他们用来定义事件回调的行为,这是一种典型的传递行为而非传递数据的情况,因此可以使用Lambda表达式.

  1. 在Java8之前,要定义事件的回调函数,必须向事件传递一个匿名内部类,在该匿名内部类中定义事件的回调行为.

    public static void main(String[] args) {
        JFrame jFrame = new JFrame("My JFrame");
        JButton jButton = new JButton("My JButton");
    
        jButton.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent e) {
                System.out.println("Button Pressed");
                System.out.println("some other procedure");
            }
        });
    
        jFrame.add(jButton);
        jFrame.setVisible(true);
    }
    
  2. 在Java8之后,可以使用Lambda表达式定义接口中需要实现的方法,用以替代匿名内部类

    public static void main(String[] args) {
        JFrame jFrame = new JFrame("My JFrame");
        JButton jButton = new JButton("My JButton");
    
        jButton.addActionListener((ActionEvent event) -> {
               System.out.println("Button Pressed");
               System.out.println("some other procedure");
        });
    
        jFrame.add(jButton);
        jFrame.setVisible(true);
    }
    

Lambda表达式使用场景2:集合操作

使用Java8中Collection类新增的forEach()方法,配合Lambda表达式,可以极大方便集合的遍历操作

  1. 在Java5之前,只能使用for循环来遍历集合:

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
    
    for (int i = 0; i < list.size(); i++) {
        System.out.println(list.get(i));
    }
    
  2. Java5引入了增强for循环,本质上是调用了迭代器进行迭代:

    List<Integer> list = Arrays.asList(1, 2, 3, 4, 5, 6, 7, 8);
    
    for (Integer i : list) {
        System.out.println(list.get(i));
    }
    
  3. 使用Java8中Collection类新增的forEach()方法遍历集合更为简洁:

    list.forEach(new Consumer<Integer>() {
        @Override
        public void accept(Integer i) {
            System.out.println(i);
        }
    });
    

    forEach()方法接收一个Consumer<T>对象,其accept()方法指定了遍历集合时对元素进行的行为,当然,可以用Lambda表达式代替上面的匿名内部类.

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

上面三种遍历集合的方法都属于外部迭代,使用JDK8新增的流Stream对集合进行内部迭代的效率更高.

Lambda表达式的语法

Java中的Lambda表达式语法如下:

(argument) -> {body}

其中argument部分为函数参数,规定如下:

  • 一个Lambda表达式可以有零个或多个参数.所有的参数必须包含在圆括号内,参数之间用逗号间隔,例如:(a, b)(int a, int b)(String a, float b).
  • 参数的类型既可以显式声明,也可以根据上下文来自动推断,例如:(int i)(i)的效果相同,为保证可读性,可以显式声明类型.当只有一个参数,且参数类型可推导时,圆括号()可省略,例如a -> retuan a*a
  • 空圆括号代表参数集为空,例如:() -> 42.

其中body部分为表达式的主体,规定如下:

  • 表达式的主体可以包含零条或多条语句
  • 如果表达式的主体只有一条语句,花括号{}return关键字都可以省略,匿名函数的返回值即为该主体表达式计算结果.(expression lambda)
  • 如果Lambda表达式的主体包含一条以上语句,则表达式必须包含在{}代码块中,匿名函数的返回值必须由return语句显式指定,若没有return语句则返回值为空.(statement lambda)

函数式接口

上边例子中遍历集合的forEach()方法接收一个Consumer<T>接口的实现类,观察Consumer接口的源代码,发现其只有一个抽象方法accept(),该方法定义了对遍历得到元素的行为.同时我们注意到,Consumer接口被加以@FunctionalInterface注解,该注解标识该接口为函数式接口.

函数式接口:有且只有一个抽象方法的接口

阅读@FunctionalInterface注解的文档内容如下:

Conceptually, a functional interface has exactly one abstract method. Since default methods have an implementation, they are not abstract. If an interface declares an abstract method overriding one of the public methods of java.lang.Object, that also does not count toward the interface’s abstract method count since any implementation of the interface will have an implementation from java.lang.Object or elsewhere.

函数式接口是指那些有且只有一个精确的抽象方法的接口.default方法不被认为是抽象方法;重新声明java.lang.Object类中的方法为抽象方法也并不被认为是该类的抽象方法,因为这些方法总是会在Object类中或其他地方被实现.

Note that instances of functional interfaces can be created with lambda expressions, method references, or constructor references.

函数式接口的实例可以通过:Lambda表达式,方法引用构造方法引用被创建.

总结成一句话,即:

函数式接口为有且只有一个精确抽象方法的接口

从Java8开始,很多符合函数式接口定义的接口(如:Runnable,Comparator等)开始被声明为函数式接口.

Lambda表达式的数据类型:函数式接口的实现类

在javascript等函数式编程语言中,函数作为’一等公民’,是可以由专门的函数类型变量的,然而对于Java来说,并不存在函数类型,所以Lambda表达式实际上就是函数式接口的实现类对象.

下面测试程序证明了这一点

@FunctionalInterface	// 一个函数式接口,只有一个精确的抽象方法
interface MyFunctionalInterface {

    // 一个自定义的精确抽象方法
    void myAbstractMethod();

    // 一个覆盖了Object对象的抽象方法
    String toString();
}

public class Test {
    public static void main(String[] args) {

        // 使用Lambda表达式定义该函数式接口的一个实例
        MyFunctionalInterface aLambdaExpression = () -> {
            System.out.println("hello");
        };

        System.out.println(myFunctionalInterface.getClass());
        System.out.println(myFunctionalInterface.getClass().getSuperclass());
        System.out.println(myFunctionalInterface.getClass().getInterfaces()[0]);
    }
}

程序输出如下,可以证明该Lambda表达式实际上是函数式接口myFunctionalInterface的一个实现类

class Test$$Lambda$14/0x0000000801204840
class java.lang.Object
interface MyFunctionalInterface
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值