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 等方法,都是默认方法:
需要注意的是,如果一个类实现多个接口,并且这些接口有相同的默认方法时,
需要显式解决冲突:
为什么要有默认方法:
向后兼容性:在Java 8之前,如果你向一个接口添加方法,那么所有实现了该接口的类都必须修改,以实现新增的方法。这在大型项目中是不现实的,因为这会破坏现有的实现。通过使用default方法,你可以向接口中添加新方法,而无需改变实现该接口的类。
代码复用:default方法允许在接口内部提供方法的实现,从而使得代码复用变得可能。这意味着如果多个实现类可以共享某个方法的实现逻辑,那么这个逻辑就可以作为一个default方法放在接口中,避免了在每个实现类中重复此逻辑。
多继承的灵活性:尽管Java不允许类多重继承(以避免多重继承的复杂性和歧义),但通过default方法,Java可以在接口层面上提供类似多重继承的能力。一个类可以实现多个接口,并且每个接口可以通过default方法提供具体实现,这给Java增加了额外的灵活性和功能。
弥补抽象类的不足:在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)进行更精确的类型检查,帮助发现潜在的空指针异常、并发问题等。