Lambda 表达式

lambda
什么是 Lambda 表达式,我觉得是我们开始学习 Lambda 表达式之前应该要弄清楚的一个概念。我们可以把 Lambda 表达式理解为简洁地表示可以传递的匿名函数的一种方式:它没有名称,但它有参数列表、函数主体、返回类型,可能还有一个可以抛出的异常列表。[1]
简单的说 Lambda 有以下的特点:
1.匿名,它没有一个明确的名字。
2.函数,是的, Lambda 表达式确实是一种函数,这毋庸置疑。
3.传递,Lambda 表达式可以作为参数传递给方法。
4.简洁,没有什么比 Lambda 更加简洁的语法了(Java 中)

Lambda 管中窥豹

概念说完我们就开始我们的代码之旅——Lambda 的语法:
(paramenters) -> expression
或者
(paramenters) -> { statements; }

Lambda 参数 + 箭头 + Lambda 主体 构成。在 Java8 中 Lambda 表达式的体现形式有以下 5 中:
1.(String s) -> s.length()
这个 Lambda 表达式具有一个 String 类型的参数并且返回一个 Int。[2]

2.(Apple a) -> a.getWeight() > 150
这个 Lambda 表达式具有一个 Apple 类型的参数并且返回一个 Boolean。

3.(int x, int y) -> (System.out.println(x + y))
这个 Lambda 表达式具有一个 String 类型的参数然而它并没有返回值(void 返回)。

4.() -> 93
这个 Lambda 表达式没有参数,返回一个 Int。

5.(Apple a, Apple b) -> a1.getWeight().compareTo(a2.getWeight())
最后一个 Lambda 表达式具有两个 Apple 类型的参数,返回一个 Int。

在哪里以及如何使用 Lambda?

我们刚刚已经了解了 Lambda 表达式的语法以及基本的 5 中表现形式,那么应该在哪里使用 Lambda 表达式以及如何使用 Lambda 表达式呢?一般我们可以在函数式接口[3]上使用 Lambda 表达式。附录 A 列举了 JDK 8之前已有的函数式接口。

我们最常用的应该是 java.lang.Runnable 接口了,在 Java8 之前要开启一个多线程,那么必定会写以下这段代码:

Runnable run = new Runnable(){
    public void run(){
        System.out.println("Start a  new Thread");
    }
};

按照正常的缩进,我们开启一个线程最少需要写 5 行代码,其中只有一行使我们的主要核心,其他 4 行代码都是为之服务的,或者称之为样板式代码。有点像 JDBC 数据库连接池一样,先要创建连接对象...最后关闭连接池。而使用 Lambda 表达式可以有效的缩短代码的行数,并且让代码写的更加的清晰。

Runnable run = () -> System.out.println("Start a  new Thread");

没错,就这么简单,一切显得是那么的优雅和华丽,没有什么多余的样板式代码,一眼就能够看代码的意图。再也不用主动过滤掉无用的代码了,应为留下来的都是有用的代码。可能你一时还不习惯这样的方式或是写法,没关系接着往下,会有更多的列子和惊喜。

刚刚我们用函数式接口来使用 Lambda 表达式,还有一种方式是使用函数描述符——函数式接口的抽象方法基本上就是 Lambda 表达式的签名。就拿刚刚线程的代码来说,Runnable 接口可以看做一个什么都没有返回的的函数签名,应为它只有一个 run 的抽象方法,这个方法既不接受参数也不返回参数。

public void process(Runnable r){
    r.run();
}

process(() -> System.out.println("Start a  new Thread"));

来一个栗子

废话不多说,先上代码:

public class ExecuteAround {

	public static void main(String ...args) throws IOException{

        // method we want to refactor to make more flexible
        String result = processFileLimited();
        System.out.println(result);

        System.out.println("---");
        
        // 使用 Lambda 表达式改进
        String oneLine = processFile((BufferedReader b) -> b.readLine());
        System.out.println(oneLine);

        String twoLines = processFile((BufferedReader b) -> b.readLine() + b.readLine());
        System.out.println(twoLines);

	}

