java特性回顾——Lambda表达式

java特性回顾——Lambda表达式

目前oracle已经推出了java10.0.1,然而java8的很多优秀特性却没有被有效的利用起来。我也是最近看hibernate最新的文档的时候,看到了Lambda表达式的写法,感觉比较新奇,于是就稍微研究了一下,这里我就把我的研究结果分享给大家。

一、概述

Lambda这个词源于数学上的λ演算,在程序里,指的是匿名函数,也就是没有名称的函数。为了方便理解匿名函数,我举一个JavaScript的例子,因为在js中比较好理解。

//定义一个函数
function test(a){
    if(a != null && typeof a === 'function'){
        a("call");
    }
}
//调用函数
test(function(text){
    console.log(text);
});

在调用过程中传入的那个函数就是一个匿名函数。java8开始可以在java代码中实现上面的js代码,如下:

package com.sv.lambda;

import java.util.function.Consumer;

/**
 * Lambda测试类
 * @author 银发Victorique
 * @date 2018年6月13日
 */
public class Main {

    /**
     * 定义一个函数
     */
    public void test(Consumer<String> a) {
        a.accept("call");
    }

    /**
     * 调用函数
     */
    public static void main(String[] args) {
        new Main().test((text) -> {
            System.out.println(text);
        });
    }

}

由于java不是动态类型(弱类型)的编程语言,因此需要定义一个匿名函数类型,上面的例子用的就是Consumer这个接口,传入的泛型是函数参数的类型,需要注意的是只能有一个参数。传入的匿名函数使用格式如下:

 (入参1, 入参2,...)  ->{ 函数体 }

这时你肯定会有一个疑问,匿名函数可以传很多的参数,但是函数类型却只能传一个参数,这样是不是很不灵活?先别急,我们先来看看几个使用Lambda的例子,再来讨论这个问题。

二、使用Lambda的例子

1. forEach循环

List<String> list = new ArrayList<>();
//传统写法
for(String str : list){
    System.out.println(str);
}
//Lambda等价写法
list.forEach((str) -> {
    System.out.println(str);
});

2.Runnable接口

//传统写法
new Thread(new Runnable() {
    @Override
    public void run() {
        System.out.println("start thread");
    }
}).start();
//Lambda等价写法
new Thread(() -> {
    System.out.println("start thread");
}).start();

上面的第一个例子比较好理解,forEach的形参是

Consumer<? super T> action

所以直接传匿名函数就可以,但第二个形参是

Runnable target

那为什么可以直接传匿名函数呢?我们先来看看Runnable接口的源码:

@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();
}

再来看一下Consumer的源码:

@FunctionalInterface
public interface Consumer<T> {

    /**
     * Performs this operation on the given argument.
     *
     * @param t the input argument
     */
    void accept(T t);

    /**
     * Returns a composed {@code Consumer} that performs, in sequence, this
     * operation followed by the {@code after} operation. If performing either
     * operation throws an exception, it is relayed to the caller of the
     * composed operation.  If performing this operation throws an exception,
     * the {@code after} operation will not be performed.
     *
     * @param after the operation to perform after this operation
     * @return a composed {@code Consumer} that performs in sequence this
     * operation followed by the {@code after} operation
     * @throws NullPointerException if {@code after} is null
     */
    default Consumer<T> andThen(Consumer<? super T> after) {
        Objects.requireNonNull(after);
        return (T t) -> { accept(t); after.accept(t); };
    }
}

这两个接口都有一个共同特点,就是使用了@FunctionalInterface注解,没错这个注解就是声明接口时匿名函数类,但这个类有一定要求的。首先接口中有且只有一个抽象方法,这样在传入匿名函数时,默认就是这个抽象方法的实现,也就是说入参和返回值和这个抽象方法相同。另外,java8的另外一个特性,就是在接口中可以定义默认方法实现,这也是一个很大的突破,正如Consumer源码中使用default关键字定义的方法,就是默认方法实现,在实现该接口的类中,可以实现或不实现该方法。而这种方法在@FunctionalInterface注解声明的接口中是可以存在的。
因此,可以利用@FunctionalInterface注解,定义自己的匿名函数类,从而突破参数传递数量的限制。

/**
 * 匿名函数接口测试(当然可以不使用泛型)
 * @author 银发Victorique
 * @date 2018年6月13日
 * @param <P1>  参数1类型
 * @param <P2>  参数2类型
 * @param <P3>  参数3类型
 * @param <R>   返回类型
 */
@FunctionalInterface
public interface TestInterface<P1, P2, P3, R> {

     public R test(P1 x, P2 y, P3 z);

     default public void defaultFunction() {
        System.out.println("default"); 
     }
}
/**
 * 定义一个函数
 */
public String test(TestInterface<String, String, String, String> a) {
    return a.test("p1", "p2", "p3");
}

/**
 1. 调用函数
 */
public static void main(String[] args) {
    System.out.println(new Main().test((p1, p2, p3) -> {
        System.out.println(p1+"\t"+p2+"\t"+p3);
        return p1+p2+p3;
    }));
}

三、关于Lambda的其他说明

  1. 预设匿名函数类
    在java.util.function中有很多预设的匿名函数接口,之前使用的Consumer也在这个包下,一般情况下这些预设接口应该就可以满足要求,如果没有符合的,那就用上面提到的@FunctionalInterface注解进行自定义。
  2. ::符号
    使用这个符号,可以拿到一个实例或类中方法的句柄,上面最后的例子可以改写为:
/**
 * 定义一个函数
 */
public String test(TestInterface<String, String, String, String> a) {
    return a.test("p1", "p2", "p3");
}

/**
 * TestInterface接口方法实现
 */
public String testFunction(String p1, String p2, String p3) {
    System.out.println(p1+"\t"+p2+"\t"+p3);
    return p1+p2+p3;
}

/**
 * 调用函数
 */
public static void main(String[] args) {
    Main main = new Main();
    System.out.println(main.test(main::testFunction));;
}

四、题外话

以下内容纯属笔者胡言乱语,如有不适,请自行离开

Lambda的这个改动,实现了传递函数的代码语法,从而可以实现类似JavaScript中的闭包。而且在java10中,对于局部变量可以使用var来进行声明。虽然在java中这些都是语法糖,到虚拟机编译时与之前没有什么变化,但是对于编程者来说,使用这种语法糖,可以极大的简化代码,而且在思维上也有了很多扩展。而且这一向JavaScript借鉴和模仿的举动,侧面反映出,JavaScript中的很多特性是指的学习的。个人认为js最大的缺点就是非编译语言,执行效率直接取决于代码编写,但在java中就完全不存在这个问题。
编程也许就是这样,不同的编程语言之间其实,并没有明显的界限。互相可以借鉴优秀的编程思想,即使只是语法糖,但对于开发思想来说也是一个飞跃。更何况,无论使用的是何种编程语言,最后不是都会变成汇编语言,不如说,所有的高级汇编语言都是语法糖。

路漫漫系其修远兮,吾将上下而求索。

端午快到了,正好把屈原的这句话献给程序届以及我们这些程序员,愿程序不断发展,芝麻开花!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值