Lambda表达式

为什么使用Lambda 表达式

Lambda 是一个 匿名函数,我们可以把Lambda表达式理解为是 一段可以传递的代码(将代码像数据一样进行传递)。可以写出更简洁、更灵活的代码。作为一种更紧凑的代码风格,使Java的语言表达能力得到了提升

什么时候可以写Lambda表达式:是个接口,并且接口里需要重写的方法只有一个

Lambda 表达式

举些例子,比如给按钮添加点击事件、或者创建一个新线程执行操作,必须要自己new 接口并且编写接口的定义和实现代码。

Lambda 表达式的出现,让代码变得简洁优雅,告别匿名内部类!

Lambda 表达式语法

Lambda 表达式在Java 语言中引入了一个新的语法元素和操作符。这个操作符为 “- >”, 该操作符被称为 Lambda 操作符或箭头操作符。它将 Lambda 分为两个部分:

左侧 :指定了 Lambda 表达式需要的所有参数

右侧 :指定了 Lambda 体,即 Lambda 表达式要执行的功能。

类型推断

上述 Lambda 表达式中的参数类型都是由编译器推断得出的。Lambda 表达式中无需指定类型,程序依然可以编译,这是因为 javac 根据程序的上下文,在后台推断出了参数的类型。Lambda 表达式的类型依赖于上下文环境,是由编译器推断出来的。这就是所谓的 “类型推断”

函数式接口

什么是函数式接口

  • 只包含一个抽象方法的接口,称为函数式接口。

  • 你可以通过 Lambda 表达式来创建该接口的对象。(若 Lambda表达式抛出一个受检异常,那么该异常需要在目标接口的抽象方法上进行声明)。

  • 我们可以在任意函数式接口上使用@FunctionalInterface 注解,这样做可以检查它是否是一个函数式接口,同时 javadoc 也会包含一条声明,说明这个接口是一个函数式接口。

Java 内置四大核心函数式接口

自定义函数式接口

虽然实际开发中,我们更多的是使用Java 内置的函数式接口,但大家还是要了解一下自定义函数式接口的写法,有个印象。

注意

  • 函数式接口必须是接口类型,不能是类、抽象类或枚举。

  • 必须且只能包含一个抽象方法。否则 Lambda 表达式可能无法匹配接口。

  • 建议使用 @FunctionalInterface 注解。虽然这个注解不是强制的,但加上后编译器会帮你检査是否符合函数式接口的规范(是否只有一个抽象方法),如果不符合会报错。

  • 可以包含默认方法 default 和静态方法 static:函数式接口允许有多个默认方法和静态方法,因为它们不是抽象方法,不影响单一抽象方法的要求。

方法引用与构造器引用

方法引用

当要传递给Lambda体的操作,已经有实现的方法了,可以使用方法引用!(实现抽象方法的参数列表,必须与方法引用方法的参数列表保持一致!)方法引用:使用操作符 “ ::” 将方法名和对象或类的名字分隔开来。

如下三种主要使用情况 :

  • 类 :: 静态方法

  • 类 :: 实例方法 (当需要引用方法的第一个参数是调用对象, 并且第二个参数是需要引用方法的第二 个 参数( ( 或无参数) ) 时可以使用)

  • 对象 :: 实例方法

  • 指向静态方法的引用

  • 指向某个对象的实例方法的引用

  • 指向某个类型的实例方法的引用

  • 指向构造方法的引用

构造器引用

格式 : ClassName::new

与函数式接口相结合,自动与函数式接口中方法兼容。可以把构造器引用赋值给定义的方法,与构造器参数列表要与接口中抽象方法的参数列表一致!

数组引用

格式 : type[] :: new

强大的stream

了解Stream

Java8中有两大最为重要的改变。第一个是 Lambda 表达式;另外一个则是Stream API(java.util.stream.* ) 。Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。

但需要注意的是,Stream的中间操作(比如map、filter等)是惰性的,这意味着它们在终端操作(比如collect、 forEach等)被调用之前不会实际执行。

什么是Stream

流) (Stream) 到底是什么呢?是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。“ 集合讲的是数据, 流讲的是 计 算 ! ”

注意 :

①Stream 自己不会存储元素。

②Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。

③Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。

详情可以看这篇文章 Stream流原理

接口中的默认方法与静态方法

接口中的默认方法

Java 8 引入的接口默认方法解决了接口演化的问题。

在默认方法出现之前,如果你想给一个被广泛使用的接口添加新方法,就会影响所有已有的实现类。想象一下,如果要给 Collection 接口添加一个新方法,ArrayList、LinkedList 等所有的实现类都需要修改,成本很大。默认方法让接口可以在 不破坏现有代码的情况下添加新功能。

举个例子,如果想要给接口增加一个 drawwithBorder 方法

使用默认方法后,实现类可以选择重写默认方法,也可以直接使用:

Java8为 Collection 接口添加了 stream、removelf 等方法,都是默认方法: 需要注意的是,如果一个类实现多个接口,并且这些接口有相同的默认方法时, 需要显式解决冲突:

为什么要有默认方法:

  1. 向后兼容性:在Java 8之前,如果你向一个接口添加方法,那么所有实现了该接口的类都必须修改,以实现新增的方法。这在大型项目中是不现实的,因为这会破坏现有的实现。通过使用default方法,你可以向接口中添加新方法,而无需改变实现该接口的类。

  2. 代码复用:default方法允许在接口内部提供方法的实现,从而使得代码复用变得可能。这意味着如果多个实现类可以共享某个方法的实现逻辑,那么这个逻辑就可以作为一个default方法放在接口中,避免了在每个实现类中重复此逻辑。

  3. 多继承的灵活性:尽管Java不允许类多重继承(以避免多重继承的复杂性和歧义),但通过default方法,Java可以在接口层面上提供类似多重继承的能力。一个类可以实现多个接口,并且每个接口可以通过default方法提供具体实现,这给Java增加了额外的灵活性和功能。

  4. 弥补抽象类的不足:在Java 8之前,如果你想为一组类提供公共实现,通常的方法是使用抽象类。然而,由于Java不支持从多个类继承,这种方法的使用非常受限。default方法提供了一种机制,通过它接口可以提供实现,而不需要抽象类,同时也不受单继承的限制。

接口中的静态方法

Optional 类

Optional<T> 类(java.util.Optional) 是一个容器类,代表一个值存在或不存在,原来用 null 表示一个值不存在,现在 Optional 可以更好的表达这个概念。并且可以避免空指针异常

之前,我们只能通过大量的 if 语句检査 nul 来避免空指针异常,不仅代码又臭又长,而且稍微不注意就漏掉了。

Optional 类的引入就是为了优雅地处理可能为空的值,可以先把它理解为"包装器",把可能为空的对象封装起来。

创建 Optional 对象

常用方法 :

  • Optional.of(T t) : 创建一个 Optional 实例

  • Optional.empty() : 创建一个空的 Optional 实例

  • Optional.ofNullable(T t):若 t 不为 null,创建 Optional 实例,否则创建空实例

  • isPresent() : 判断是否包含值

  • orElse(T t) : 如果调用对象包含值,返回该值,否则返回t

  • orElseGet(Supplier s) :如果调用对象包含值,返回该值,否则返回 s 获取的值

  • map(Function f): 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()

  • flatMap(Function mapper):与 map 类似,要求返回值必须是Optional

新时间日期API

传统的日期处理方式

使用新的日期时间 API,代码会更简洁

典型的应用场景是从字符串解析日期,一行代码就能搞定:

还有日期和时间的计算,也变得更直观、见名知意:

还支持时区处理和时间戳处理,不过这段代码就没必要记了,现在有了 AI,直接让它生成时间日期操作就好。

重复注解

Java8之前,同一个注解不能在同一个地方重复使用。如果需要多个相似的配置,只能使用数组形式的注解:

Java8引入了重复注解特性,让同一个注解可以在同一个地方多次使用:

要实现重复注解,需要定义一个容器注解:

然后在:@Schedule 注解上使用@Repeatable 指定容器注解:

类型注解

Java8 扩展了注解的使用范围,现在注解可以用在任何使用类型的地方,包括:

类型注解主要用于静态分析工具(如 Checker Framework)进行更精确的类型检查,帮助发现潜在的空指针异常、并发问题等。