作为jdk1.8的新特性,8的标准库都开始大量使用lambda表达式了,你有什么理由不去学习lambda,这么简洁,这么爽的一种编程方法,不学不觉得可惜吗?
lambda即λ,是匿名函数的意思,在java中,lambda表达式本质上是定义一个匿名内部类而已。即定一个一匿名函数的同时,定义一个匿名内部类。
匿名内部类,可以对接口和抽象类进行使用,直接生成其子类或实现类对象。
现有一个接口,其内有一个抽象方法。
public interface Ability {
void doAbility(String token);
}
当我们想用匿名内部类的时候,即可以不通过定义子类或实现类,直接获得子类对象或实现类对象,如下:
public class LOLHero {
public static void main(String[] args) {
Ability ability = new Ability(){
@Override
public void doAbility(String token) {
System.out.println("我只是一个栗子");
}
};
}
}
哈哈,下面用lambda实现同样的效果,这个例子就可以看到lambda的本质了,颤抖吧,小伙伴们:
public class LOLHero {
public static void main(String[] args) {
Ability ability2 = (token)->{System.out.println("我也只是一个栗子");};
}
}
是的,相信你已经看出来了,lambda表达式表达的就是一个匿名内部类,不过不是通过new一个接口或抽象类,而是直接定义里面方法,对,就是匿名方法,来表达一个匿名内部类。所以前面说,lambda表达式就是一种使用匿名内部类的简化方法。不同点在于,匿名内部类使用范围必须是接口和抽象类,且接口和抽象类内可以有n个抽象方法+n个具体方法,但是lambda表达式适用范围仅仅是接口,且接口内只能有1个抽象方法+n个具体方法。没错,这个抽象方法就是你定义的匿名函数。
lambda表达式的使用,是java通过对于上下文语境进行推断,得到接口类型,若发现接口只有一个抽象方法,就可以使用lambda表达式。
第一个场景,形参位置:
public class LOLHero {
public static void main(String[] args) {
specialFuntion((token)->{
System.out.println(token+":万箭齐发。。。。。。");
});
}
public static void specialFuntion(Ability ability){
ability.doAbility("寒冰射手");
}
}
java可以根据形参的数据类型来推断lambda表达式。在main方法中,我们调用specialFunction方法的时候,要传入一个匿名内部类,由于机智的你发现这个接口只有一个抽象方法,因此我们直接用lambda表达式,直接定义匿名函数,就可以拿到这个匿名内部类,至于接口名,接口抽象方法的形参类型,java都可以推断出来,你就不用填写啦。嗯。。。。也许你会说是,语法糖。。。。嘛。是的,但是像是魔法一样有趣。
运行结果:
寒冰射手:万箭齐发。。。。。。
第二个场景,返回值位置:
public class LOLHero {
public static void main(String[] args) {
Ability ability1 = getAbility();
}
public static Ability getAbility(){
return (token)->{
System.out.println(token+":万箭齐发。。。。。。。。");
};
}
}
这种情况下,java会根据返回值的数据类型来推断lambda表达式。道理是一样的,java知道要返回一个Ability接口,而该接口仅有一个抽象方法,因此在这里机智的你可以使用lambda表达式来代表一个匿名内部类。
线程启动
public class LOLHero {
public static void main(String[] args) {
ScheduledExecutorService es = Executors.newScheduledThreadPool(2);
es.scheduleAtFixedRate(()->{
System.out.println("我是栗子!");
},0,2,TimeUnit.SECONDS);
}
}
第一个参数是Rnnable接口,这个接口只有一个抽象方法,因此此处可以使用lambda表达式。
流对象之遍历
机智的你们都知道,在实际使用集合时,最常用的时遍历迭代每一个元素,对每一个元素进行操作。而使用集合的流对象+lambda表达式,可以很方便的做到这一点。
public class LOLHero {
public static void main(String[] args) {
List heros = new ArrayList<>(Arrays.asList("爱希","蛮王","易大师","皇子","皇子","皇子","我"));
heros.stream().forEach(x-> System.out.println(x));
}
}
该处,forEach方法中形参是Consumer接口,他有唯一的抽象方法accept(T t),此处lambada表达式就是一个实现了Consumer接口的匿名内部类。遍历结果:
爱希
蛮王
易大师
皇子
皇子
皇子
我
机智的你,想过滤重复的数据,和筛选一定条件的数据,如下:
public class LOLHero {
public static void main(String[] args) {
List heros = new ArrayList<>(Arrays.asList("爱希","蛮王","易大师","皇子","皇子","皇子","我"));
heros.stream().filter(n->n.length() > 1).distinct().forEach(n-> System.out.println(n));
}
}
filter方法的形参是Predicate接口,有唯一的抽象方法test(T t),用于筛选长度>1的元素,distinct()进行过滤重复元素,遍历结果如下:
爱希
蛮王
易大师
皇子
流对象之函数式编程
函数式编程的核心是map进行元素转换,reduce进行元素聚合,如下:
public class LOLHero {
public static void main(String[] args) {
List heros = new ArrayList<>(Arrays.asList("爱希","蛮王","易大师","皇子","皇子","皇子","我"));
Integer reduce = heros.stream().map((x) -> x.length()).reduce(0, (sum, x) -> sum + x);
System.out.println(reduce);
}
}
map方法的形参是Function接口,其有唯一的抽象方法apply(T t),reduce方法的第一个参数是初始值,第二个参数为接口BinaryOperator,其有唯一抽象方法apply(T t,U u),reduce方法会把前面元素的计算结果放在sum位置(第一次放初始值),把下一个元素放在x位置进行聚合操作。该例子中,map将每个元素转化成其长度,用reduce获取其总长度,结果如下:
14
下面还有几个用函数编程的例子,大家可以自行探索:
public class LOLHero {
public static void main(String[] args) {
List heros = new ArrayList<>(Arrays.asList("爱希","蛮王","易大师","皇子","皇子","皇子","我"));
//按一定格式将集合转化成字符串
String s = heros.stream().map(x->x+1).collect(Collectors.joining(","));
//将集合转化后并获取
List list = heros.stream().map(x -> x + x).collect(Collectors.toList());
//获取int,long,double的统计对象,可以获取最大值,最小值,平均值,总和等等,十分方便
IntSummaryStatistics intSummaryStatistics = heros.stream().map(x -> x.length()).mapToInt(x -> x).summaryStatistics();
}
}
说了这么多,其实lambda的本质,还是像开头说的那样,定义一个匿名内部类而已,只要抓住这个关键,使用lambda便是游刃有余啊。
java8新增四大核心函数接口(函数接口,即只有一个抽象函数的接口):
Consumer : 消费型接口(有进无出)
void accept(T t);
Supplier : 供给型接口(无进有出)
T get();
Function : 函数型接口(有进有出)
R apply(T t);
Predicate : 断言型接口(有进有出)
boolean test(T t);