Java8实战Lambda表达式
Posted 7788it
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java8实战Lambda表达式相关的知识,希望对你有一定的参考价值。
Java 8的Lambda表达式借鉴了C#和Scala等语言中的类似特性,简化了匿名函数的表达方式。Lambda表达式可以直接以内联的形式为函数式接口的抽象方法提供实现,并把整个表达式作为函数式接口的实例。什么是函数式接口?简单来说就是只包含一个抽象方法的接口,允许有默认的实现(使用default关键字描述方法)。函数式接口建议使用@FunctionalInterface注解标注,虽然这不是必须的,但是这样做更符合规范。
在Java 8之前,实现Runnable常用方式是编写一个匿名类:
Thread thread = new Thread(new Runnable()
@Override
public void run()
System.out.println("hello");
);
thread.start();
使用Lambda表达式后,上面的代码可以改造为:
Thread thread = new Thread(() -> System.out.println("hello"));
thread.start();
是不是很神奇?!很简洁?!
Lambda表达式解析
Lambda表达式的基本语法如下:
(parameters) -> expression
or
(parameters) -> statements;
由语法可以看到,Lambda表达式包含了三个部分:
-
参数列表;
-
箭头->把参数列表与Lambda主体分隔开;
-
Lambda主体,只有一行代码的时候可以省略大括号和return关键字。
比如下面这些Lambda表达式都是合法的:
(String str) -> str.length()
(String str) -> return str.length();
?
() -> System.out.println("hello")
?
() ->
() -> 17
?
(int x, int y) ->
System.out.println(x);
System.out.println(y);
Lambda的使用场合
什么时候可以使用Lambda表达式?使用Lambda必须满足以下两个条件:
-
实现的对象是函数式接口的抽象方法;
-
函数式接口的抽象方法的函数描述符和Lambda表达式的函数描述符一致。
函数式接口
函数式接口的定义开头已经说了,这里就不再赘述。在Java 8之前,常见的函数式接口有java.util.Comparator,java.lang.Runnable等。拿java.util.Runnable来说,查看其源码如下:
@FunctionalInterface
public interface Runnable
public abstract void run();
这个接口只有一个抽象方法,并且使用@FunctionalInterface注解标注。
接口现在还可以拥有默认方法(即在类没有对方法进行实现时,其主体为方法提供默认实现的方法)。哪怕有很多默认方法,只要接口只定义了一个抽象方法,它就仍然是一个函数式接口。
函数描述符
函数描述符其实也可以理解为方法的签名。比如上述的Runnable抽象方法不接受参数,并且返回void,所以其函数描述符为() -> void。而() -> System.out.println("hello")Lambda表达式也是不接受参数,并且返回void,即其函数描述符也是() -> void。所以代码Runnable r = () -> System.out.println("hello");是合法的。
特殊的void兼容规则
如果一个Lambda的主体是一个语句表达式, 它就和一个返回void的函数描述符兼容(当然需要参数列表也兼容)。例如,以下Lambda是合法的,尽管List的add方法返回了一个 boolean,而不是Runnable抽象方法函数描述符() -> void所要求的void:
List<String> list = new ArrayList<>();
Runnable r = () -> list.add("hello");
更简洁的Lambda
编写一个类型转换的函数式接口:
@FunctionalInterface
public interface TransForm<T, R>
R transForm(T t);
编写一个Lambda表达式实现该函数式接口,用于实现String转换为Integer,代码如下:
TransForm<String, Integer> t = (String str) -> Integer.valueOf(str);
System.out.println(t.transForm("123"));
上面的Lambda表达式可以进一步简化为如下方式:
TransForm<String, Integer> t = (str) -> Integer.valueOf(str);
System.out.println(t.transForm("123"));
因为Java编译器会从上下文(目标类型)推断出用什么函数式接口来配合Lambda表达式,这意味着它也可以推断出适合Lambda的签名。就拿这个例子来说,TransForm的抽象方法transForm在本例中的函数描述符为(String) -> Integer,所以对应的Lambda的签名也是如此,即Lambda的参数即使不声名类型,Java编译器可以知道其参数实际上为String类型。
其实,上面的Labmda表达式还不是最简洁的,其还可以更进一步地简化为如下写法:
TransForm<String, Integer> t = Integer::valueOf;
System.out.println(t.transForm("123"));
你肯定很困惑,这还是Lambda表达式吗,箭头去哪里了?双冒号又是什么鬼?其实这种写法有一个新的名称,叫做方法的引用。
方法引用可以被看作仅仅调用特定方法的Lambda的一种快捷写法。它的基本思想是,如果一个Lambda代表的只是“直接调用这个方法”,那最好还是用名称来调用它,而不是去描述如何调用它,这样代码可读性更好。基本写法就是目标引用放在分隔符::前,方法的名称放在后面。
举几个Lambda及其等效方法引用的例子:
Lambda表达式 | 等效方法引用 |
---|---|
(String s) -> System.out.println(s) | System.out::println |
(str, i) -> str.substring(i) | String::substring |
() -> Thread.currentThread().dumpStack() | Thread.currentThread()::dumpStack |
符号::除了出现在方法的引用外,它还常见于构造函数的引用中。为了演示什么是构造函数的引用,我们创建一个新的函数式接口:
@FunctionalInterface
public interface Generator<T, R>
R create(T t);
创建一个Apple类:
public class Apple
public Apple(String color)
this.color = color;
private String color;
?
public String getColor()
return color;
public void setColor(String color)
this.color = color;
现在我们可以使用如下方式来创造一个Apple实例:
Generator<String, Apple> g = Apple::new;
Apple apple = g.create("red");
这种通过ClassName::new的写法就是构造函数的引用。在这里Generator的抽象方法接收一个String类型参数,返回值类型为Apple,这和Apple类的构造函数相符合,所以这里编译可以通过。它等价于下面的写法:
Generator<String, Apple> g = (color) -> new Apple(color);
Apple apple = g.create("red");
Lambda表达式访问变量
Lambda表达式可以访问局部final变量,成员变量和静态变量。
这里主要说下局部final变量。有无final关键字不重要,重要的是确保该变量的值不会被改变就行了。比如下面的例子可以编译通过:
String hello = "hello lambda";
Runnable r = () -> System.out.println(hello);
而下面的这个就会编译出错,因为变量hello的值被改变了:
Lambda表达式实战
假如现在有如下需求:现有一个包含了各种颜色不同重量的苹果的List,编写一个方法,从中筛选出满足要求的苹果。比如筛选出红色的苹果、红色并且重量大于1kg的苹果、绿色重量小于0.5kg的苹果或者红色大于0.5kg的苹果等等。
不使用Lambda
在没有接触Lambda之前,我们一般会这样做:
定义一个筛选的接口
import cc.mrbird.java8.domain.Apple;
?
public interface AppleFilter
boolean test(Apple apple);
然后根据筛选的条件来编写各个不同的实现类:
筛选出红色苹果的实现方法:
import cc.java8.domain.Apple;
?
public class RedApple implements AppleFilter
@Override
public boolean test(Apple apple)
return "red".equalsIgnoreCase(apple.getColor());
筛选出红色并且重量大于1kg的苹果的实现方法:
import cc.java8.domain.Apple;
?
public class RedAndMoreThan1kgApple implements AppleFilter
@Override
public boolean test(Apple apple)
return "red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 1.0;
筛选出绿色重量小于0.5kg的苹果或者红色大于0.5kg的苹果的实现方法:
import cc.java8.domain.Apple;
?
public class GreenAndLessThan05OrRedAndMoreThan05Apple implements AppleFilter
@Override
public boolean test(Apple apple)
return ("green".equalsIgnoreCase(apple.getColor()) && apple.getWeight() < 0.5)
|| ("red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 0.5);
筛选苹果的方法:
import cc.java8.domain.Apple;
import java.util.ArrayList;
import java.util.List;
?
public class AppleFilterMethod
public static List<Apple> filterApple(List<Apple> list, AppleFilter filter)
List<Apple> filterList = new ArrayList<>();
for (Apple apple : list)
if (filter.test(apple))
filterList.add(apple);
return filterList;
开始筛选苹果:
List<Apple> appleList = new ArrayList<>();
appleList.add(new Apple("red", 0.4));
appleList.add(new Apple("red", 0.6));
appleList.add(new Apple("red", 1.3));
appleList.add(new Apple("green", 0.2));
appleList.add(new Apple("green", 0.35));
appleList.add(new Apple("green", 1.1));
?
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList, new RedApple());
for (Apple apple : appleFilterList)
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
输出:
red apple,weight:0.4
red apple,weight:0.6
red apple,weight:1.3
剩下的略。
可以看到,我们为了满足各种筛选条件创造了各种筛选接口的实现类,真正起作用的只有筛选方法中return那一行代码,剩下的都是一些重复的模板代码。使用Java 8中的Lambda可以很好的消除这些模板代码。
使用Lambda
AppleFilter接口实际上就是一个函数式接口,所以它的各种实现可以用Lambda表达式来替代,而无需真正的去写实现方法。
定义筛选接口:
import cc.java8.domain.Apple;
?
public interface AppleFilter
boolean test(Apple apple);
筛选苹果的方法:
import cc.mrbird.java8.domain.Apple;
import java.util.ArrayList;
import java.util.List;
?
public class AppleFilterMethod
public static List<Apple> filterApple(List<Apple> list, AppleFilter filter)
List<Apple> filterList = new ArrayList<>();
for (Apple apple : list)
if (filter.test(apple))
filterList.add(apple);
return filterList;
接下来便可以开始筛选了:
筛选红色的苹果:
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList,
(apple) -> "red".equalsIgnoreCase(apple.getColor()));
for (Apple apple : appleFilterList)
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
输出:
red apple,weight:0.4
red apple,weight:0.6
red apple,weight:1.3
筛选出红色并且重量大于1kg的苹果:
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList,
(apple) -> "red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 1.0);
for (Apple apple : appleFilterList)
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
输出:
red apple,weight:1.3
筛选出绿色重量小于0.5kg的苹果或者红色大于0.5kg的苹果:
List<Apple> appleFilterList = AppleFilterMethod.filterApple(appleList,
(apple) -> ("green".equalsIgnoreCase(apple.getColor()) && apple.getWeight() < 0.5) ||
("red".equalsIgnoreCase(apple.getColor()) && apple.getWeight() > 0.5));
for (Apple apple : appleFilterList)
System.out.println(apple.getColor() + " apple,weight:" + apple.getWeight());
输出:
red apple,weight:0.6
red apple,weight:1.3
green apple,weight:0.2
green apple,weight:0.35
使用Lambda表达式消除了大量的样板代码,并且可以灵活的构造筛选条件!
以上是关于Java8实战Lambda表达式的主要内容,如果未能解决你的问题,请参考以下文章
《Java8实战》读书笔记07:Lambda 重构测试和调试(设计模式实现)
《Java8实战》读书笔记07:Lambda 重构测试和调试(设计模式实现)