在JDK1.8的新特性中,引入了一个叫Lambda表达式的东西,或许有些小伙伴到现在都没有写过。它的核心理念就是“可以像对象一样传递匿名函数”。在本篇博文中,我会详细介绍Lambda的概念,同时介绍几个JDK1.8中新增加的一些类和关键性的注解,在后面会给出一些简单的Lambda的例子供大家交流。笔者目前整理的一些blog针对面试都是超高频出现的。大家可以点击链接:http://blog.csdn.net/u012403290
1、匿名内部类
在java中,继承一个父类或者实现一个接口的时候,为了书写简单,可以使用匿名内部类,比如说下面这段代码:
//一个接口,一个待实现的方法
package com.brickworkers;
/**
*
* @author Brickworker
* Date:2017年4月14日下午2:24:23
* 关于类LamdaInterface.java的描述:匿名内部类展示
* Copyright (c) 2017, brcikworker All Rights Reserved.
*/
@FunctionalInterface
public interface LamdaInterface {
void print();
}
//在类中采用静态内部类直接实现接口方法
package com.brickworkers;
public class LamdaImpl{
public static void main(String[] args) {
LamdaInterface lamdaInterface = new LamdaInterface() {//匿名内部类直接实现接口
@Override
public void print() {
System.err.println("helloworld");
}
};
lamdaInterface.print();
}
}
//输出:helloworld
上面这个例子中就是我们用匿名内部类实现了接口中的方法,其实还有更常见的,在我们新写一个线程的时候:
Thread t1 = new Thread(new Runnable() {//匿名内部类直接实现Runnable接口
@Override
public void run() {
System.out.println("t1 run");
}
});
t1.start();
通过匿名内部类实现接口相信大家有所了解了,那么我再继承中如何实现呢?比如说我要重写父类中的某个方法:
package com.brickworkers;
import java.util.ArrayList;
import java.util.List;
public class LamdaImpl{
public static void main(String[] args) {
List str = new ArrayList(){
@Override
public String toString() {
return "匿名内部类直接重写List的toString方法";
}
};
System.out.println(str.toString());
}
}
//输出:匿名内部类直接重写List的toString方法
这样一来,就不需要重写写一个类专门处理一些方法的重写了,匿名内部类能够很好的简化代码,也可以增强代码的可读性。
2、函数接口@FunctionalInterface
简单来说就是一个接口中只有唯一的一个函数。比如说我们常用的Runnable接口,就只存在一个待实现的函数,以下是Runnable的源码:
*/
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface Runnable
is used
* to create a thread, starting the thread causes the object's
* run
method to be called in that separately executing
* thread.
*
* The general contract of the method run
is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
同时,如果这个接口是函数接口,那么需要用注解标注@FunctionalInterface,能否使用Lambda表达式要看这个接口是否存在这个注解,这个注解中,JDK对它的解释是:
* Note that instances of functional interfaces can be created with
* lambda expressions, method references, or constructor references.
所以Runable这个接口是可以使用Lambda表达式的。
但是这里要说明一点,因为所有的类都继承Object类,所以如果是重写了toString等属于Object的方法并不属于接口中的一个函数。
3、Lambda表达式
“Lambda 表达式”(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。Lambda表达式可以表示闭包 -百度词条
之所以称之为匿名函数,因为
比如说它在表达x+y的时候是这么玩的:
(x,y) -> x+y;
再比如void函数:
() - > {system.out.println()}
具体不再详细介绍,看下面例子就可以了。
3、介绍几个JDK1.8中新的类和函数接口
①Function函数接口:主要是接收一个类型的参数,返回另外一个类型的结果,比如说入参是String,返回是Intger,那么就是一个类型转换的功能。
②Consumer函数接口:主要是接收一个类型参数,void方法,没有返回。
③Predicate函数接口:主要是接收一个类型参数,boolean方法,可以进行条件判断。
①我们可以用Lambda表达来写上面描述到的匿名内部类,比如说你现在要写一个线程,再也不用需要用上面用匿名内部类来表示了,你可以直接:
package com.brickworkers;
public class LamdaImpl{
public static void main(String[] args) {
Thread t1 = new Thread(() -> {System.out.println("t1 执行");});
t1.start();
}
}
//输出:t1 执行
你不需要去指定Runable接口,JVM会自己会根据上下文进行识别,是不是比原来方便了很多呢?然后我们对“() -> {System.out.println(“t1 执行”);}”这部分进行解析,()表示入参,这个表示没有入参; ->表示run方法,为什么表示run方法?因为函数接口中只会存在一个方法。鼠标放在这个上面会展示详细信息:
{System.out.println(“t1 执行”);}其实就是放在run方法中的需要执行的代码块。
②在JDK1.5中引入了增强for循环,称为for-each,在JDK1.8中引入更简便的迭代方式,就是用Lanbda来替代for-each遍历,请看下面的例子:
package com.brickworkers;
import java.util.ArrayList;
import java.util.List;
public class LamdaImpl{
public static void main(String[] args) {
List list = new ArrayList();
for (int i = 0; i < 10; i++) {
list.add(i+"");
}
System.out.print("for-each遍历:");
for (String string : list) {
System.out.print(string+" ");
}
System.out.print("Lanbda遍历:");
list.forEach(o -> {System.out.print(o+" ");});
}
}
//输出结果: for-each遍历:0 1 2 3 4 5 6 7 8 9 Lanbda遍历:0 1 2 3 4 5 6 7 8 9
其实是在Iterable接口中多了这个forEach的方法,它的底层其实还是for - each的迭代方式,以下是forEach的源码:
default void forEach(Consumer super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
之所以能使用Lambda表达式是因为入参是Consumer这个函数接口。
③在JDK1.8中还增加了Stream API,充分利用现代多核CPU,可以写出更加简洁的代码,这里我们不考虑多线程,我们就简单说说Lambda表达式在流中是怎么操作的。
比如说我们在在一个String的List中找到包含名叫“brickworker”的人,代码如下:
package com.brickworkers;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LamdaImpl{
public static void main(String[] args) {
List list = new ArrayList();
list.add("Amy");
list.add("Gary");
list.add("tom");
list.add("tom");
list.add("brickworker");
list.add("brickworker2");
list.add("brickworker3");
List brickworkers = list.stream().filter(s -> s.indexOf("brickworker") >-1).collect(Collectors.toList());
brickworkers.forEach(s -> System.out.println(s));
}
}
//输出结果
//brickworker
//brickworker2
//brickworker3
仔细观察上面代码,其实分为了几步:
a、list.stream()把list中的数据转化成一个流
b、filter把流中的数据中保留包含brickwork的数据,其他的数据删除
c、collect把流重写转换成一个List
在来一个更难的例子,比如说,在开发中发现有一个String的List,它主要存储了用户的年龄,我们希望能够拿出年龄在20岁以下的用户并单独存储起来,那么我们就可以通过流和Lambda的形式快速实现:
package com.brickworkers;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
public class LamdaImpl{
public static void main(String[] args) {
List list = new ArrayList();
list.add("13");
list.add("14");
list.add("15");
list.add("20");
list.add("33");
list.add("22");
list.add("45");
List ages = list.stream().map(s -> new Integer(s)).filter(s -> s < 20).collect(Collectors.toList());
ages.forEach(s -> System.out.println(s));
}
}
//输出结果:
//13
//14
//15
在这个过程中,在比较的过程中需要用int型进行比较,但是因为为是一个String的链表,我们需要先通过map的方法将他进行转型。在map的这个方法中,入参其实就是我们上面提到的Function函数接口,它支持入参一个类型,返回另外一个类型。以下是map的源码:
/**
* Returns a stream consisting of the results of applying the given
* function to the elements of this stream.
*
* This is an intermediate
* operation.
*
* @param The element type of the new stream
* @param mapper a non-interfering,
* stateless
* function to apply to each element
* @return the new stream
*/
Stream map(Function super T, ? extends R> mapper);//Function接口进行转型
当然了,在Collectors的静态方法中不仅仅只有toList,还可以toMap和toSet等等,具体使用要看具体的场景。还有很多简洁方便的使用方式,希望大家自己去探究。
关于Lambda就介绍这么多,个人意见:在日常的开发中,如果你没有见到过项目中有这样的东西,就不要用Lambda来写了,因为大多的程序员没有了解到这些东西,阅读代码会造成很大的困扰。同时,不常用肯定是有原因的,要么不方便,要么性能不好。但是面试的时候,可能面试官会考察你JDK版本的新特性哦。如果大家有什么问题可以在下方留言交流,共同学习进步。