Lambda表达式 函数式编程 Stream API学习笔记

简介: Lambda表达式 函数式编程 Stream API学习笔记

什么是Lambda表达式

我们知道接口是无法直接被实例化的,你只能new他的实现类,但是假如当前接口只有一个抽象方法,又不想再创建一个类,在JDK8之前,你就只能使用匿名内部类。像下面这样:

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello world");
            }
        }).start();

现在的IDE已经很强大了,你只用写到new Runnable()就会帮你把代码补全,但是你还是要移动光标到run方法体内,写对应的代码。在学到匿名内部类的时候,我就希望代码变的简洁紧凑一些 (事实上官方写的指导书《The Java™ Tutorials》也是将Lambda式放在匿名类这一章下面的,介绍Lambda表达式这一章在: https://docs.oracle.com/javase/tutorial/java/javaOO )。JDK8之后上面的代码就可以化简成下面这样:

new Thread(()-> System.out.println("hello world")).start();

怎么样是不是变得相当简洁了呢! ()-> System.out.println("hello world") 就可以称之为Lambda表达式。Lambda是λ这个符号的名称。维基百科是这样描述Lambda表达式的:

a function (or a subroutine) defined, and possibly called, without being bound to an identifier。

直接翻译的话就是: 可能被调用的一个函数或者是子程序,但是没有绑定到一个标识符上。

我们放到java里来理解一下,众所周知java是面向对象的,函数也是放在一个类中的。调用方法的话就大致有以下两种形式:

  • 类名.方法名
  • 类的实例.方法名。

所以在java中,我们姑且就可以将类名+方法名称之为一个标识符,因为我们通过类名(严谨的说是全类名)+方法名定位到一个方法。()-> System.out.println("hello world"),就是一个匿名类和匿名方法的组合化简之后产物,类名没有直接给出,方法名也是没有,这些通通是编译器来给。

百度百科是这样描述Lambda表达式的:

Lambda 表达式(lambda expression)是一个匿名函数,Lambda表达式基于数学中的λ演算得名,直接对应于其中的lambda抽象(lambda abstraction),是一个匿名函数,即没有函数名的函数。--《百度百科》

我们知道数学中的演算是需要运算符(演算符的),调用一个函数是需要给定参数的,是需要方法体的。

() 里面就可以放参数,-> 是运算符,-> 之后就是方法体。形如()->{}的表达式,在java中我们就可以称之为Lambda表达式。因为Runnable接口中的run方法没有参数,所以()里面啥也没写。

顺便提一下,Lambda表达式并不算是java率先提出。2007年11月19,.NET Framework 3.5就已经引入了,jdk8的发布时间是2014-03-18。主流的编程语言应该都有这个概念。

我们来想想为什么可以化简到这种地步,假如我们有这样两个方法:

  • add(int i)
  • add(Long i)

我们称之为方法重载,同一个方法名,参数类型不同。我们在调用的时候,编译器或者说是JVM,就能根据我们给定的参数来调用对应的方法。那么new Thread(()-> System.out.println("hello world"))来推断这是Thread的哪一个构造函数也是能够做到的事情。所以看似你写的是这个:

new Thread(()-> System.out.println("hello world")).start();

事实上编译器会帮你补成:

new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("hello world");
            }
        }).start();

这也是一些人说在某些情况下Lambda性能不佳的原因。

适用场景

如果你打开Runnable接口看的话,会发现Runnable接口上有一个注解: @FunctionalInterface。FunctionalInterface意味函数式接口,什么是函数式接口呢?就是一个接口中只有一个抽象方法的接口,我们称之为函数式接口。我们知道,jdk8之前接口中只能放抽象方法,jdk8之后可以放非抽象方法。方法中要加上default关键字,我们称之为默认实现。形式如下:

@FunctionalInterface
public interface MyLambdaInterface {
    void sayHello(String str);
    default void sayWorld(String str){
        System.out.println(str);
    }
}

加上@FunctionalInterface的接口,只能有一个抽象方法,允许存在和Object中方法名相同的方法,但要求参数类型一致。否则会通不过编译。如下所示:

@FunctionalInterface
public interface MyLambdaInterface {
    void sayHello(String str);
    int hashCode();
    boolean equals(Object obj);
    default void sayWorld(String str){
        System.out.println(str);
    }
}

Lambda表达式的适用场景即为: 假如我有一个函数式接口,但是呢,我又不想创建这个类的显示实现类(显示实现类即为新建Class文件),我又想使用这个方法,Lambda表达式大显身手的就到了。

