java8特性
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;
可选的小括号,如果只有一个参数,可以省略小括号
a -> return a + 5;
可选的return关键字,如果函数体只有一个表达式,且运算结果匹配返回类型,return可以省略
a -> a + 5
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
:
评估参数里面的表达式
返回值是一个boolean类型
示例:
Predicate<String> predicate = new Predicate<String>() {
@Override
public boolean test(String s) {
return s.equals("zhangsan");
}
};
System.out.println(predicate.test("lisi"));
提供的其他的default方法:
and
, 等同于短路与&&
or
,等同于逻辑或||
negate
,等同于逻辑非!
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方法
// 格式为: 方法持有方::方法名
// 如果是实例方法,就是 对象::方法名 称为普通方法引用
// 如果是静态方法,就是 类::方法名 称为静态方法引用
方法引用语法格式
方法引用运算符:::
哪儿些方法可以引用?
类方法:
Integer::parseInt
构造方法:
Student::new
实例方法:
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)
:
明确obj不可能为null
明确obj为null,并快速抛出异常
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
下面,并且都是线程安全的:
LocalDate
本地日期LocalTime
本地时间LocalDateTime
本地日期时间DateTimeFormatter
日期时间格式化类Instant
时间戳Duration
时间段(两个时间的间隔)Period
日期段(两个日期的间隔)ZonedDateTime
具有时区的日期时间
此外,Java中使用的历法是ISO 8601日历系统,也就是公历。平年有365天,闰年有366天。此外,Java8还提供了4套其他的历法,他们分别是:
ThaiBuddhistDate
:泰国佛教历MinguoDate
:中华民国历JapaneseDate
:日本历HijraDate
:伊斯兰历
旧版日期时间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
是不带时区的,带时区的提供了三个对应的类,分别为:
ZonedDate
ZonedTime
ZonedDateTime
其中每个时区都对应着一个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>
LocalDate映射数据库中的date类型
LocalTime来映射数据库中的time类型
LocalDateTime字段来映射数据库中的datetime类型
最后更新于
这有帮助吗?