Java Lambda 表达式 -- 快速入门篇

引言

在这篇文章,我们将学习 Lambda 表达式以及 Lambda 表达式与函数式接口的使用,泛型函数式接口,并演示流式传输 API。

Lambda 表达式是在 Java 8 中首次引入的,它的主要目的是提高编程语言的表达能力,简化冗余代码。

不过,在进入深入学习匿名函数之前,我们首先需要了解函数式接口。

什么是函数式接口?

如果一个 Java 接口包含一个且只有一个抽象方法,那么它被称为函数式接口。这样只有有一个方法规定了接口的预期结果类型。

示例 1:在 Java 中定义一个函数式接口

@FunctionalInterface
public interface DemoInterface {
    // 单一抽象方法
    Integer getValue();
}

在上面的例子中,接口 DemoInterface 只有一个抽象方法 getValue()。因此,它是一个功能接口。 在这里,我们使用了注解 @FunctionalInterface ,此注解的作用是强制 Java 编译器指示此接口为函数式接口。

在 Java 7 中,函数式接口被认为是 Single Abstract Method 或 SAM 类型,在 Java 7中,SAM通常用匿名类来实现。

示例 2:在 Java 中使用匿名类实现 SAM
 

public class FunctionInterfaceTest {
    public static void main(String[] args) {
        // 匿名类
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("我刚刚实现了 Runnable 的函数接口。");
            }
        }).start();
    }
}

输出:

我刚刚实现了 Runnable 的函数接口。

 

在这里,我们可以将匿名类传递给方法。这有助于在 Java 7 中使用更少的代码编写程序。但是,语法仍然很困难,并且需要大量冗余的代码行。

Java 8 进一步扩展了 SAM 的功能。由于我们知道函数式接口只有一个方法,因此在将其作为参数传递时不需要定义该方法的名称。 Lambda 表达式恰好可以做到这一点。

Lambda 表达式简介

Lambda 表达式本质上是一个匿名或未命名的方法。Lambda 表达式不会自行执行。相反,它用于实现由函数式接口定义的方法。

如何在 Java 中定义 Lambda 表达式?

以下是我们如何在 Java 中定义 Lambda 表达式。
 

(参数集合) -> { Lambda 结构体 }

使用的新运算符 -> 称为 箭头运算符 或 Lambda 运算符 。或许语法可能说不太清楚。让我们体验一些示例:

假设,我们有一个这样的方法:
 

String getHubUrl() {
    return "https://github.com";
}

我们可以使用 Lambda 表达式将此方法编写为:
 

() -> "https://github.com";

这里,该方法没有任何参数。因此,运算符的左边包括一个空参数。右边是λ主体,指定λ表达式的动作。在这种情况下,它返回值 https://github.com 。

Lambda 方法体的类型

在 Java 中,Lambda 主体有两种类型。

1. 单一表达式的主体
 

() -> System.out.println("Lambdas test.");

 2. 由代码块组成的主体
 

() -> {
    String sameSexUrl = "https://github.com";
    return sameSexUrl;
};

这种类型的 Lambda 主体称为代码块。代码块允许 ambda 体包含多个语句。这些语句包含在大括号内,必须在大括号后添加一个分号。

笔记:对于块体,如果代码块返回值,则需要有一个 return 返回语句。但是,单一表达式主体不需要 return 语句。

示例 3: Lambda 表达式

让我们编写一个使用 Lambda 表达式返回 一个网址 的 Java 程序。

如前面提到的,一个 Lambda 表达式并不能自行执行。确切的说,它构成了函数式接口所定义的抽象方法的实现。

所以,我们需要先定义一个函数式接口:
 

@FunctionalInterface
public interface DemoInterface {
    // 单一抽象方法
    String getUrl();
}
public class DemoLambda {
    public static void main(String[] args) {
        // 声明对 DemoInterface 的引用
        DemoInterface demoInterface;
        // Lambda 表达式
        demoInterface = () -> "https://www.github.com";
        System.out.println("同行交流网站: " + demoInterface.getUrl());
    }
}

输出:
 

