Lambda
Lambda是一种函数式编程,可以不严谨的理解为一个函数,是一套关于函数f(x)
定义、输入量、输出量的计算方案。除了输入输出以及函数内容,其他的lambda都不关心:
复制 factory = new Factory() {
@Override
public Object getObject() {
return new User();
}
}
// 只关心 输入 - () 输出 factory 内容 {}
factory = () -> {
return new User();
}
函数式编程的主要特点:
Lambda语法格式
格式1
复制 (parameters) -> {statements;}
parameters: 函数的参数列表
statements: 函数的执行语句
-> : 代表使用指定的参数去完成某项功能
public int method(int a, int b) {
return a + b;
}
(int a, int b) -> {return a + b;}
格式2
复制 (parameters) -> expression
parameters: 函数的参数列表
statements: 表达式(一定会有一个结果)
-> : 代表使用指定的参数去完成某项功能
public int method(int a, int b) {
return a + b;
}
(int a, int b) -> a + b
其他规则:
可选的大括号,当函数体只包含一个语句,可以省略大括号
复制 (int a) -> return a + 5;
可选的参数类型声明,编译器可以根据参数值进行推断
复制 interface Cal {
int cal(int a);
}
// 这里编译器可以根据接口Cal推断出lambda函数的参数类型为int,故可以省略
Cal cal = (a) -> return a + 5;
可选的return关键字,如果函数体只有一个表达式,且运算结果匹配返回类型,return可以省略
Lambda使用前提(函数式接口)
因为在Java中没有函数的概念,只有方法的概念,所有的方法一定从属于一个对象或者Class,所以为了可以表达函数的概念,提供了函数式接口。函数式接口是Java中一种特殊的接口:
有且只有一个抽象方法的接口是函数式接口
复制 public interface Factory {
Object getObject();
}
满足一条件的就是函数式接口,可以通过@FunctionalInterface
注解标记,被标记的接口如果不符合函数式接口的条件将会报错
复制 @FunctionalInterface
public interface Factory {
Object getObject();
}
看到函数式接口,就代表定义了一个这样的函数签名
常见的函数式接口
Runnable / Callable
略。
Comparator
略。
Supplier
Supplier即提供实例的供应商。该接口的定义,是获取一个结果,或者说,返回一个指定类型的实例,而且,针对同一个类型,不保证每次返回实例相同。
当我们把一个实例的类型、数据等信息收集好了,就可以交给Supplier接口(通过反射)去完成实现,当需要的时候就通过get()返回那个实例,这就是懒加载。在Spring和JDK中都有这么用过。
复制 //创建Supplier容器,声明为TestSuppler类型,此时并不会调用对象的构造方法,即不会创建对象
Supplier<TestSupplier> sup= TestSupplier::new;
//调用get()方法,此时会调用对象的构造方法,即获得到真正对象
sup.get();
//每次get都会调用构造方法,即获取的对象不同
sup.get();
应用场景:
配合Future(J.U.C),把返回的信息封装/设置成具体类型的实例;
在流操作中,获取源数据(资源文件、管道等)的实例,封装各种buffer的实例等,包括反射获取source;
java.util.stream,用作返回收集、分割、查找、过滤等操作的实例;
在日志系统中,封装一个“消息提供者”
复制 logger.log(level, msgSupplier, thrown);
在网络编程中,封装二进制数据
复制 Supplier<? extends ByteBuffer> binarySupplier;
在Spring的实例初始化时,在AbstracBeanDefinition中提供了实例供应商,用于回调生成bean
复制 private Supplier<?> instanceSupplier;
Supplier<?> instanceSupplier = mbd.getInstanceSupplier();
if (instanceSupplier != null) {
return obtainFromSupplier(instanceSupplier, beanName);
}
Consumer
对象消费者:
复制 package java.util.function;
import java.util.Objects;
@FunctionalInterface
public interface Consumer<T> {
// 接收一个t对象并处理消费
void accept(T t);
// 提供链式调用方式执行,先执行本身的accept在执行传入参数after.accept方法
// 如果在执行调用链时出现异常,会将异常传递给调用链功能的调用者,且发生异常后的after将不会在调用
default Consumer<T> andThen(Consumer<? super T> after) {
Objects.requireNonNull(after);
return (T t) -> { accept(t); after.accept(t); };
}
}
典型应用场景:
复制 // Iterable 接口的 defult方法:forEach
default void forEach(Consumer<? super T> action) {
Objects.requireNonNull(action);
for (T t : this) {
action.accept(t);
}
}
BiConsumer
代表连续消费两个入参的操作:
复制 BiConsumer<Integer,Integer> accept=(x,y)->{};
Predicate
谓语,Predicate代表一个断定式子,其函数名为 test
:
示例:
复制 Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.equals("zhangsan");
}
};
System.out.println(predicate.test("lisi"));
提供的其他的default方法:
isEqual
,判断两个对象是否相同,实际是调用Objects.equals
方法
注意,上述方法都返回当前Predicate函数对象,所以可以进行链式调用,但是test
方法返回true。
Function
复制 @FunctionalInterface
public interface Function<T, R> {
// 传入类型T对象将其转换为类型R对象
R apply(T t);
// 返回一个新的Function,他会在当前函数之前先执行before函数,和andThen相反
// 可以将整个执行想成一个链,往当前函数前面插入一个函数
default <V> Function<V, R> compose(Function<? super V, ? extends T> before) {
Objects.requireNonNull(before);
return (V v) -> apply(before.apply(v));
}
// 返回一个新的Function,他会在当前函数之后先执行after函数,和compose相反
// 可以将整个执行想成一个链,往当前函数后面插入一个函数
default <V> Function<T, V> andThen(Function<? super R, ? extends V> after) {
Objects.requireNonNull(after);
return (T t) -> after.apply(apply(t));
}
// 返回一个输入什么对象,就输出什么对象的Function函数
// 如果某些方法需要Function但是又不需要对数据进行特殊处理的时候,可以使用这个函数
static <T> Function<T, T> identity() {
return t -> t;
}
}
BiFunction
接受输入两个参数,返回一个结果
复制 BiFunction<Integer, Integer, Integer> add = (x, y) -> x + y;
//return 30
System.out.println(add.apply(10,20));
Lambda底层实现
Lambda表达式实际上是匿名内部类的匿名实现。
方法引用
方法引用是对Lambda表达式的再次简化:
复制 printStr(s -> {
System.out.println(s);
})
// 上述代码中,整个Lambda表达式仅仅只调用了一个println方法
// 也就是说整个Lambda要执行的函数体已经在println方法中存在了
// 我们就认为Lambda表达式的存在就是冗余的了
// 这时可以使用方法引用替代上述代码
printStr(System.out::println);
// 上述代码的作用为,提取println的方法的内容,也就是方法引用,作为本来要传入的lambda表达式给printStr方法
// 格式为: 方法持有方::方法名
// 如果是实例方法,就是 对象::方法名 称为普通方法引用
// 如果是静态方法,就是 类::方法名 称为静态方法引用
方法引用语法格式
方法引用运算符:::
哪儿些方法可以引用?
实例方法:System.out::println
,super::方法名
,this::方法名
,int[]::new
数组方法引用
可以采用引用的前提:
方法引用底层实现
与Lambda的原理一致,本质也是匿名内部类。
Stream
关注做什么,而不是怎么做。
提供串行/并行两种模式(fork/join框架拆分任务)
Stream常用API
Stream流的API大致可以分为两大类:
中间操作,可以有零个或多个,打开流,过滤/映射,返回新流
复制 map
filter
distinct
sorted
peek
limit
skip
parallel
sequential
unordered
concat
终结操作,只能有一个的最后的操作,调用终结操作Stream流将会被关闭。
终结操作也是一种短路操作,可以根据情况中断流处理。
复制 forEach
forEachOrdered
toArray
reduce
collect
min
max
count
iterator
anyMatch
allMatch
noneMatch
findFist
findAny
构建Steam
复制 // list集合转换Stream流
List<String> list = new ArrayList<>();
Stream<String> s1 = list.stream();
// set集合转换Stream流
Set<String> set = new HashSet<>();
Stream<String> s2 = set.stream();
// map转换Stream流
Map<String, String> map = new HashMap<>();
Stream<String> s3 = map.keySet().stream();
Stream<String> s4 = map.valueSet().stream();
Stream<String> s4 = map.entrySet().stream();
// 数组转换Stream流
Integer[] arr = {1, 2, 3, 4, 5, 6};
Stream<Integer> s5 = Stream.of(arr);
// 使用Stream.Builder
// 构建IntStream
IntStream.builder()
.add(1)
.add(99)
.add(-6)
.build();
// 构建LongStream
LongStream.builder()
.add(1L)
.add(99L)
.add(-6L)
.build();
// 构建DoubleStream
DoubleStream.builder()
.add(1.0)
.add(99.0)
.add(-6.1)
.build();
// 构建Stream
Stream.builder()
.add("a")
.add(new Object())
.add(100)
.build();
collect 收集
复制 R collect(Collector<? super T, A, R> collector);
Collector
收集器有一个对应的工具类 Collectors
,可以返回一些比较常用的收集器:
复制 // 收集到List
stringCollection.stream()
.filter(x -> x.startsWith("a")).collect(Collectors.toList());
// 收集到Set
stringCollection.stream()
.filter(x -> x.startsWith("a")).collect(Collectors.toSet());
// 收集到Collection
stringCollection.stream()
.filter(x -> x.startsWith("a"))
.collect(Collectors.toCollection(ArrayList::new));
// 收集到Map
stringCollection.stream()
.filter(x -> x.startsWith("a"))
.distinct()
.collect(Collectors.toMap(Function.identity(), xu));
// 收集到ConcurrentHashMap
Collectors.toConcurrentMap();
并行Stream
stream()
方法产生的流是串行的,也就是说流中的操作在一个线程中运行。而通过parallelStream()
创建的流则是一个并行的流,他的内部是一个ForkJoinPool
,处理时拆分处理,最后将结果合并。
获取并行流:
复制 List<Integer> list = new ArrayList<>();
// 通过parallelStream()方法获取一个并行流
list.parallelStream();
// 通过parallel方法将流转换为一个并行流
Stream.of(1, 2, 3).parallel();
并行流因为基于ForkJoinPool,所以也有线程安全的问题(在流处理函数中访问非线程安全的变量)。个人认为,当数据量处理过慢,或者集合过大,应该优先从其他方面优化(提升执行速度、减少数据量),而不是一定要使用并行流。
使用Optional处理Null
三种构造
复制 // obj 不能为null值,否则直接快速抛出NullpointException
Optional.of(obj);
// 允许为null,但是如果传入null,就得到了 Optional.empty()
// 不是null,则会获取 Optional.of(obj)
Optional.ofNullable(obj)
// 空的Optional
Optional.empty()
何时使用每种构造?
Optional.of(obj)
:
Optional.ofNullable(obj)
:
不明确obj是否为null,又要对obj进行处理的
存在即返回,无则提供默认值
复制 User u = Optional.ofNullable(user).orElse(User.UNKNOWN_USER);
存在即返回,无则由函数生成
复制 User u = Optional.ofNullable(user).orElseGet(() -> new User());
存在则返回, 无则抛出异常
复制 User u = Optional.ofNullable(user).orElseThrow(() -> new IllegalArgumentException());
变种,存在则返回,无则抛出空指针异常:
复制 User u = Objects.requireNonNull(user,"用户信息获取异常,无法进行操作");
存在才执行, 无则不会执行
复制 Optional.ofNullable(user).ifPresent(uu -> System.out.println(uu));
map 处理级联数据
有一 user 对象,不知是否为null
如果为null,返回null
如果不为null,返回 name
如果name 为null,返回 null
如果name 不为null, 将name 转换为大写
这种级联的null 处理,需要使用map, map可以嵌套无数层。
复制 String n = Optional.ofNullable(user).map(u -> u.getName()).map(name -> "姓名:" + name).orElse(null);
flatMap:
flatMap
同 map
类似,只是参数不同,他的函数参数需要返回 Optional
类型:
复制 String n = Optional.ofNullable(user).flatMap(u -> Optional.ofNullable(u.getName())).map(name -> "姓名:" + name).orElse(null);
总结
一句话小结 : 使用 Optional 时尽量不直接调用 Optional.get()
方法, Optional.isPresent()
更应该被视为一个私有方法, 应依赖于其他像 Optional.orElse()
, Optional.orElseGet()
, Optional.map()
等这样的方法.
新的日期API
JSR-310规范提供一个新的和改进的Java日期与时间API,该规范领导者Stephen Colebourne就是joda-time作者,因此很多环节很像joda-time。
JDK8的新的设计时间日期API,位于java.time
下面,并且都是线程安全的:
DateTimeFormatter
日期时间格式化类
此外,Java中使用的历法是ISO 8601日历系统,也就是公历。平年有365天,闰年有366天。此外,Java8还提供了4套其他的历法,他们分别是:
旧版日期时间API缺陷
日期时间类设计不合理,有java.util.date
以及java.sql.Date
两个类。其中前者拥有日期和时间,后者仅仅拥有日期。
日期时间格式化设计不合理,在java.text
下。
时区处理麻烦,日期类并不提供国际化,没有时区的支持。
LocalDate
复制 // 创建指定日期
LocalDate date1 = LocalDate.of(2021, 5, 6);
System.out.println(date1); // 2021-05-06
// 创建当前日期
LocalDate now = LocalDate.now();
System.out.println(now); // 2022-08-25
// 获取对应的日期信息
System.out.println(now.getYear()); // 2022
System.out.println(now.getMonth()); // 枚举值 AUGUST
System.out.println(now.getDayOfMonth());// 25
System.out.println(now.getDayOfWeek()); // 枚举值 THURSDAY
LocalTime
复制 // 创建指定的时间
LocalTime time = LocalTime.of(6, 26, 33, 23145);
System.out.println(time);
// 得到当前时间
LocalTime now = LocalTime.now();
System.out.println(now);
// 获取时间信息
System.out.println(now.getHour());// 小时
System.out.println(now.getMinute());// 分钟
System.out.println(now.getSecond());// 秒
System.out.println(now.getNano());// 纳秒
LocalDateTime
复制 // 创建指定的时间
LocalDateTime dateTime = LocalDateTime.of(2021, 3, 20, 6, 26, 33, 23145);
System.out.println(dateTime);
// 得到当前时间
LocalDateTime now = LocalDateTime.now();
System.out.println(now);
// 获取时间信息
System.out.println(now.getYear()); // 年
System.out.println(now.getMonth()); // 月
System.out.println(now.getDayOfMonth());// 日
System.out.println(now.getDayOfWeek()); // 星期
System.out.println(now.getHour());// 小时
System.out.println(now.getMinute());// 分钟
System.out.println(now.getSecond());// 秒
System.out.println(now.getNano());// 纳秒
日期时间修改
LocalDate
、LocalTime
、LocalDateTime
提供了一系列的with
方法,用于根据当前日期修改得到新的日期。注意,他们默认都是不可修改的对象,故他们是线程安全的,修改操作会返回一个新的日期时间对象:
复制 LocalDateTime localDateTime = LocalDateTime.now();
LocalDateTime newDateTime = now.withYear(1998);
// withHour
// withDayOfMonth
// ....
也提供了一系列的plus
以及minus
方法,用于在当前日期的基础上,加上或者减去指定的时间:
复制 now.plusDays(2);
now.plusYears(10);
now.minusYears(10);
now.minusHours(5);
更复杂的情况可以借助时间矫正器 TemporalAdjuster
,完成类似的以下的日期时间修改功能:
复制 LocalDateTime now = LocalDateTime.now();
// 将当前日期调整到下个月的一号
TemporalAdjuster adjuster = temporal -> {
return temporal
// 下个月
.plus(1, ChronoUnit.MONTHS)
// 一号
.with(ChronoField.DAY_OF_MONTH, 1);
};
LocalDateTime with = now.with(adjuster);
System.out.println(with);
TemporalAdjuster 提供了大量的默认实现,用于简化常用的日期修改操作。
日期时间比较
复制 // 比较两个日期/时间的前后
now.isAfter(date);
now.isBefore(date);
now.isEqueal(date);
日期格式化
在JDK8中,可以通过java.time.format.DateTimeFormatter
类进行日期时间格式化于解析:
复制 // Date -> String
// 使用提供的pattern
String dateStr = now.format(DateTimeFormatter.ISO_LOCAL_DATE_TIME);
// 使用自定义的pattern
String dateStr2 = now.format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
// String -> Date
LocalDateTime dateTime = LocalDateTime.parse("1997-05-07 21:12:55", DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss"));
Instant
表示时间戳,内部保存了从1770年1月1日0时0分以来的秒数以及纳秒数。
复制 Instant now = Instant.now();
// 同样支持 plus一级minus 修改时间
now.getEpochSecond() // 秒
now.toEpochMilli() // 毫秒
now.getNano(); // 纳秒
// DateTime获取毫秒数(先转换为Instant)
Instant localDateTime2Instant = localDateTime.atZone(ZoneId.systemDefault()).toInstant();
Duration 和 Period
DK8提供的用于计算时间日期时间差的工具类。其中
Duration,可以计算 LocalTime
、LocalDateTime
、Instant
的时间差
Period,可以计算LocalDate
之间的时间差
复制 LocalTime now = LocalTime.now();
LocalTime time = LocalTime.of(22, 34, 59);
// 通过Duration计算时间差(Diration内部记录了相差的纳秒数)
Duration durationTime = Duration.between(now, time);
durationTime.toDays(); // 差的天数
durationTime.toHours(); // 差的小时
durationTime.toMillis();
// ...
// 通过Period计算日期差(Period记录了相差的天数)
LocalDate nowDate = LocalDate.now();
LocalDate date = LocalDate.of(1997, 2, 2);
Period period = Period.between(date, nowDate);
period.getYears(); // 相差年份
period.getMonths(); // 相差月份
period.getDays(); // 相差天数
时区日期时间类
Java8中,LocalDate
、LocalTime
、LocalDateTime
是不带时区的,带时区的提供了三个对应的类,分别为:
其中每个时区都对应着一个ID,存储在ZonedId
类中。
复制 // 获取支持的时区
ZoneId.getAvailableZoneIds().forEach(System.out::println);
// 获取当前系统的时区的时间
ZonedDateTime now = ZonedDateTime.now(Clock.systemUTC());
// 获取某个特定时区的时间
ZonedDateTime americaTime = ZonedDateTime.now(ZoneId.of("America/Argentina/Buenos_Aires"));
框架支持
要想在Mybatis、Jackson中使用JSR310,需要添加JSR310的实现。
复制 <dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-typehandlers-jsr310</artifactId>
<version>1.0.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.2</version>
</dependency>
LocalDateTime字段来映射数据库中的datetime类型