Java8 lambda表达式

無名 发表于: 2016-06-25   最后更新时间: 2016-06-25 09:44:37  
{{totalSubscript}} 订阅, 5,138 游览

Java8 lambda表达式

java8中最大的变化就是引入了lambda表达式,一种紧凑的传递行为的方式,这也是本书剩下部分所要讨论的内容,让我们进入其中吧。

编写第一个lambda表达式

swing是一个平台无关的gui库,在该库中,有很多常见的习惯,比如为了知道用户点点击了什么,注册一个事件监听器,这个事件监听器可以执行一些操作响应用户的输入。

button.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent event){
    System.out.println("button clicked");
  }
})

在该例子中,我们创建了一个对象实现了ActionListener接口,该接口只有一个方法actionPerformed(),当用户点击了按钮之后,这个方法会被调用,该匿名内部类提供了该方法的实现。

匿名内部类是为了让java程序员传递行为和传递数据一样容易,不幸的是,他们并不容易,为了调用处理逻辑的代码仍然有四行模板代码,重复的模板代码并不是唯一的问题,这种代码也难以阅读,我们并不想传递一个对象,而仅仅只需要传递某种行为,在java8中我们可以写得更简洁

button.addActionListener(event -> System.out.println("button cliecked"));

不同于传递一个实现某个接口的对象,我们传递了一段没有命名函数的代码,event是参数,同匿名内部类的参数一样, -> 将参数和lambda表达式的内容体分开。同匿名内部类做法另外一种不同就是,我们申明变量的方式,之前,我们需要显示申明类型ActionEvent,在该例中,我们不需要提供类型,编译也能通过,在这背后发生的是javac从上下文获取event类型,此处是从addActionListener签名中获取,这意味着你不需要显示申明其类型,我们之后会更加详细讨论这种设计,首先让我们看看编写lambda表达式几种不同的方法。

如何在正确的场合使用lambda表达式?
下面有几个使用lambda表达式的例子

①Runnable noArguments = () -> System.out.println("hello world");

②ActionListener oneArgument = event -> System.out.println("button click");

③Runnable multiStatement = () ->{
System.out.print("hello");
System.out.println(" world");
}

④BinaryOperator<Long> add = (x, y) -> x + y;

⑤BinaryOperator<Long> addExplicit = (Long x, Long y) -> x + y;

①展示了在没有参数的情况下如何使用lambda,可以使用一对空的小括号来表示没有参数,这是一个实现了Runnable的lambda的表达式,该接口只有一个方法run(),该方法不接受任何参数,且返回void.

②展示了只有一个参数的lambda的表达式。不同于只包含一条表达式的lambda。

③包含了一个代码块,包含在一个大括号中,代码块同平常的方法类似,在结束之前可以返回结果或者抛出异常,

④包含了多个参数,这种情况下有必要考虑如何阅读这条表达式,这行代码没有相加两个数,它创建了一个函数相加两个数,add这个变量并不是两个数的和,而是一个相加两个数的函数。

⑤目前为止,所有的lambda表达式参数类型都是由编译器确定,这非常棒,但是有些时候我们需要明确参数类型。

上述所有表达式都表明lambda表达式依赖于上下文,由编译器去判断,这种做法也不完全新创的,在example2-4中数组的初始化依赖于上下文环境,还有null,只有把它分配的时候才知道其类型。

final String[] array = {"hello", "world"};

Using Values

你以往使用匿名内部类的时候,你可能遇到这种情况,在内部类中你想使用外部的变量,需要把外部变量申明为final类型,变量申明为final意味着不能将该变量指向其他对象。

final String name = getUserName();
    button.addActionListener(new ActionListener(){
    public void actionPerformed(ActionEvent event){
    System.out.println("hi"+name);
  }
})

在java8中,这种限制有所放松,可以引用非final的参数,虽然没有申明为final类型,但是必须当做final类型看待,不能去改变其所指的对象,否则会报错。

String name = getUserName();
button.addActionListener(event -> System.out.println("hi" + name));

如果多次为一个变量赋值,并且单算在lambda表达式中进行引用,那么会得到一个变异错误。

String name = getUserName();
name = formatUserName(name);
button.addActionListener(event -> System.out.println("hi" + name));

这也是为什么人们把lambda表达式当做闭包看待,在编程语言的争论中,有许多关于java是否有闭包的争论,因为其只能引用final变量,为了避免这一个无端的争论,我在本书中将他们称之为lambda表达式。但是无论我如何称呼他们,我已经提到过lambda表达式是静态类型的,因此让我们研究表达式本身:他们称之为函数式接口。

函数式接口

函数式接口是指的只有一个抽象方法的接口,被当做是lambda表达式的一种类型使用。
在java中,所有的参数都有类型,如果我们传入3到一个方法中,这个参数是int类型,那么lambda是什么类型的呢?

类型讨论?

某些情况下,需要提供确切的类型标志,我的建议是你和你的团队怎么易读怎么做。有的时候不标明类型让其看起来更加简洁,有时候加上类型标志让其更加明显。我发现,起初加上类型可能看起来很有用,但是之后可能你只会在必要的时候才会加上,本章将会提供几个原则让你很容易辨别出是否必要加上。
类型的讨论其实是java7中类型讨论的延续。

Map<String, Integer> oldWordCount = new HashMap<String, Integer>();
Map<String, Integer> diamonWordCounts = new HashMap<>();

对于变量oldWordCount我们明确指明了泛型的类型,但是diamonWordCounts使用了diamond operator,泛型类型并没有指出,编译器会指定其类型。
如果你直接传递一个构造函数到方法中,编译器也能指出泛型类型,同样的java7允许空出构造函数的泛型,java8允许lambda表达式的参数类型。
其实没有什么魔法,java编译器在lambda表达式上下文中寻找参数确定的类型。
让我们通过几个例子更加深入讨论。
在下面两种情况下,我们传入一个参数到一个函数接口中,因此我们很容易确定两者的区别。

第一个例子:

Predicate<Integer> atLeast5 = x -> x >5

不同于之前的ActionListener示例,Predicate是一个有返回值的lambda表达式,这里表达式的内容为x > 5,表达式的值作为lambda表达式的返回值。
我们可以看到Predicate有一个泛型参数,上一个表达式中我们传入一个int值,java编译器会检测返回值是否为一个boolean值。

public interface Predicate<T>{
boolean test(T t);
}

让我们看看另外一个更加复杂的例子:

BinaryOperator接口
BinaryOperator<Long> addLongs = (x, y) -> x+y;

这个接口有两个参数以及一个返回值,两个参数类型相同,在实例中我们使用的参数类型为Long类型,但是如果不提供足够的信息编译器就会返回一个变异错误,例如在下面的一个表达式中就会抛出变异错误

BinaryOperator add = (x, y) -> x + y;

总结:

1.lambda表达式没有命名,用来像传递数据一样传递操作。
2.函数接口指的是只有一个抽象方法的接口,被当做是lambda表达式的类型。

更新于 2016-06-25

查看java更多相关的文章或提一个关于java的问题,也可以与我们一起分享文章