如何使用以及 Lambda表达式的几种形式

我们首先介绍Lambda表达式的形式,在根据形式去讲解如何使用。

  • 方法参数 Lambda从形式上来看更像是一个函数,(函数所需要的参数)->{方法实现}

方法参数的话 new Thread(()-> System.out.println("hello world")).start(); 这个就是一个例子。虽然原则上讲并不应该直接创建一个线程,应该通过线程池。我们知道函数的参数类型可以是基本类型和引用类型,new Thread(()-> System.out.println("hello world")) 这个例子好像就是将一个方法作为参数传递进去了一样。

  • 方法引用的形式一:  指向自己创建的方法 以MyLambdaInterface为例,方法引用我们可以这么写:
MyLambdaInterface myLambdaInterface = (str)-> System.out.println(str);

从形式上来讲,myLambdaInterface好像指向一个函数一样。(str)-> System.out.println(str);就是方法sayHello的实现。

  • 方法引用的形式二: 指向已存在的方法 指向已存在的方法的语法为:
  • Reference to a static method(引用一个静态方法):
public class Person {
    LocalDate birthday;
    public static int compareByAge(Person a, Person b) {
        return a.birthday.compareTo(b.birthday);
    }
}

方法引用的写法: Person::compareByAge

但是这句话直接出现在代码中会报错,我们需要找一个函数式接口来指向它。函数式接口中的方法返回类型和接收参数要和compareByAge保持一致,像下面这样。

public interface CompareTo<T> {
    int compare(T t1,T t2);
}
CompareTo<Person> p = Person::compareByAge;
  • Reference to an instance method of an arbitrary object of a particular type(对特定类型的实例方法的调用)
String[] stringArray = {"Barbara", "James", "Mary", "John",
                "Patricia", "Robert", "Michael", "Linda"};
        Arrays.sort(stringArray, String::compareToIgnoreCase);
  • Reference to an instance method of a particular object (对特定对象的实例方法的调用) 首先你需要new一个对象, 形式就是 函数式接口 = 对象::方法 或者将 对象::方法当做一个方法参数传递进去。
  • Reference to a constructor (引用构造函数) 形式: 类名:: new。用法和前面三种是一致的,可以当做一个函数式接口的实现,也可以当做一个方法参数。

