Java学习(六)-lambda表达式

(1)为什么引入lambda表达式
lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。
指定时间间隔完成工作。可以将这个工作放到一个ActionListeneractionPerformed方法中:

	class Worker implements ActionListener {
		public void actionPerformed(Action event) {
			// do some work
		}
	}

想要反复执行这个代码时,可以构造一个Worker类的实例。然后把这个实例提交到一个Timer对象。这里的重点是actionPerformed方法包含希望以后执行的代码。
或者可以考虑如何用一个定制比较器完成排序。如果想按长度而不是默认的字典顺序对字符串排序,可以向sort方法传入一个Comparator对象:

	class LengthComparator implements Comparator<String> {
		public int compare(String first, String second) {
			return first.length() - second.length();
		}
	}
	...
	Arrays.sort(String, new LengthComparator());

compare方法不是立即调用。实际上,在数组完成排序之前,sort方法会一直调用compare方法,只要元素的顺序不正确就会重新排列元素。将比较元素所需的代码段放在sort方法中,这个代码将与其余的排序逻辑集成。
这两个例子有一个共同点,都是将一个代码块传递到某个对象(一个定时器,或者一个sort方法)。这个代码块会在将来的某个时间调用。
到目前为止,在Java中传递一个代码段并不容易,不能直接传递代码段。Java是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法能包含所需的代码。
(2)lambda表达式的语法
lambda表达式就是一个代码款,以及必须传入代码的变量规范。
参数,箭头(->)以及一个表达式。

	(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) //Same as(String first, String second)
	-> first.length() - second.length();

在这里,编译器可以推导出first和second必然是字符串,因为这个lambda表达式将赋给一个字符串比较器。
如果方法只有一个参数,而且这个参数的类型可以推导出,那么甚至可以省略小括号:

	ActionListener listener
	= event -> System.out.priintln("This time is " + new Date());
	//Instead of (event) -> ... or (ActionEvent event) -> ...

无需指定lambda表达式的返回类型。lambda表达式的返回类型总是会由上下文推导得出:

	(String first, String second) -> first.length() - second.length();

可以在需要int类型结果的上下文中使用。
(3)函数式接口
前面已经讨论过,Java中已经有很多封装代码块的接口,如ActionListenerComparatorlambda表达式与这些接口是兼容的。
对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。
为了展示如何转换为函数式接口,下面考虑Array.sort方法。它的第二个参数需要一个Comparator实例,Comparator就是只有一个方法的接口,所以可以提供一个lambda表达式:

	Arrays.sort(words, (first, second) -> first.length() - second.length());

在底层,Arrays.sort方法会接收实现了Comparator<String>的某个类的对象。在这个对象上调用compare方法会执行这个lambda表达式的体。这些对象和类的管理完全取决于具体实现,与使用传统的内联相比,这样可能高效得多。最好把lambda表达式看作是一个函数,而不是一个对象,另外要接受lambda表达式可以传递到函数式接口。
lambda表达式可以转换为接口,这一点让lambda表达式很有吸引力。

	Timer t = new Timer(1000, event -> System.out.println("At the tone, the time is " + new Date()));
	Toolkit.getDefaultToolkit().beep();

与实现了ActionListener接口的类相比,这个代码可读性要好得多。
实际上,在Java中,对lambda表达式所能做的也只是能转换为函数式接口
JavaAPI在java.util.function包中定义了很多非常通用的函数式接口。其中一个接口==BiFunction<T,U,R>==描述了参数类型为T和U而且返回类型为R的函数。可以把我们的字符串比较lambda表达式保存在这个类型的变量中:

	BiFunction<String, String, Integer> comp =
	(first, second) -> first.length() - second.length();

(4)构造器引用
构造器引用与方法引用很类似,只不过方法名为new。例如,Person::newPerson构造器的一个引用。哪一个构造器呢?这取决于上下文。假设你有一个字符串列表。可以把它转换为一个Person对象数组,为此要在各个字符串上调用构造器,调用如下:

	ArrayList<String> names = ...;
	Stream<Person> stream = name.stream().map(Person::new);
	List<Person> people = stream.collect(Collectors.toList());

(5)变量作用域
通常,你可能希望在lambda表达式中访问外围方法或类中的变量。考虑下面这个例子:

	public static void repeatMessage(String text, int delay) {
		ActionListener listener = event -> {
			System.out.println(text);
			Toolkit.getDefaultToolkit.beep();
		};
		new Timer(delay, listener).start();
	}
	//看这样一个调用:
	respeatMessage("hello", 1000);//prints hello every 1000 milliseconds

现在来看lambda表达式中的变量text。注意这个变量并不是在lambda表达式中定义的。实际上,这是repeatMessage方法的一个参数变量。
如果再想想看,这里好像会有问题,尽管不那么明显。lambda表达式的代码可能会在repeatMessage调用返回很久以后才运行,而那时这个参数变量已经不存在了。如何保留text变量呢?
lambda表达式有3个部分:
1)一个代码块
2)参数
3)自由变量的值,这里只非参数而且不在代码中定义的变量。
我们的例子中,这个lambda表达式有1个自由变量text。表示lambda表达式的数据结构必须存储自由变量的值,在这里就是字符串“hello”,我们说它被lambda表达式捕获。(下面来看具体的实现细节。例如,可以把一个lambda表达式转换为包含一个方法的对象,这样自由变量的值就会复制到这个对象的实例变量中。)
注释:关于代码块以及自由变量的值有一个术语:闭包(closure)。在Java中,lambda表达式就是闭包。
可以看到,lambda表达式可以捕获外围作用域中变量的值。,在Java中,要确保所捕获的值是明确定义的,这里有一个重要的限制。==在lambda表达式中,只能引用值不会改变的变量。例如:

	public static void countDown(int start, int delay) {
		ActionListener listener = event ->
		{
			start --;
			System.out.println(start);
		}
		new Timer(delay, listener).start();
	}

之所以有这个限制是有原因的。如果在lambda表达式中改变变量,并发执行多个动作时就会不安全。对于目前为止我们看到的动作不会发生这种情况,不过一般来讲,这确实是一个严重的问题。
另外
如果在lambda表达式中引用变量,而这个变量可能在外部改变,这也是不合法的。

	public static void repeat(String text, int count)
	{
		for(int i =1; i <= count; i ++) {
			ActionListener listener = event ->
			{
				System.out.println(i + ":" + text);
			};
			new Timer(1000, listener).start();
		}
	}

这里有一条规则:lambda表达式中捕获的变量必须实际上是最终变量。实际上的最终变量是指,这个变量初始化之后就不会再为它赋新值。在这里,text总是指示同一个String对象,所以捕获这个变量是合法的。不过,i的值会改变,因此不能捕获i。
lambda表达式的体与嵌套块有相同的作用域。这里同样适用命名冲突和遮蔽的有关规则。在
lambda表达式中声明与一个局部变量同名的参数或局部变量是不合法的。

(6)处理lambda表达式
==使用lambda表达式的重点是延迟执行。==之所以希望以后再执行代码,这有很多原因,如:
*在一个单线程中运行代码;
*多次运行代码;
*在算法的适当位置运行代码(例如,排序中的比较操作);
*发生某种情况时执行代码(如,点击了一个按钮,数据到达,等等);
*只在必要时才运行代码。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值