    public static String processFileLimited() throws IOException {
        try (BufferedReader br =
                     new BufferedReader(new FileReader("data.txt"))) {
            return br.readLine();
        }
    }


	public static String processFile(BufferedReaderProcessor p) throws IOException {
		try(BufferedReader br = new BufferedReader(new FileReader("data.txt"))){
			return p.process(br);
		}

	}

	public interface BufferedReaderProcessor{
		public String process(BufferedReader b) throws IOException;

	}
}

这个例子就是我们平时处理资源的一段代码,我们打开一个资源,做一些处理(读啊,写啊),然后关闭资源。这里对比了使用传统方式和使用 Lambda 表达式的方式来写这一段代码。这里可以看出使用 Lambda 表达式可以使代码更加灵活,在这段代码中的提现是:可以由传入参数的不同而决定方法的实现。

使用函数式接口

正如前面说的,函数式接口定义且只定义了一个抽象方法。我们在 JDK8 之前常用的如 Comparable、Runnable 和 Callable 等,在 Java8 中伟大的设计师又引进了几个新的函数式接口,这里介绍其中的一些作为了解。

Predicate

java.util.function.Predicate 接口定义了一个名叫 test 的抽象方法,它接受泛型 T 对象,并返回一个 boolean。

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

public static List<Apple> filter(List<Apple> inventory, ApplePredicate p){
		List<Apple> result = new ArrayList<>();
		for(Apple apple : inventory){
			if(p.test(apple)){
				result.add(apple);
			}
		}
		return result;
}   

List<Apple> greenApples = filter(inventory, (Apple a) -> "green".equals(a.getColor()));
System.out.println(greenApples);

Consumer

java.util.function.Consumer 接口定义了一个名叫 accept 的抽象方法,它接受泛型 T 对象,但并没有返回值。如果需要访问类型 T 的对象,并对其执行某些操作,就可以使用这个接口。比如循环一个对象。

@FunctionalInterface
public interface Consumer<T>{
    void accept(T t);
}

public static <T> forEach(List<T> list, Consumer<T> c){
    for(T i: list){
        c.accept(i);
    }
}

List<int> list = Arrays.asList(1, 2, 3, 4, 5);
//Lambda 是 Consumer 中 accept 方法的实现
forEach(list, (Integer i) -> System.out.println(i))

Function

java.util.function.Function<T, R> 接口定义了一个叫做 apply 的方法,该方法接受一个泛型的 T 对象,返回一个泛型的 R 对象。如果你需要一个 Lambda 表达式将对象的信息映射到输出,那么久可以使用这个接口。

@FunctionalInterface
public interface Function<T, R>{
    R apply(T t);
}

public static <T, R> List<R> map(List<T> list, Function<T, R> f){
    List<R> result = new ArrayList<>();
    for(T s: list){
        result.add(f.apply(s));
    }
    return result;
}

List<int> list = Arrays.asList("java8", "in", "action");
List<Interger> listFun = map {
    list,(String s) -> s.length();
};

附录A

JDK 8之前已有的函数式接口:
java.lang.Runnable
java.util.concurrent.Callable
java.security.PrivilegedAction
java.util.Comparator
java.io.FileFilter
java.nio.file.PathMatcher
java.lang.reflect.InvocationHandler
java.beans.PropertyChangeListener
java.awt.event.ActionListener
javax.swing.event.ChangeListener

本文由个人 hexo 博客 co2fe.com 迁移
date: 2017-07-16 22:03:37


  1. Java8 实战中对 Lambda 的描述。 ↩︎

  2. 注意 Lambda 没有 return 语句,其本身隐含了一个 return,故不用显示声明出来。 ↩︎

  3. 函数式接口(Functional Interface)是Java 8对一类特殊类型的接口的称呼。 这类接口只定义了唯一的抽象方法的接口。 ↩︎

posted @ 2018-12-28 15:32  LiuChengloong  阅读(171)  评论(0编辑  收藏  举报