Lambda表达式本质上也是内部类,那么同内部类一样,要想要访问局部变量,那么这个局部变量一定要是final类型的。现在我们来总结一下Lambda表达式的写法: (函数式接口所需的参数)->方法体; 假如方法体只有一行,可以不用写{ 例如:

new Thread(()-> System.out.println()).start();

假如有多行的话需要写{

我们可以认为每一个Lambda表达式都是一个函数式接口的匿名实现。

JDK提供的函数式接口

JDK也内置了一些函数式接口,这些函数式接口大多都在java.util.function下,理解这些函数式接口是学习Stream API的关键。这里我们选取几个比较经典的讲解一下。

  • Predicate 断言中抽象方法为:  boolean test(T t);
  • Consumer 消费,抽象方法为:  void accept(T t);
  • Function<T, R> 函数式,抽象方法为:  R apply(T t);‘
  • Supplier供给型,抽象方法为:  T get();

boolean test(T t); 这个方法接收一个参数,返回一个boolean类型的值。

我们可以这样写:

Predicate<String> predicate = (str)-> str.contains("s");

还可以这样写:

List<String> list = new ArrayList<>();
  Predicate<String> predicate = list::add;
  predicate.test("add");//等价于调用list.add("add")
  predicate.test("bb");

Consumer、 Function、 Supplier用法类似,这里不再做过多的介绍。

由函数式编程到 Stream API

首先Stream是一个接口,位于java.util.stream下。假如我们的集合泛型是一个学生类,像下面这样。

List<Student> student = new ArrayList<>();

那么假如我像统计年龄在18岁以上的学生总数,那么在不用流的情况下,我们通常是这样做的。

int count = 0;
    List<Student> studentList = new ArrayList<>();
    for (Student student : studentList) {
        if (student.getAge() > 18){
            count++;
        }
    }

Student可以映射进数据库中,我们在数据库做这样的事情是简单的,那么在代码能否像sql一样执行count,where操作呢。有了流之后就可以,我们可以这样写:

studentStream.filter( (student) -> student.getAge() > 18).count();

是不是变的简单多了呢! 那我能不能group by 在count呢,也是可以的。按性别分组求出男生和女生的人数。

Map<Boolean, Long> result = studentStream.collect(Collectors.groupingBy(Student::isSex, counting()));

细究他的实现的话,这个并不是本篇的主题,本篇只是简单入门,详细讨论是另一篇了。顺便提一下, 流有两种类型,串行和并行流,这将是我们下一篇文章重点阐释的内容。

相关文章
|
3天前
|
存储 Java 数据挖掘
Java 8 新特性之 Stream API:函数式编程风格的数据处理范式
Java 8 引入的 Stream API 提供了一种新的数据处理方式,支持函数式编程风格,能够高效、简洁地处理集合数据,实现过滤、映射、聚合等操作。
|
3天前
|
Java API 开发者
Java中的Lambda表达式与Stream API的协同作用
在本文中,我们将探讨Java 8引入的Lambda表达式和Stream API如何改变我们处理集合和数组的方式。Lambda表达式提供了一种简洁的方法来表达代码块,而Stream API则允许我们对数据流进行高级操作,如过滤、映射和归约。通过结合使用这两种技术,我们可以以声明式的方式编写更简洁、更易于理解和维护的代码。本文将介绍Lambda表达式和Stream API的基本概念,并通过示例展示它们在实际项目中的应用。
|
5天前
|
安全 Java API
Java中的Lambda表达式与Stream API的高效结合####
探索Java编程中Lambda表达式与Stream API如何携手并进,提升数据处理效率,实现代码简洁性与功能性的双重飞跃。 ####
18 0
|
1月前
|
Java API 数据处理
探索Java中的Lambda表达式与Stream API
【10月更文挑战第22天】 在Java编程中,Lambda表达式和Stream API是两个强大的功能,它们极大地简化了代码的编写和提高了开发效率。本文将深入探讨这两个概念的基本用法、优势以及在实际项目中的应用案例,帮助读者更好地理解和运用这些现代Java特性。
|
17天前
|
JSON API 数据格式
淘宝 / 天猫官方商品 / 订单订单 API 接口丨商品上传接口对接步骤
要对接淘宝/天猫官方商品或订单API,需先注册淘宝开放平台账号,创建应用获取App Key和App Secret。之后,详细阅读API文档,了解接口功能及权限要求,编写认证、构建请求、发送请求和处理响应的代码。最后,在沙箱环境中测试与调试,确保API调用的正确性和稳定性。
|
29天前
|
供应链 数据挖掘 API
电商API接口介绍——sku接口概述
商品SKU(Stock Keeping Unit)接口是电商API接口中的一种,专门用于获取商品的SKU信息。SKU是库存量单位,用于区分同一商品的不同规格、颜色、尺寸等属性。通过商品SKU接口,开发者可以获取商品的SKU列表、SKU属性、库存数量等详细信息。
|
1月前
|
JSON API 数据格式
店铺所有商品列表接口json数据格式示例(API接口)
当然,以下是一个示例的JSON数据格式,用于表示一个店铺所有商品列表的API接口响应
|
2月前
|
编解码 监控 API
直播源怎么调用api接口
调用直播源的API接口涉及开通服务、添加域名、获取API密钥、调用API接口、生成推流和拉流地址、配置直播源、开始直播、监控管理及停止直播等步骤。不同云服务平台的具体操作略有差异,但整体流程简单易懂。
|
20天前
|
JSON API 数据安全/隐私保护
拍立淘按图搜索API接口返回数据的JSON格式示例
拍立淘按图搜索API接口允许用户通过上传图片来搜索相似的商品,该接口返回的通常是一个JSON格式的响应,其中包含了与上传图片相似的商品信息。以下是一个基于淘宝平台的拍立淘按图搜索API接口返回数据的JSON格式示例,同时提供对其关键字段的解释
|
2月前
|
人工智能 自然语言处理 PyTorch
Text2Video Huggingface Pipeline 文生视频接口和文生视频论文API
文生视频是AI领域热点,很多文生视频的大模型都是基于 Huggingface的 diffusers的text to video的pipeline来开发。国内外也有非常多的优秀产品如Runway AI、Pika AI 、可灵King AI、通义千问、智谱的文生视频模型等等。为了方便调用,这篇博客也尝试了使用 PyPI的text2video的python库的Wrapper类进行调用,下面会给大家介绍一下Huggingface Text to Video Pipeline的调用方式以及使用通用的text2video的python库调用方式。