Iambda表达式有三个部分,
1)参数列表
2)箭头->
箭头把参数列表与Lambda主体分隔开。
3)Lambda主体
比较两个Apple的重量。表达式就是Lambda的返回值了。
Lambda的基本语法是
(parameters) -> expression
或(请注意语句的花括号)
(parameters) -> { statements; }
在哪里以及如何使用Lambda
可以在函数式接口上使用Lambda表达式。
在上面的代码中,你可以把Lambda表达式作为第二个参数传给filter方法,因为它这里需要Predicate
函数式接口
Predicate
因为Predicate中仅仅定义了一个抽象方法:
public interface Predicate<T> {
boolean test (T t) ;
}
注意:接口现在还可以拥有默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。
哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。
用函数式接口可以干什么呢?
Lambda表达式允许你直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例(具体说来,是函数式接口一个具体实现的实例)。
你用匿名内部类也可以完成同样的事情,只不过比较笨拙,需要提供一个实现,然后再直接内联将它实例化。
下图的代码是有效的,因为Runnable是-一个只定义了一个抽象方法run的函数式接口:
(2)中的实际写法为:
public Callable<String> fetch() {
return new Callable<String>() {
@Override
public String call() throws Exception {
return "Tricky example ;-)";
}
};
}
![$WL)Z5UY8SI[9HY@IA3IDF.png
函数描述符
函数式接口的抽象方法的签名基本上就是Lambda表达式的签名。
我们将这种抽象方法叫作函数描述符。
例如,Runnable接口可以看作一个什么也不接受什么也不返回( void)的函数的签名,因为它只有一个叫作run的抽象方法,这个方法什么也不接受,什么也不返回(void)。
可以用一个特殊表示法来描述Lambda和函数式接口的签名。
() -> void代表了参数列表为空,且返回void的函数,这正是Runnable接口所代表的。
(Apple,Apple) -> int 代表接受两个Apple作为参数且返回int的函数。
现在,只要知道Lambda表达式可以被赋给一个变量,或传递给一个接受函数式接口作为参数的方法就好了,当然这个Lambda表达式的签名要和函数式接口的抽象方法一样。
比如,在我们之前的例子里,你可以像下面这样直接把一个Lambda传给process方法:
public void process (Runnable r) {
r. run() ;
}
process(() -> System. out .println( "This is awesome! !") ) ;
Predicate
java.util. function. Predicate
在你需要表示一个涉及类型T的布尔表达式时,就可以使用这个接口。
@ FunctionalInterface
public interface Predicate<T>{
boolean test (T t) ;
}
Consumer
java .util. function. Consumer
你如果需要访问类型T的对象,并对其执行某些操作,就可以使用这个接口。
比如,你可以用它来创建一个forEach方法,接受一个Integers的列表,并对其中每个元素执行操作。
在下面的代码中,你就可以使用这个forEach方法,并配合Lambda来打印列表中的所有元素。
@FunctionalInterface
public interface Consumer<T> {
void accept (T t) ;
}
public static <T> void forEach(List<T> list, Consumer<T> c) {
for(T i: list) {
c. accept (i) ;
}
}
forEach ( Arrays.asList(1,2,3,4,5), (Integer i) -> System. out. println(i) );
Function
java.util. funct ion. Function
如果你需要定义一个Lambda,将输人对象的信息映射到输出,就可以使用这个接口(比如提取苹果的重量,或把字符串映射为它的长度)。
在下面的代码中,我们向你展示如何利用它来创建一个map方法,以将一个string列表映射到包含每个String长度的Integer列表。
@FunctionalInterface
public interface Function<T, R>{
R apply(T t) ;
}
public static <T,R> List<R> map(List<T> list, Function<T,R> f) {
List<R> result = new ArrayList<>() ;
for(T s: list) {
result .add(f.apply(s) ) ;
}
return result;
}
// [7, 2,6]
List<Integer> l = map (Arrays .asList( "lambdas", "in", "action") ,(String s) -> s. length( ));
原始类型特化
我们介绍了三个泛型函数式接口: Predicate
还有些函数式接口专为某些类型而设计。
Java类型要么是引用类型(比如Byte、Integer、 Object、 List ),要么是原始类型(比如int、double、 byte、char )。
但是泛型(比如Consumer
因此,在Java里有一个将原始类型转换为对应的引用类型的机制。
这个机制叫作装箱( boxing )。相反的操作,也就是将引用类型转换为对应的原始类型,叫作拆箱( unboxing )。
Java还有一个自动装箱机制来帮助程序员执行这一任务:装箱和拆箱操作是自动完成的。
但这在性能方面是要付出代价的。
装箱后的值本质上就是把原始类型包裹起来,并保存在堆里。
因此,装箱后的值需要更多的内存,并需要额外的内存搜索来获取被包裹的原始值。
Java 8为我们前面所说的函数式接口带来了一个专门的版本,以便在输人和输出都是原始类型时避免自动装箱的操作。
比如,在下面的代码中,使用IntPredicate就避免了对值1000进行装箱操作,但要是用Predicate
一般来说,针对专门的输人参数类型的函数式接口的名称都要加上对应的原始类型前缀,比如DoublePredicate、IntConsumer 、LongBinaryOperator、IntFunction等。
Function接口还有针对输出参数类型的变种:ToIntFunct ion
表3-2总结了Java API中提供的最常用的函数式接口及其函数描述符。
这只是一个起点,如果有需要,你可以自己设计一个。
请记住,(T,U) -> R的表达方式展示了应当如何思考一个函数描述符。
表的左侧代表了参数类型。这里它代表一个函数,具有两个参数,分别为泛型T和U,返回类型为R。
![%LTHY$6]X(033FRY~DJA{K.png