Java 8 主要是在原来面向对象的基础上增加了函数式编程的能力。
lambda表达式是一段可以传递的代码,它可以被执行一次或多次。
目录
一、为什么要使用lambda表达式
在lambda出现之前,在Java中向其他代码传递一段代码并不容易。
由于Java是一个面向对象的语言,因此你不得不构建一个属于某个类的对象,由该对象的某个方法来包含所需的代码。
// a simple example of how to sort a list of strings in prior versions of Java
List<String> names = Arrays.asList("peter", "anna", "mike", "xenia");
Collections.sort(names, new Comparator<String>() {
public int compare(String a, String b) {
return b.compareTo(a);
}
});
lambda表达式的出现,使得在Java语言中传递一段代码也很容易。
二、lambda 表达式的语法
以上面的sort a list of strings 为例,使用lambda表达式的语法,可以这样写:
// a simple example of how to sort a list of strings using lambda
Collections.sort(names, (String a, String b) -> {
return a.compareTo(b);
});
由上面的例子,不难发现,lambda表达式的语法格式为:参数列表、箭头->,以及一个代码块。
在该语法基础上,还有一些附加语法:
// 如果代码块比较简单,可以用一个表达式替代:
Collections.sort(names, (String a, String b) -> b.compareTo(a));
// 如果表达式没有参数,用一对空的小括号表示:
() -> { System.out.println("hi~"); }
// 如果表达式的的参数类型是可以被推导出的,就可以省略参数类型:
Collections.sort(names, (a, b) -> b.compareTo(a));
// 如果表达式只有一个参数,且该参数类型可被推导出来,就可以省略小括号:
s -> { System.out.println(s); }
// 可以像对待方法参数一样,向lambda表达式的参数添加final修饰符和注解:
(final String name) -> ...
(@NonNull String name) -> ...
三、函数式接口
functional interface,即函数式接口,是指只有一个抽象方法的接口。
a.对该接口的其他方法,如default方法、static方法没有要求
b.对于接口中声明的Object类中的方法,如equals()等,不算在抽象方法的统计里面(https://docs.oracle.com/javase/specs/jls/se8/html/jls-9.html#jls-9.4.1.2)
使用@FunctionalInterface注解来标注一个函数接口,可以在编译期确保一个接口只有一个抽象方法。
也就是说,这个注解也不是必须的,不用这个注解标注,也没啥问题
函数式接口与lambda表达式有什么关系?
对一个函数式接口,你可以通过lambda表达式来创建该接口的对象。
// 1.通过lambda表达式来创建函数式接口的对象
Comparator<String> comparator = (a, b) -> { return b.compareTo(a); };
// 2.不使用FunctionalInterface标注函数式接口的例子
public interface MyFunctionalInterface<T> {
int compare(T o1, T o2);
}
MyFunctionalInterface<String> myFunctionalInterface = (a, b) -> { return b.compareTo(a); };
java与其他语言的不同:
在很多其他的支持函数式编程的语言中,可以直接将lambda表达式赋值给一个函数类型的变量。但是,Java的设计者们没有使用这种方式,他们坚持使用熟悉的接口概念,而没有将函数类型加入到Java中。
你甚至不能将一个lambda表达式赋值给一个Object类型的变量,因为Object不是一个函数式接口。
四、方法引用
如果要传递的代码已经有别的方法实现了,可以直接传递该方法引用。
有3种方法引用的传递方式:
-
对象::实例方法
-
类::静态方法
-
类::实例方法
前两种方式,方法引用等同于提供方法参数的lambda表达式;第三种方式,第一个参数会成为执行方法的对象。
// 方式1,对象::实例方法
System.out::println // 相当于 System.out::println(x)
this::equals // 相当于 this.equals(x)
// 方式2,类::静态方法
Math::pow // 相当于Math.pow(x,y)
// 方式3,类::实例方法
String::compareToIgnoreCase // 相当于 x.compareToIgnoreCase(y)
五、构造器引用
构造器引用可以看作一种特殊的方法引用,它与方法引用类似,不同的是构造器引用中方法名是new。
对拥有多个构造器的类,使用那个构造器,取决于上下文。
ArrayList::new // 相当于 new ArrayList()
int[]::new // 相当于 x -> new int[x]
六、变量作用域
一个例子:
// 使用lambda表达式声明一个方法sayHi()
public void sayHi(int count) {
new Thread(() -> {
for (int i = 0; i < count; i++) {
System.out.println("hi~");
}
}
).start();
}
// 调用这个方法,打印10次hi~
sayHi(10);
变量count并没有在lambda表达式中定义,而是方法sayHi()的参数变量。
lambda表达式可能在sayHi(10)返回之后才执行,此时参数变量已经销毁了,怎么会能正常打印结果呢?
lambda表达式中的变量作用域:
一个lambda表达式分为3部分:
-
一段代码
-
参数
-
自由变量的值(不是参数且没有在lambda代码中定义的变量)
这里的count即为lambda表达式中的自由变量,它的值已经被lambda表达式捕获了。(含有自由变量的代码块,被称作“闭包”)
可以将一个表达式转化为只含有一个方法的对象,这样自由变量的值就会被复制到该对象的实例变量中。
在lambda表达式中,被引用变量的值不能被更改。
七、默认方法
八、接口中的静态方法
参考资料:
《写给大忙人看的 Java SE 8》
网友评论