同行交流网站: https://www.github.com
  • 在上述的例子中,我们创建了一个名为 DemoInterface 的函数式接口。它包含一个名为 getUrl() 的抽象方法。
  • 在 DemoLambda 类中,我们声明了对 DemoInterface 的引用。请注意,我们可以声明接口的引用,但不能实例化接口。比如:
    // 会抛出错误
    DemoInterface ref = new DemoInterface();
    // 正确编译
    DemoInterface ref;

    然后我们为引用分配了一个单一的 Lambda 表达式。
     

    demoInterface = () -> "https://www.github.com";

    最后,我们使用引用接口调用方法 getUrl() ,在如下时刻:
     

    System.out.println("同行交流网站: " + demoInterface.getUrl());

    带参数的 Lambda 表达式

    到目前为止,我们已经创建了没有任何参数的 Lambda 表达式。但是,与调用方法类似,Lambda 表达式也可以有参数。例如:
     

    (a,b) -> a + b

    如上代码,括号内的变量 a 与 b 是传递给 Lambda 表达式的参数。 Lambda 主体接受参数并对它们进行相加。

    示例 4:使用带参数的 Lambda 表达式
     

    import java.util.List;
    @FunctionalInterface
    public interface ParamInterface {
        // 抽象方法
        List<String> getStrList(String str);
    }
    import java.util.Arrays;
    public class ParamMain {
        public static void main(String[] args) {
            // 声明对 ParamInterface 的引用
            // 将 lambda 表达式分配给引用
            ParamInterface ref = (str) -> {
                String[] array = str.split("-");
                return Arrays.asList(array);
            };
            // 调用接口的方法
            System.out.println(ref.getStrList("l-m-b-d-a").toString());
        }
    }

    输出:
     

    [l, m, b, d, a]

    通用函数式接口

    到现在为止,我们使用的是只接受一种类型的值的函数式接口。比如说:
     

    @FunctionalInterface 
    public interface DemoInterface { 
        // 单一抽象方法 
        String getUrlByText(String text); 
    }

    上面的功能接口只接受 String 类型并返回 String 类型。但是,我们可以使这个函数接口通用化,这样就可以接受任何数据类型。如果你对泛型不确定,请先忽略,之后搜索 Java泛型 学习下后再看也不迟。

    示例5:泛型函数式接口与 Lambda 表达式
     

    @FunctionalInterface
    public interface GenericsInterface<T> {
        // 泛型方法
        T func(T t);
    }
    public class GenericsMain {
        public static void main(String[] args) {
            // 声明对 GenericsInterface 的一个引用, 针对字符进行操作
            // 为其分配一个 lambda 表达式
            GenericsInterface<String> ref = (str) ->  str.toUpperCase();
            System.out.println("lambda 转为大写: " + ref.func("lambda"));
            // 声明对 GenericsInterface 的另一个引用, 针对整数进行操作
            // 为其分配一个 lambda 表达式
            GenericsInterface<Integer> ref2 = (n) -> n * 100;
            System.out.println("5 * 100 = " + ref2.func(5));
        }
    }

    输出:
     

    lambda 转为大写: LAMBDA
    5 * 100 = 500

    在上面的例子中,我们创建了一个名为 GenericsInterface 的泛型函数接口。它包含一个名为 func() 的泛型方法。

    这里,在 GenericsMain 类里面:
     

    GenericsInterface<String> ref
    GenericInterface<Integer> ref2

    Lambda 表达式和 Stream API

    JDK8 中加入了新的 java.util.stream 包,它允许java开发者对集合进行搜索、过滤、映射、还原等操作。

    例如,我们有一个数据流(在我们的例子中是一个字符串列表),每个字符串是省市名和地区名的组合。现在,我们可以处理这个数据流,只检索上海的地方。

    为此,我们可以通过 Stream API 和 Lambda表达式 的组合在流中进行批量操作。

    示例6: 在 Stream API 中使用 Lambda 操作的演示
     

    import java.util.ArrayList;
    import java.util.List;
    
    public class StreamTest {
        // 使用 ArrayList 创建集合对象
        static List<String> regionList = new ArrayList<String>() {{
            add("上海市,青浦区");
            add("上海市,闵行区");
            add("北京市,朝阳区");
            add("北京市,海淀区");
        }};
        public static void main(String[] args) {
            System.out.println("来自上海的地区:");
            regionList.stream()
                    .filter((p) -> p.startsWith("上海"))
                    .map((p) -> p.split(",")[1])
                    .sorted()
                    .forEach((p) -> System.out.println(p));
        }
    }

    输出:
     

    来自上海的地区:
    闵行区
    青浦区

    在上面的例子中,特别注意如下代码:
     

    regionList.stream()
            .filter((p) -> p.startsWith("上海"))
            .map((p) -> p.split(",")[1])
            .sorted()
            .forEach((p) -> System.out.println(p));

    在这里,我们使用的是 Stream API 的 filter() 、 map() 和 forEach() 等方法。这些方法可以接受一个 Lambda 表达式作为输入。

  • 我们可以根据上面学到的语法定义我们自己的表达式。
  • 这使我们能够大幅减少代码行数,正如我们在上面的例子中看到的那样。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值