lambda 表达式
为什么引入 lambda 表达式
lambda 表达式是一个可传递的代码快,可以在以后执行一次或多次。
在 1.8 之前,在 Java 中传递一个代码快并不容易,不能直接传递代码快。Java 是一个面向对象语言,所以必须构造一个对象,这个对象需要有一个方法能包含所需的代码。
Java 的设计者一直拒绝增加这个特性,毕竟 Java 的强大之处就在于其简单性和一致性。
如果只要一个特性能够让代码稍简洁一些,就把这个特性添加到语言中,这个语言很快就会一团糟。而在 1.8中,设计者们终于找到一个适合 Java 的设计来支持这种函数编程。
lambda表达式语法
(String first,String second) -> first.length() - second.length()
Java 是一种强类型语言,所以一定要制定类型。lambda 表达式就是一个代码快,以及必须传入代码的变量规范。
以上就是 lambda 表达式的一种形式:参数,箭头 ( -> ) 以及一个表达式。如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些底阿妈放在 {} 中,并包含显示的 return 语句。
(String first,String second) -> {
if(first.length() < second.length()) return -1;
else if(first.length() > second.length()) return 1;
else return 0;
}
即使 lambda 表达式没有参数,仍要提供空括号,就像无参数方法一样。
() -> { for(int i=100;i>=0;i--) System.out.println(i); }
如果可以推导出一个 lambda 表达式的参数类型,则可以忽略其类型。
Comparator<String> comp= (first, second) -> first.length() - second.length();
在这里,编译器可以推导出 first 和 second 必然是字符串,因为这个 lambda 表达式将赋值给一个字符串比较器。
如果方法只有一个参数,而且这个参数类型可以推导得出,那么深知还可以省略小括号。
ActionListener listener = event ->
System.out.println("THis time is "+ new Date());
无需指定 lambda 表达式的返回类型, lambda 表达式的返回类型总是会由上下文推导得出。
(String first,String second) -> first.length() - second.length()
上面的表达式可以在需要 int 类型结果的上下文中使用。
import java.awt.*;
import java.util.Arrays;
import java.util.Date;
import javax.swing.*;
public class Test {
public static void main(String[] args) {
String[] plants = new String[] {"Mercury","Venum","Earth","Mars","Jupiter","Saturn","Uranus","Neptune"};
System.out.println(Arrays.toString(plants));
System.out.println("Sorted om doctopmary order:");
Arrays.sort(plants);
System.out.println(Arrays.toString(plants));
System.out.println("Sorted by length:");
Arrays.sort(plants,(String first,String second)->first.length() - second.length());
System.out.println(Arrays.toString(plants));
Timer t = new Timer(1000,event->
System.out.println("The time is " + new Date()));
t.start();
JOptionPane.showMessageDialog(null, "Quit program?");
System.exit(0);
}
}
函数式接口
Java 中有很多封装代码块的接口,如 ActionListener 或 Comparator。lambda 表达式和这些接口是兼容的。
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个 lambda 表达式。这种接口称为函数式接口。
Arrays.sort(plants,(String first,String second)->first.length() - second.length());
在底层,Arrays.sort() 方法会接受实现了 Comparator<String> 的某个类对象。在这个对象上调用 compare方法会之星这个 lambda 表达式的主体。这些对象和类的管理完全取决于具体实现,与传统的内联类相比,这样可能高效的多。最好把 lambda 表达式看作是一个函数,而不是一个对象,例外要接受 lambda 表达式可以传递到函数式接口。
实际上,在 Java 中,对 lambda 表达式所能做的也只能是转换为函数式接口。
方法引用
已经有线程的方法可以完成你想要传递到其他代码的的某个动作,你可以直接调用
Timer t = new Timer(1000,event->System.out.println(event));
但是,如果直接把 println 方法传递到 Timer 构造器就更好了。
Timer t = new Timer(1000,System.out::println);
System.out::println 就是一个方法引用,等价于 lambda 表达式 x->System.out.println(x)。
要用 :: 操作符分隔方法名与对象或类名。主要有三种情况 :
- object::instanceMethod
- Class::staticMethod
- Class::instanceMethod
也可以在方法引用中使用 this 和 super 参数。
构造器引用
构造器引用和方法引用很类似,只不过方法名为 new。例如, Person::new 是 Person构造器的一个引用。具体使用哪个构造器取决于上下文。
不过 Java 有一个限制,无法构造泛型类型 T 的数组。
变量作用域
lambda 表达式可以捕获外围作用域中变量的值。在 Java 中,要确保所捕获的值是明确定义的,这里有一个重要限制。在 lambda 中,只能引用值不会改变的变量。(如果在 lambda 表达式中改变变量,并发执行多个动作时就会不安全)
另外如果在 lambda 表达式中引用变量,而这个变量可能在外部改变,这也是不合法的。
这里有一条规则 : lambda 表达式中捕获的变量必须实际上是最终变量(初始化后就不会在为它赋新值)。
lambda 表达式的体与嵌套块有相同的作用域。在 lambda 表达式中声明与一个局部变量同名的参数或局部变量是不合法的。
处理 lambda 表达式
使用 lambda 表达式的重点是延迟执行。毕竟,如果想要立即执行代码,完全可以直接执行,而无需把它包装在一个 lambda 表达式中。之所以希望以后再执行代码,有很多原因。如:
- 在一个单独的线程中运行代码
- 多次运行代码
- 在算法的适当位置运行代码
- 发生某种情况时执行代码
- 只在必要时才运行代码
还有点问题有待解决,后继补充。