lambda表达式详解

209 阅读7分钟

lambda 表达式以及方法引用

1.浅谈lambda表达式

最近在学习lamdba的时候遇到了一些问题,在此记录,如有不当,希望大家指正;

lambda是java8的新特性,皆在解决匿名内部类书写繁琐的问题; 由此我们知道,lambda一定是一个匿名内部类,它的特点是它必须是实现一个接口中的一个抽象方法,必须有且仅有一个抽象方法;我们知道所有的类都是Object的子类,所以如果该接口中有任何object类中的同名方法,都不算做是该接口私有的方法;java8的新特性中还有方法的具体实现,用关键字default和static作为限定符,当然这两种实现也不能算作该接口的私有抽象方法,举例如下

/**
 * 判断是否是函数式接口
 */
@FunctionalInterface
public interface InterfaceTest {

    int sout(int a);

    int hashCode();

    default void test(){
        System.out.println("我是default方法");
    }
    static void staticTest(){
        System.out.println("我是static方法");
    }

}

如何判断一个接口是否是函数式接口,可以通过@FunctionalInterface 来判断, 新增的一个函数式接口包, 凡是在这个包下的接口都是lamdba表达式的接口,即函数式接口

java.util.function;

另外,lambda表达式规定函数式接口不能有throws 声明,具体还不知道,如有知道的大佬可以留言告知

2.如何书写lambda表达式

lamdba表达式由 () -> {} 组成,其中() 是函数式接口中方法的(),-> 箭头指向实现;{} 括号指具体实现;个人建议先理解匿名内部类,这样对理解lambda表达式有很大的帮助,这里不细讲;看一个具体的例子,接口定义如下

int sout(int a);

返回值 int类型, 方法名称 sout ,参数 int a; 对应的lambda 表达式如下

InterfaceTest test = (int x)->{
    System.out.println("我是lamdba表达式");
    return 1;
};

便于理解,这里给出匿名内部类的实现

// 匿名内部类
InterfaceTest tt = new InterfaceTest() {
    @Override
    public int sout(int a) {
        return 0;
    }
};

对比可以发现第一种实现更简洁,都传递了int 类型的参数

3.lambda的简写

lambda还有很多的简写 如下的列子就可以写成

InterfaceTest test1 = x-> Math.abs(x);

以“->”分割成两部分来看,箭头前由(int x) 简写成了 x,去掉了()和参数类型int,箭头后半部分由{xxx};变成了Math.abs(x);这里重点讲一下后半部分,如果lambda的表达式实现只有一行代码,那么可以省掉 “{}”,其次如果该函数式接口的抽象方法有返回值,那么需要加上 return ,如果是一行代码则return也可以省掉,**当然这一行代码必须的有正确的返回!!

总结了几点如下

// 一个参数省去参数类型和括号,多个参数不可以省掉括号
// 当大括号里只有一条语句时候可以去掉大括号
// 当有返回值时且只有一个return可以省略掉,同时去掉大括号

如果一个函数式接口中有多个参数时候则可以省掉参数类型如下

@FunctionalInterface
interface test<T>{
    int m(T t,String sub,int x);
}

lambda如下

test<String> t51 = ( str, sub, x)->{
    String s = new String(str);
    int i = s.indexOf(sub, x);
    return i;
};

这里的泛型不需要关注

4.什么是方法引用

简单来说就是使用了"::"符号书写的代码,具体看以下链接 blog.csdn.net/lkforce/art…

5.lambda 如何使用方法引用

这里讲 静态引用,构造器引用,实例引用,对象引用 讲解之前我们需要明白lambda的箭头前和箭头后的参数传递,在使用"::"的时候,我们的参数传递顺序不能乱 这里参数顺序不能乱是指对象和实例引用,其他的待验证 即如下,()括号里的参数是和函数式接口的参数对应这个不用多说,在编译器时候就会检查,但是在{}使用中我们是可以随意使用的,不用在意顺序;

test<String> t51 = ( str, sub, x)->{
    String s = new String(str);
    int i = s.indexOf(sub, x);
    return i;
};

静态引用

先看函数式接口:

int sout(int a);

普通的lambda表达式

InterfaceTest a = (int x) -> {
    int abs = Math.abs(x);
    return abs;
};

使用方法引用的表达式

InterfaceTest t = Math::abs;

abs是Math的静态方法,相当于普通的用法类名.方法名;前面省略掉了(int x),注意,大家查看源码可知abs有一个参数类型为int的参数;这里需要注意abs 省掉了参数;

构造器引用

函数式接口

int sout(int a);

普通的lambda表达式

InterfaceTest t2 = (int x) -> {
    Integer integer1 = new Integer(x);
    return integer1;
};

使用方法引用的lambda表达式

InterfaceTest t1 = Integer::new;

看起来更加简洁,大家知道在new 一个Integer 的时候是需要给一个int 类型的参数的,所以在这里new 后面省掉了参数,如果有多个参数的话,结合下面的对象方法引用,我认为参数是按顺序传递的,如果顺序错误将得出错误的结果,这一点待验证

实例引用

函数式接口如下

int sout(int a);

普通的lambda表达式如下

InterfaceTest t4 = (int x) -> {
    String s = new String("xx");
    int i = s.indexOf(x);
    return i;
};

第一种方法引用

InterfaceTest t3 = new String("第一种方法引用")::indexOf;

第二种方法引用

String xx = new String("xx");
InterfaceTest t3 = xx::indexOf;

第一种和第二种方法引用的区别是将new的对象放在了外面,为什么能这么做呢,因为上面讲过了,lambda也是匿名内部类,那么匿名内部类是能够访问所在类中的方法和属性的; 注意这里的indexOf 方法不是一个静态的方法,所以我们使用了实例对象的方法引用,参数传递顺序不能乱,这里举例说明为什么不能乱,如果函数式接口是int sout(int start,String str,String sub),而String.indexOf("字符串",x)方法是这样的,那么我们的参数传递将出现问题

对象方法引用

首先我们思考一个问题,在上面实例方法引用中,indexOf方法是要返回一个字符的下标,那么我们这里的new String("xx") 中的xx如何不写死呢?通常做法如下,将s传入上面的new String()的构造器中;

String s = "xx";

如果用lambda表达式来解决问题的话如下

test<String> t5 = String::indexOf;

所使用的函数式接口,注意看这里有三个参数,为了解决这一问题,我们增加一个参数str,在这里的str参数是泛型T,也可以不加泛型T,主要看函数接口如何书写的

@FunctionalInterface
interface test<T>{
    int m(T t,String sub,int x);
}

正常写法如下,对字符串str 取字符串sub的下标,从位置x(int类型)开始

test<String> t51 = ( str, sub, x)->{
    String s = new String(str);
    int i = s.indexOf(sub, x);
    return i;
};

这里Stirng必须是一个声明的类名称,为什么必须要声明呢,因为泛型T ,在lambda推算过程中必须知道该类是什么类型,这里我们先分析一下String.indexOf,源码如下

public int indexOf(String str, int fromIndex) {
    return indexOf(value, 0, value.length,
            str.value, 0, str.value.length, fromIndex);
}

在indexOf 中有两个参数,第一个str是子字符串,fromIndex 是下标的位置 返回一个int类型的下标值 看lambda中String::indexOf 并没有写参数,更没有参数的位置,由此我们知道,lambda的参数顺序是不能随意书写,::符号之前是类名称,之后是方法名称,lambda在中间起到了参数传递的作用

6.结论

lambda 优点,解决了匿名内部类繁琐的写法,缺点,不易维护和排查问题