Lambda表达式
为什么使用Lambda表达式
- Lambda是一个匿名函数,相当于匿名内部类
- 只有函数式接口(可以用@FunctionalInterface注解修饰) 可以使用Lambda表达式
- 使用 Lambda省略了写实现类的繁琐步骤,可以写出更简洁、更灵活的代码,使Java的语言表达能力得到了提升。
Lambda表达式语法
Lambda 表达式在Java 语言中引入了一个新的操作符, “->”, 它将 Lambda 分为两个部分:
左侧:指定了 Lambda表达式的参数列表
右侧:指定了 Lambda体,即 Lambda表达式要执行的功能。
语法格式一:无参数,无返回值
1 | () -> System.out.println("Hello Lambda!"); |
语法格式二:有一个参数,并且无返回值
1 | (x) -> System.out.println(x); |
语法格式三:若只有一个参数,小括号可以省略不写
1 | x -> System.out.println(x) |
语法格式四:有两个以上的参数,有返回值,并且 Lambda体中有多条语句
1 | Comparator<Integer> com = (x, y) -> { |
语法格式五:若 Lambda体中只有一条语句, return和大括号都可以省略不写
1 | Comparator<Integer> com = (x, y) -> Integer.compare(x, y); |
语法格式六:参数列表的数据类型可以省略不写
JVM编译器可以通过上下文推断出参数列表对应的数据类型,Lambda 表达式中无需指定类型,程序依然可以编译,即**“类型推断”**。
1 | // Integer推荐省略 |
语法速记口诀:
上联:左右遇一括号省
下联:左侧推断类型省
横批:能省则省
函数式接口
什么是函数式接口
- 只包含一个抽象方法的接口,称为函数式接口。
- 可以通过 Lambda 表达式来创建该接口的实现类。
函数式接口只会有一个抽象方法,default方法不属于抽象方法,接口重写了Object的公共方法也不算入内,所以Comparator是函数式接口。
Java内置函数式接口
四大核心接口
接口名 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
Consumer<T> 消费型接口 | T | void | void accept(T t); 适合“传递参数没有返回值”的场景 |
Supplier<T> 供给型接口 | 无 | T | T get(); 适合“没有参数有返回值”的场景 |
Function<T,R> 函数型接口 | T | R | R apply(T t); 适合“传递参数有返回值”的场景 |
Predicate<T> 断定型接口 | T | boolean | boolean test(T t); 适合“传递参数返回boolean值”的场景 |
其他接口
接口名 | 参数类型 | 返回类型 | 用途 |
---|---|---|---|
BiFunction<T, U, R> | T, U | R | R apply(T t, U u); 适合“传递两个参数有返回值”的场景 |
UnaryOperator<T> (Function子接口) | T | T | T apply(T t); 进行一元运算,适合“传递一个参数有返回值”的场景 |
BinaryOperator<T>(BiFunction 子接口) | T, T | T | T apply(T t1, T t2); 进行二元运算,适合“传递两个参数有返回值”的场景 |
BiConsumer<T, U> | T | void | void accept(T t, U u); 适合“传递两个参数没有返回值”的场景 |
方法引用、构造器引用、数组引用
方法引用
若Lambda 体中的功能,已经有方法提供了实现,可以使用方法引用。
可以将方法引用理解 Lambda 表达式的另外一种表现形式。
使用操作符 “::” 将方法名和对象或类的名字分隔开来。
主要有下面三种使用情况:
- 对象::实例方法
1 | // 示例1 |
- 类::静态方法
1 | // 示例1 |
- 类::实例方法
1 | // 示例1 |
注意:
- 方法引用所引用的方法的参数列表与返回值类型,需要与函数式接口中抽象方法的参数列表和返回值类型保持一致。
- 若Lambda 的参数列表的第一个参数,是实例方法的调用者,第二个参数(或无参)是实例方法的参数时,格式: ClassName::MethodName
构造器引用
构造器的参数列表,需要与函数式接口中参数列表保持一致。
语法格式:
类名 :: new
示例:
1 | Function<String, Employee> fun = Employee::new; |
数组引用
语法格式:
类型[] :: new
示例:
1 | Function<Integer, String[]> fun = (length) -> new String[length]; |
强大的Stream API
Stream 是 Java8 中处理集合的关键抽象概念,它可以指定你希望对集合进行的操作,可以执行非常复杂的查找、过滤和映射数据等操作。使用Stream API 对集合数据进行操作,就类似于使用 SQL 执行的数据库查询。也可以使用 Stream API 来并行执行操作。简而言之,Stream API 提供了一种高效且易于使用的处理数据的方式。
什么是Stream
Stream是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
集合讲的是数据,流讲的是计算!
注意:
- Stream 自己不会存储元素。
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
Stream操作的三个步骤
Stream操作的三个步骤如下:
- 创建 Stream
一个数据源(如:集合、数组),获取一个流 - 中间操作
一个中间操作链,对数据源的数据进行处理 - 终止操作(终端操作)
一个终止操作,执行中间操作链,并产生结果
示例如下:
1 | //所有的中间操作不会做任何的处理 |
创建Stream
一、Java8 中的Collection接口被扩展,提供了两个获取流的方法:
- default Stream
stream() : 返回一个顺序流 - default Stream
parallelStream() : 返回一个并行流
示例代码如下:
1 | //1、 Collection提供了两个方法 stream() 与 parallelStream() |
二、由数组创建流
Java8 中的 Arrays 的静态方法 stream() 可以获取数组流:
static
示例代码如下:
1 | //2、 通过 Arrays 中的 stream() 获取一个数组流 |
三、用静态方法Stream.of()
可以使用静态方法 Stream.of(), 创建一个流。它可以接收任意数量的参数。
public static
示例代码如下:
1 | //3、通过 Stream 类中静态方法 of() |
四、创建无限流
示例代码:
1 | //迭代 |
中间操作
多个中间操作可以连接起来形成一个流水线,除非流水线上触发终止操作,否则中间操作不会执行任何的处理!而在终止操作时一次性全部处理,称为**“惰性求值”**。
筛选与切片
常用方法如下:
方法 | 描述 |
---|---|
filter(Predicate p) | 过滤: 接收 Lambda , 从流中排除某些元素。 |
limit(long maxSize) | 截断流: 使其元素不超过给定数量。 |
skip(long n) | 跳过元素: 返回一个扔掉了前 n 个元素的流。若流中元素不足 n 个,则返回一个空流。 与 limit(n) 互补 |
distinct() | 去重: 通过流所生成元素的hashCode()和equals()去除重复元素 |
示例代码如下:
1 | // 1、filter过滤 |
映射
常用方法如下:
方法 | 描述 |
---|---|
map(Function f) | 接收一个函数作为参数,该函数会被应用到每个元素上,并将其映射成一个新的元素。 |
mapToDouble(ToDoubleFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的DoubleStream。 |
mapToInt(ToIntFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的IntStream。 |
mapToLong(ToLongFunction f) | 接收一个函数作为参数,该函数会被应用到每个元素上,产生一个新的LongStream。 |
flatMap(Function f) | 接收一个函数作为参数,将流中的每个值都换成另一个流,然后把所有流连接成一个流 |
示例代码如下:
1 | public static void main(String[] args) { |
排序
常用方法如下:
方法 | 描述 |
---|---|
sorted() | 产生一个新流,按自然顺序排序。 |
sorted(Comparator comp) | 产生一个新流,按比较器顺序排序。 |
示例代码如下:
1 | // 自然排序 |
终止操作(终端操作)
注意:
流进行了终止操作后,不能再次使用。
查找与匹配
常用方法如下:
方法 | 描述 |
---|---|
allMatch(Predicate p) | 检查是否匹配所有元素 |
anyMatch(Predicate p) | 检查是否至少匹配一个元素 |
noneMatch(Predicate p) | 检查是否没有匹配所有元素 |
findFirst() | 返回第一个元素 |
findAny() | 返回当前流中的任意元素 |
示例代码如下:
1 | boolean bl = emps.stream() |
内部迭代
方法 | 描述 |
---|---|
forEach(Consumer c) | 内部迭代 |
最值、数量
方法 | 描述 |
---|---|
count() | 返回流中元素总数 |
max(Comparator c) | 返回流中最大值 |
min(Comparator c) | 返回流中最小值 |
示例代码如下:
1 | long count = emps.stream() |
归约
方法 | 描述 |
---|---|
reduce(T iden, BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。 返回 T。 |
reduce(BinaryOperator b) | 可以将流中元素反复结合起来,得到一个值。 返回Optional |
示例代码如下:
1 | List<Integer> list = Arrays.asList(1,2,3,4,5,6,7,8,9,10); |
收集
方法 | 描述 |
---|---|
collect(Collector c) | 将流转换为其他形式。接收一个Collector接口的实现,用于给Stream中元素做汇总的方法 |
Collector 接口中方法的实现决定了如何对流执行收集操作(如收集到 List、Set、Map)。但是Collectors 实用类提供了很多静态方法,可以方便地创建常见收集器实例,具体方法与实例如下表:
示例代码如下:
1 | List<DeptInfoVo> deptInfoVoList = getDeptInfoVoData(); |
接口中的默认方法与静态方法
默认方法
Java 8中允许接口中包含具有具体实现的方法,该方法称为“默认方法”,默认方法使用 default关键字修饰。
接口默认方法的类优先原则:
一个子类继承了一个父类,同时实现了一个接口。这个父类和接口有同名(默认)的方法,则字类调用方法会优先调用父类的方法。
示例代码如下:
1 | public class ParentClass { |
当然,如果子类再重写方法的话,以子类的为准。
接口默认方法冲突的解决方法:
如果一个父接口提供一个方法,而另一个父接口接口也提供了一个具有相同名称和参数列表的方法(不管方法是否是默认方法),那么必须重写该方法来解决冲突。
示例代码如下:
1 | public interface MyInterface { |
静态方法
Java8接口中允许添加静态方法。
示例代码如下:
1 | public interface MyInterface { |
新时间日期API
简介
SimpleDateFormat有线程安全问题,且Date类不好用。
Java8提供了LocalDate、LocalTime和LocalDateTime 类,它们的实例是不可变的对象,做任何操作都会新生成一个对象,分别表示使用 ISO-8601日历系统的日期、时间、日期和时间。它们提供了简单的日期或时间,并不包含当前的时间信息。也不包含与时区相关的信息。
注:ISO-8601日历系统是国际标准化组织制定的现代公民的日期和时间的表示法
相关方法如下:
示例代码如下:
1 | LocalDateTime ldt = LocalDateTime.now(); |
Instant时间戳
Instant用于“时间戳”的运算。它是以Unix元年(传统的设定为UTC时区1970年1月1日午夜时分)开始所经历的毫秒数进行运算。
示例代码如下:
1 | // 默认使用 UTC 时区 |
Duration和Period
1、Duration: 用于计算两个“时间”间隔
2、Period:用于计算两个“日期”间隔
示例代码如下:
1 | Instant ins1 = Instant.now(); |
时间校正器
- TemporalAdjuster : 时间校正器。有时我们可能需要获
取例如:将日期调整到“下个周日”等操作。 - TemporalAdjusters: 该类通过静态方法提供了大量的常
用 TemporalAdjuster 的实现。
示例代码如下:
1 | LocalDateTime ldt = LocalDateTime.now(); |
解析和格式化日期或时间
示例代码如下:
1 | //DateTimeFormatter dtf = DateTimeFormatter.ISO_LOCAL_DATE; |
带时区的时间或日期
Java8 中加入了对时区的支持,带时区的时间为分别为:ZonedDate、ZonedTime、ZonedDateTime。其中每个时区都对应着 ID,地区ID都为 “{区域}/{城市}”的格式,
例如 :Asia/Shanghai。
示例代码如下:
1 | LocalDateTime ldt = LocalDateTime.now(ZoneId.of("Asia/Shanghai")); |
与传统日期处理的转换
相关方法如下:
Optional类
Optional
常用方法如下:
方法 | 描述 |
---|---|
Optional.of(T t) | 创建一个 Optional实例 |
Optional.empty() | 创建一个空的 Optional实例 |
Optional.ofNullable(T t) | 若 t 不为 null,创建 Optional 实例,否则创建空实例。相当于Optional.of(T t)+Optional.empty() |
isPresent() | 判断是否包含值 |
orElse(T t) | 如果调用对象包含值,返回该值,否则返回t |
orElseGet(Supplier s) | 如果调用对象包含值,返回该值,否则返回 s 获取的值 |
map(Function f) | 如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty() |
flatMap(Function mapper) | 与 map 类似,要求返回值必须是Optional |
JVM的新特性
使用元空间Metaspace代替持久代PermGen space。
在JVM参数方面,使用-XX:MetaSpaceSize和-XX:MaxMetaspaceSize代替原来的-XX:PermSize和-XX:MaxPermSize。
优化HashMap
从 “数组+链表” 数据结构改为了 “数组+(链表/红黑树)”。
当链表长度大于8,且HashMap总容量大于64,会将链表自动转为红黑树。
添加元素比链表慢,其他的都比链表更快速。
重复注解
Java8允许在同一声明类型(类,属性,或方法)的多次使用同一个注解,这就是重复注解。Java8开始注解可以应用在任何地方。
Java8之前对重复注解的支持
示例代码:
1 | public Authority { |
Java8之前,重复注解实现是由另一个注解来存储重复注解。
在使用时候,用存储注解Authorities来扩展重复注解。
很明显,这种方式可读性不是很好。
Java8对重复注解的支持
示例代码:
1 |
|
不同的地方是,创建重复注解Authority时,加上@Repeatable,指向存储注解Authorities。
在使用时候,直接可以重复使用Authority注解。
从上面例子看出,java 8里面做法更适合常规的思维,可读性强一点。
说明:本笔记整理自尚硅谷Java8新特性课程以及互联网,仅供学习使用。