Lambda表达式是一个匿名函数,也可以理解为闭包。在Java8之前的版本中,Java是作为面向对象式编程,但是在Java8之后引入了函数式接口。因为Java不像JavaScript那种将函数列为一级的语言,所以采用了函数式接口依附的形式实现函数,而其中Lambda表达式作为重要的匿名函数便是其中的核心。
以遍历为例,JDK1.4中采用的是外置的迭代器进行遍历,通过迭代器对集合中的数据进行一个一个的访问及操作
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
for (int i = 0; i < list.size() ; i++) {
System.out.print(list.get(i)+"\t");
}
JDK1.5中增加了增强遍历,也就是forEach,使得迭代器看起来更加便捷
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
for (Integer i : list) {
System.out.print(i+"\t");
}
当然,以上作为我们熟悉的迭代方式,应用的应该也很多,在实际业务开发中,时长会因为业务处理的繁琐,for循环的叠加,导致整个代码看起来都特别的繁重。
首先是最初的实现,JDK1.8添加了一个名为Consumer的消费型函数式接口,而通过重写它的accept接口达到了遍历。
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
list.forEach(new Consumer<Integer>() {
//Consumer的抽象接口accept定义为接收参数不返回结果。
@Override
public void accept(Integer integer) {
System.out.print(integer+"\t");
}
});
而Consumer接口作为函数式接口可以直接使用Lambda表达式替代
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
list.forEach(i -> {System.out.print(i+"\t");});
这样看起来就简洁多了。
函数式接口还提供了方法引用的方式对函数式接口实现的方式
List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9);
list.forEach(System.out::print);
因为Lambda表达式是匿名函数,所以它传入的方式可以如下:
//Lambda表达式会根据实现的类去推断出传入参数的类型,并不需要声明,所以是匿名函数
() -> {} //无参数是可以直接使用括号代替
a -> {} //传入单个参数可以不带括号
(String a) -> {} //完整的实现
跟进List包我们发现List继承了Collection包,Collection包又继承了Iterable包,forEach语法来源于JDK1.5的Iterable包下,所以List拥有了forEach方法。但是forEach是JDK1.8版本才引进的,需要注意这一点。
而且,我们发现forEach这个方法所在的位置Iterable包是一个接口,我们可以看到forEach接口的定义为default默认方法,添加版本为JDK1.8,通过默认方法可以在接口里面添加接口的功能,同时也保证了Java8之前功能可以正常使用。(也就是说JDK1.8版本之后接口中可以使用default语法对接口进行默认的实现)
通过JDK1.8添加的forEach接口我们可以得到很多Java8的新特性的了解。首先,我们来看一下forEach接口的完整实现。
/**
* Performs the given action for each element of the {@code Iterable}
* until all elements have been processed or the action throws an
* exception. Unless otherwise specified by the implementing class,
* actions are performed in the order of iteration (if an iteration order
* is specified). Exceptions thrown by the action are relayed to the
* caller.
*
* @implSpec
* The default implementation behaves as if:
*
{@code
* for (T t : this)
* action.accept(t);
* }
*
* @param action The action to be performed for each element
* @throws NullPointerException if the specified action is null
* @since 1.8
*/
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
forEach的说明告诉我们,forEach传入的是一种行为,它处理的是一个动作而不是数据。这也正是Lambda的操作,这是它处理的将是行为而不再是预定义行为来操作数据的转变。
而且在附加说明中告诉我们,forEach可以指定迭代方式,如果不指定则按照顺序执行。
在Java8中添加的函数式接口定义为:有且只能有一个抽象方法的接口称为函数式接口。
这个定义在Java8之前肯定是多余的,因为接口的方法只能是抽象方法,而在Java8之后则是有了静态方法(static)和默认方法(default)的实现。
函数式接口会通过注解@FunctionalInterface定义。在源码中,很多之前的接口也都修改为了函数式接口,如线程的Runnable接口就是其中之一。
根据Runnable接口分析,我们可以得知,函数式接口必须满足两个条件:
当然,需要注意的是:
定义一个测试接口
@FunctionalInterface
interface FunctionalInterfaceTest {
void test();
//异常实现
//String myString();
//非异常实现
String toString();
}
如上,我们自定义的接口myString()会因为函数式接口的定义报错,但是toString()接口却不会。原因是,函数式接口中的抽象方法实现是要经过java.lang.Object的,所以如toString()等经由Object的接口并不在函数式接口限制范围内。
四大核心函数接口的泛型定义
Consumer<T> //消费型接口,有参数,无返回值类型的接口。
Supplier<T> //供给型接口,只有返回值,没有参数
Function<T, R> //函数型接口,输入一个参数,返回一个值。
Predicate<T> //断言型接口,输入一个参数,返回boolean值。
//如forEach接口的定义
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
test.supplierTest(10,() -> 10)
public int supplierTest(int a, Supplier<Integer> supplier){
return a * supplier.get();
}
public int compute(int a, Function<Integer,Integer> function){
return function.apply(a);
}
public boolean filterInt(int a, Predicate<Integer> pre){
boolean bool = pre.test(a);
return bool;
}
BiFunction<T, U, R> //传入两个参数,返回一个值,抽象方法为:R apply(T t, U u);
BiConsumer<T, U> //传入两个参数,无返回值,抽象方法为:void accept(T t, U u);
ToIntFunction<T> //传入一个参数,返回int
ToLongFunction<T> //传入一个参数,返回long
ToDoubleFunction<T> //传入一个参数,返回double
IntFunction<R> //传入int,返回给定值
LongFunction<R> //传入long,返回给定值
DoubleFunction<R> //传入double,返回给定值