lambda表达式
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次.
Java1.8新特性lambda表达式还没有出来之前,在Java中是不能直接传递代码段,因为java是一种面向对象的语言,所以必须构造一个对象,这个对象的类需要一个方法能包含所含有的代码.
lambda表达的语法
比如之前想实现一个按长度而不是默认的字段顺序对字符串排序,可以向sort方法穿入一个Comparator对象:
public class LengthComparator implements Comparator<String> {
@Override
public int compare(String o1, String o2) {
return o1.length() - o2.length();
}
public static void main(String[] args) {
String[] strs = {"A","BB"};
Arrays.sort(strs,new LengthComparator());
}
}
第一个lambda表达式,参数,箭头(->)以及一个表达式
(String first, String second) -> first.length() - second.length()
如果代码要完成的计算无法放在一个表达式中,就可以像写方法一样,把这些代码放在{}中,并包含显式的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();
等同于
Comparator<String> comp = (String first,String second) -> first.length() - second.length();
函数式接口
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式.这种接口称为函数式接口(functional interface)
Array.sort方法,它的第二个参数需要一个Comparator实例,Comparator就是只有一个方法的接口,所以可以提供一个lambda表达式:
Arrays.sort(words,(first,second) -> first.length() - second.length());
在底层,Arrays.sort方法会接受实现了Comparator< String >的某个类的对象.在这个对象上调用compare方法会执行这个lambda表达式的体.
方法引用
如果已经有现成的方法可以完成你想要传递到其他代码的某个动作,例如,你希望只要出现一个定时器时间就打印这个事件对象.
Timer timer = new Timer(1000, e -> System.out.println(e));
但是,如果直接把println方法传到到Timer构造器就更好.
Timer timer = new Timer(1000, System.out::println);
表达式 System.out::println 是一个方法引用(method reference),等价于lambda表达式 x -> System.out.println(x).
还有例子,比如要对字符串进行排序,不考虑字母的大小写,
Arrays.sort(strings,String::compareToIgnoreCase)
要用::操作符分割方法名与对象或类名.
存在以下3中情况
- object::instanceMethod
- Class::staticmethod
- Class::instanceMethod
在前2中情况中,方法引用等价于提供方法参数的lambda表达式.
System.out::println 等价于 x -> System.out.println(x)
Math::pow 等价于 (x,y) -> Math:pow(x,y)
对于第3种情况,第一个参数会成为方法的目标.
String::compareToIgnoreCase 等价于 (x,y) -> x.compareToIgnoreCase(y)
构造引用
构造器引用与方法引用类似,例如Person::new是Person构造器的一个引用.哪一个构造器,取决于上下文.假如有一个字符串列表,可以把它转换为一个Person对象数组,为此要在各个字符串上调用构造器,调用如下:
ArrayList<String> names =...;
Stream<Person> stream = names.stream().map(Person::new);
List<Person> people = stream.collect(Collectors.toList());
可以用数组类型简历构造器引用.例如 int[]::new 是一个构造器引用,它有一个参数:即数组的长度,等价于 x -> new int[x]
Java有一个限制,无法构造泛型类型T的数组,数组构造器引用对于克服这个限制很有用.
表达式 new T[n]是会产生错误,因为这会改为new Object[n].
对于开发类库的人来说,这是一个问题.例如,假设我们需要一个Person对象数组.Stream接口有一个toArray方法可以返回Object数组:
Object[] people = stream.toArray();
用户希望得到一个Person引用数组,而不是Object引用数组.流库利用构造器引用解决了这个问题.可以把Person[]::new传入toArray方法:
Person[] people = stream.toArray(Person[]::new)
toArray方法调用这个构造器来得到一个真确类型的数组.然后填充这个数组并返回.
变量作用域
lambda表达式有3个部分:
1.一个代码块;
2.参数;
3.自由变量的值,这里值非参数而且不再代码中定义的变量.
public static void repeatMessage(String text, int delay) {
ActionListener listener = event -> {
System.out.println(text);
Toolkit.getDefaultToolkit().beep();
};
new Timer(delay, listener).start();
}
repeatMessage(“Hello”,1000);
来看这一段代码中的变量text,这个变量并不是在lambda表达式中定义的,实际上,这是repeatMessage方法的一个参数变量.
这里 存在一个问题,lambda表达式的代码可能会在repeatMessage调用返回很久以后才运行,而那时这个参数变量已经不存在了.
这个lambda表达式有1个自由变量text,表示lambda表达式的数据结构必须存储在自由变量的值,在这里就是字符串”Hello”,它被lambda表达式捕获(captured).(可以把一个lambda表达式转换为包含一个方法的对象,这样自由变量的值就会赋值到这个对象的实例变量中)
在lambda表达式中,有一个中重要的限制,只能引用值不会改变的变量.
- 在lambda表达式中改变变量的,并发执行多个动作时就会不安全.
- 如果在lambda表达式中引用变量,而这个变量可能在外部改变,也是不合法的.
规则:lambda表达式中捕获的变量必须实际上是最终变量(effectively final).