Java lambda表达式---巩固强化

1.为什么引入lambda表达式

lambda表达式是一个可传递的代码块,可以在以后执行一次或多次。具体介绍语法之前,下面先退一步,观察我们在Java中的那些地方用过这种代码块。
如何用一个定制比较器完成排序。如果想按长度而不是默认的字典顺序队字符串进行排序,可以向sort方法传入一个Comparator对象:

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

compare方法不是立即调用。实际上,在数组完成排序之前,sort方法会一直调用compare方法,只要元素的顺序不正确就会重新排列元素。将比较元素所需的代码段放在sort方法中,这个代码将与其余的排序逻辑集成。这个例子有个特点,就是将一个代码块传递到某个对象,这个代码块会在将来某个时间调用。
到目前为止,在Java中传递一个代码段并不容易,你不能直接传递代码段。Java是一种面向对象语言,所以必须构造一个对象,这个对象的类需要有一个方法包含所需的代码。

2.lambda表达式的语法

再考虑上面讨论的排序例子。我们传入代码来检查一个字符串是否比另一个字符串短。这里要计算:
first.length()-second.length()
first和second是什么它们都是字符串。Java是一种强类型语言,所以我们还要指定它们的类型:

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

这就是你看到的第一个lambda表达式。lambda表达式就是一个代码块,以及必须传入代码的变量规范。
你已经见过Java中的一种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);}

如果方法只有一个参数,而且这个参数的类型可以推导得出,那么甚至还可以省略小括号:

ActionListener listener=event->System.out.println("...");

:如果一个lambda表达式只在某些分值返回一个值,而另外一些分支不返回值,这是不合法。
下面程序显示了如何对一个比较器使用lambda表达式。
在这里插入图片描述

3.函数式接口

对于只有一个抽象方法的接口,需要这种接口的对象时,就可以提供一个lambda表达式。这种接口称为函数式接口。
为了展示如何转换为函数式接口,下面考虑Arrays.sort方法。它的第二个参数需要一个Comparator实例,Comparator就是只有一个方法的接口,所以可以提供了一个lambda表达式:

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

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

4.变量作用域

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

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);//prints Hello every 1,000 milliseconds

现在来看lambda表达式中的变量text。注意这个变量并不是在这个lambda表达式中定义的。实际上,这是repeatMessage方法的一个参数变量。
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 next,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.

5.处理lambda表达式

使用lambda表达式的重点是延迟执行。毕竟,如果想要立即执行代码,完全可以直接执行,而无须把它包装在一个lambda表达式中。之所以希望以后再执行代码,这有很多原因,如:

  • 在一个单独的线程中运行代码
  • 多次运行代码
  • 在算法的适当位置运行代码(例如,排序中的比较操作)
  • 发生某种情况时执行代码(如,点击了一个按钮,数据到达,等等)
  • 值在必要时才运行代码。

猜你喜欢

转载自blog.csdn.net/Achenming1314/article/details/105794572