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 优点,解决了匿名内部类繁琐的写法,缺点,不易维护和排查问题