这些是自己在看Java书籍时从书上摘抄的,主要是为了加强自己对知识点的记忆,同时也是对这个知识点不太懂所以记录一下,就是自己的学习笔记。
理解lambda表达式的Java实现,有两个结构十分关键:第一个是lambda表达式自身,第二个是函数式接口。
lambda表达式本质上就是一个匿名(即未命名)方法。但是,这个方法不是独立执行的,而是用于实现由函数式接口定义的另一个方法。因此,lambda表达式会产生一个匿名类。lambda表达式也常被称为闭包。
函数式接口是仅包含一个抽象方法的接口。这个方法指明了接口的目标用途。因此,函数式接口通常表示单个动作。例如:标准接口Runnable是一个函数式接口,因为它只定义了一个方法run(),run()定义了Runnable的动作。此外,接口式函数定义了lambda表达式的目标类型。特别注意:lambda表达式只能用于其目标类型已被指定的上下文中。
lambda表达式在Java语言中引入了一个新的语法元素和操作符。这个操作符是-> ,操作符被称为lambda操作符或者是箭头操作符。它将lambda表达式分成两个部分,左侧指定了lambda表达式需要的所有参数(如果不需要参数,则使用空的参数列表“()”),右侧指定了lambda体,即lambda表达式要执行的动作。
Java定义了两种lambda体:一种包含单独一个表达式,另一种包含一个代码块。
看一个简单的lambda表达式的例子,它的计算结果是一个常量值,如下所示:
() -> 123.45
这个lambda表达式没有参数,所以参数列表为空,它返回常量值123.45。因此,这个表达式的作用类似于下面的方法;
double myMath(){
return 123.45;
}
lambda表达式定义的方法没有名称
下面在看一个lambda表达式:
() -> Math.random() * 100
这个lambda表达式使用Math.random()获得一个随机数,将其乘以100,然后返回结果。这个lambda表达式也不需要参数。
当lambda表达式需要参数时,需要在操作符左侧的参数列表中加以指定,下面是一个简单的例子:
(n) -> (n % 2) == 0
如果参数n的值是偶数,这个lambda表达式会返回true。尽管可以显式指定参数的类型,例如本例中的n,但是通常不需要这么做,因为很多时候,参数的类型是可以从上下文中推断出来。与命名方法一样,lambda表达式可以指定需要用到的任意数量的参数。
函数式接口是仅指定了一个抽象方法的接口。从JDK8开始,可以为接口声明的方法指定默认行为(默认的方法实现),即所谓的默认方法,只有当没有指定默认实现时,接口的方法才是抽象方法。因为没有指定默认实现的接口方法隐式的是抽象方法,所以没有必要使用abstract修饰符。
下面是函数式接口的一个例子:
public interface MyNumber {
double getValue();
}
在本例中,getValue()方法隐式的是抽象方法,并且是MyNumber定义的唯一方法,所以MyNumber是一个函数式接口,其功能由getValue()定义。
lambda表达式不是独立执行的,而是构成了一个函数式接口定义的抽象方法的实现,该函数式接口定义了它的目标类型,只有在定义了lambda表达式的目标类型的上下文中,才能使用该表达式。当把一个lambda表达式赋给一个函数式接口引用时,就创建了这样的上下文。
下面通过一个例子来说明如何在参数上下文中使用lambda表达式。首先,声明对函数式接口MyNumber的一个引用
MyNumber myNum;
接下来,将一个lambda表达式赋给该接口引用:
myNum = () -> 123.45;
当目标类型上下文中出现lambda表达式时,会自动创建实现了函数式接口的一个类的实例,函数式接口声明的抽象方法的行为由lambda表达式定义。当通过目标调用该方法时,就会执行lambda表达式。因此,lambda表达式提供了一种将代码片段转换为对象的方法。
在前面的例子中,lambda表达式成了getValue()方法的实现,所以,下面的代码将显示123.45:
System.out.println(myNum.getValue());
//结果如下:
//123.45
因为赋给myNum的lambda表达式返回值为123.45,所以调用getValue()方法返回的值也是123.45
为了在目标类型上下文中使用lambda表达式,抽象方法的类型和lambda表达式的类型必须兼容。例如:如果抽象方法指定了两个int类型的参数,那么lambda表达式也必须指定两个参数,其类型要么被显式的指定为int类型,要么在上下文中可以隐式的推断为int类型。总的来说,lambda表达式的参数类型和数量必须与方法的参数兼容;返回类型必须兼容;并且lambda表达式可能抛出的异常必须能被方法接受。
package lambda;
interface NumericTest{
boolean test(int n);
}
public class LambdaDemo2 {
public static void main(String[] args) {
NumericTest isEven = (n) -> (n % 2) == 0;
if (isEven.test(10)){
System.out.println("10 是偶数");
}
if (!isEven.test(9)){
System.out.println("9不是偶数");
}
NumericTest isNonNeg = (n) -> n >= 0;
if (isNonNeg.test(1)){
System.out.println("1是正数");
}
if (!isNonNeg.test(-1)){
System.out.println("-1不是正数");
}
}
}
//结果:
//10 是偶数
//9不是偶数
//1是正数
//-1不是正数
这个程序演示了关于lambda表达式的一个重要的地方:函数式接口引用可以用来执行任何与其兼容的lambda表达式。注意,程序中定义了两个不同的lambda表达式,它们都是与函数式接口NumericTest的test()方法兼容,所以都可以通过NumericTest引用执行。
当lambda表达式中有多个参数时,如下所示:
(n,d) -> (n % d) == 0;
两个参数n和d才参数列表中指定,并且用逗号隔开。每当需要一个以上的参数时,就在lambda表达式操作符左侧,使用一个带括号的参数列表指定参数,参数之间使用逗号隔开。
对于lambda表达式中的多个参数,有一点十分重要:如果需要显式声明一个参数类型,那么必须为所有的参数声明类型。例如下面的代码是合法的:
(int n,int d) -> (n % d) == 0;
这是不合法的:
(int n, d) -> (n % d) == 0;
块lambda表达式是操作符的右侧的代码不是一个简单的表达式,而是由一个代码块组成,其中包含多条语句。这种类型的lambda体被称为块体,具有块体的lambda表达式被称为块lambda。
块lambda扩展了lambda表达式内部可以处理的操作类型,因为它允许lambda体包含多条语句。例如,在块lambda中可以声明变量、使用循环、指定if和switch语句、创建嵌套代码块等。创建块lambda只需要使用花括号包围lambda体,就像创建其他语句块一样。
在块lambda中重要一点是:在块lambda中必须显式使用return语句来返回值。因为块lambda体代表的不是单独一个表达式。
下面这个例子使用块lambda来计算并返回一个int类型值的阶乘:
package lambda;
interface NumuricFunc{
int func(int n);
}
public class BlockLambdaDemo {
public static void main(String[] args) {
NumuricFunc factorial = (n) -> {
int result = 1;
for (int i = 1;i <= n;i++){
result *= i;
}
return result;
};
System.out.println(factorial.func(3));
System.out.println(factorial.func(6));
}
}
//结果如下:
//6
//720
在程序中,块lambda声明了变量result,使用了for循环,有一条return语句。在lambda表达式中出现return语句时,只是从lambda体返回,而不会导致包围lambda提的方法返回。
lambda表达式自身不能指定类型参数。所以lambda表达式不能是泛型的(由于存在类型推断,所有lambda表达式都展现出类似于泛型的特征)。与lambda表达式关联的函数式接口可以是泛型的。此时,lambda表达式的目标类型部分由声明函数式接口引用时指定的参数类型决定。如下例子所示:
定义接口
interface SomeFunc<T>{
T func(T t);
}
声明接口,并将lambda表达式赋给接口变量
SomeFunc<String> reverse = (str) -> {
String result = null;
int i;
for (i = str.length() - 1;i >= 0;i--){
result += str.charAt(i);
}
return result;
};
SomeFunc<Integer> factorial = (n) -> {
int result = 1;
for (int i = 1;i <= n;i++){
result *= i;
}
return result;
};
SomeFunc接口用于提供对两种不同类型的lambda表达式的引用。第一种表达式使用String类型,第二种使用Integer类型。因此,同一个泛型函数式接口可以用于不同类型的lambda表达式,参数的类型由声明接口的类型推断得出。
lambda表达式可以用在任何提供了目标类型的上下文中。一种情况就是作为参数传递lambda表达式。这是lambda表达式的一种常见用途。另外这也是lambda表达式的一种强大用途,因为可以将可执行代码作为参数传递给方法,这极大地增强了Java的表达力。下面演示这个过程:
定义接口
interface StringFunc{
String func(String n);
}
定义一个方法
static String stringOp(StringFunc sf,String s){
return sf.func(s);
}
调用方法时使用lambda表达式代替StringFunc sf
String inStr = "lambda add power to java";
String outStr;
//方式一
outStr = stringOp((str) -> str.toUpperCase(),inStr);
//方式二
outStr = stringOp((str) -> {
String result = null;
for (int i = 0;i < str.length() - 1;i++){
result += str.charAt(i);
}
return result;
},inStr);
//方式三
StringFunc reverse = (str) -> {
String result = null;
for (int i = str.length() - 1;i >= 0;i--){
result += str.charAt(i);
}
return result;
};
stringOp(reverse,inStr);
注意到stringOp()方法有两个参数,第一个参数类型是StringFunc,而StringFunc是一个函数式接口,所以这个参数可以接受对任何StringFunc实例的引用,包括lambda表达式创建的实例,第二个参数是需要操作的字符串。
上例中使用三种方式向stringOp传递参数。可以传递简单的表达式,也可以传递一段代码块,甚至可以先创建接口,将lambda表达式赋值给接口引用,直接将接口引用传递给方法。
传递一个简单的表达式lambda作为参数,这会创建函数式接口的一个实例,并把对该实例的引用传递给方法参数,这就把嵌入在一个类实例中的lambda表达式代码传递给了方法。目标类型上下文由参数的类型决定。
lambda表达式可以抛出异常。但是,如果抛出经检查的异常,该异常就必须与函数式接口的抽象方法的throws子句中列出的异常兼容。看如下例子:
定义函数式接口,定义自己的异常
interface DoubleNumericArrayFunc{
double func(double[] n) throws EmptyArrayException;
}
class EmptyArrayException extends Exception{
EmptyArrayException(){
super("Array Empty");
}
}
将lambda表达式赋值给接口引用
DoubleNumericArrayFunc average = (n) -> {
double sum = 0;
if (n.length == 0){
throw new EmptyArrayException();
}
for (int i = 0;i < n.length;i++){
sum += n[i];
}
return sum;
};
在func()方法中包含throws子句是必要的,如果不这么做,那么由于lambda表达式不在与func()方法兼容,程序将无法通过编译。
在lambda表达式中可以访问其外层作用域内定义的变量。
有一个重要的特征与lambda表达式相关,叫做方法引用。方法引用提供了一种引用而不执行方法的方式。这种特性与lambda表达式相关,因为它也需要由兼容的函数式接口构成的目标上下文。计算时,方法引用也会创建函数式接口的一个实例。
被引用的方法实际上是与函数式接口中的方法兼容的,也就是参数列表相同,返回类型相同。
1.静态方法的方法引用
ClassName::methodName
2.实例方法的方法引用
objRef::methodName
3.泛型中的方法引用
(1)泛型类
ClassName<类型参数>::methodName
(2)泛型方法
ClassName::<类型参数>methodName
与创建方法引用相似,可以创建构造函数的引用(包括对象,数组的构造函数)
classname::new
当是泛型类时
classname<类型参数>::new