JDK8 新特性【部分】
本章目标
-
重点掌握 Lambda 表达式的使用
-
掌握新日期/时间 API 的使用
-
掌握 Optional 类的使用
-
掌握接口增强的使用
Lambda 表达式
Lambda 表达式的出现
针对使用匿名内部类语法冗余的问题,JDK8 推出了 Lambda 表达式。
-
Lambda 表达式体现的是函数式编程思想,只需要将要执行的代码放到函数中即可(函数就是类中的方法)
-
Lambda 表达式就是一个匿名函数,我们只需要将执行的代码放到 Lambda 表达式中即可
Lambda 表达式语法格式
Lambda 表达式省去面向对象的条条框框,Lambda 的标准格式由 3 部分组成:
(参数类型 参数名称) -> {
方法体;
return 返回值;
}
-
(参数类型 参数名称):参数列表部分
-
{...}:方法体,即要执行的代码部分
-
->:箭头,无实际含义,起到连接参数列表和方法体的作用
Lambda 表达式的省略规则
-
小括号中的参数类型可以省略
-
如果小括号中只有一个参数,那么可以省略小括号
-
如果大括号中只有一条语句,那么可以同时省略大括号、return 关键字及语句分号
Lambda 表达式的使用
public class LambdaDemo { public static void main(String[] args) { //匿名内部类方式 new Thread(new Runnable() { @Override public void run() { System.out.println("新线程执行代码了"); } }).start(); //体验Lambda表达式 new Thread(() ->{ System.out.println("Lambda表达式执行了"); }).start(); } }
Lambda 表达式的好处
-
可以简化匿名内部类,让代码更加精简
-
Lambda 表达式免去了使用匿名方法的麻烦,并且给予Java简单但是强大的函数化的编程能力
Lambda 表达式的常见使用场景
-
使用 Lambda 表达式替换线程 Runnable 匿名内部类
-
使用 Lambda 表达式实现 Comparator 比较器
-
使用 Lambda 表达式实现 ActionListener 按钮事件监听器
Lambda 表达式的使用限制条件
-
方法的参数或局部变量类型必须为接口
-
接口中有且仅有一个抽象方法
@FunctionalInterface:JDK新增注解,用于检测接口只有一个抽象方法,否则会报错
Lambda 和 匿名内部类对比
所需的类型不一样
-
匿名内部类:需要的类型可以使具体类、抽象类、接口
-
Lambda 表达式:需要的类型必须是接口
抽象方法数量不一样
-
匿名内部类:接口中抽象方法的数量没有限制
-
Lambda 表达式:接口中的抽象方法的数量只能有 1 个
实现原理不同
-
匿名内部类:是在编译后会形成额外的一个 类名$0 的.class文件
-
Lambda 表达式:是在程序运行的时候动态生成 .class 文件(Lambda 表达式最终的实现还是基于匿名内部类的方式)
总结
当接口中只有一个抽象方法时,建议使用 Lambda 表达式;其他情况下,还是需要使用匿名内部类
新日期/时间 API
老日期/时间的设计缺陷
在 JDK8 之前,我们经常使用到的时间 API 包括(Date、Calendar),Date 与字符串之间的转换使用 SimpleDateFormat 进行转换(parse()、format() 方法),然而 SimpleDateFormat 不是线程安全的。在设计上也是存在一些缺陷,如下:
-
设计很差:在 java.util 和 java.sql 的包中都有日期类。java.util.Date 同时包含日期和时间,而java.sql.Date仅包含日期,此外用于格式化和解析的类又在 java.text 包中定义;
-
非线程安全:java.util.Date 是非线程安全的,所有的日期类都是可变的,这是 java 日期类最大的问题之一;
-
时区处理麻烦:日期类并不提供国际化,没有时区支持。因此 java 引入了 java.util.Calendar 和 java.util.TimeZone 类,但他们同样存在上述所有的问题
在 JDK8 中,引入了一套全新的时间日期 API,这套 API 在设计上比较合理,使用时间操作也变得更加方便。并且支持多线程安全操作。
新日期/时间 API 介绍
JDK8 中增加了一套全新的日期时间 API,这套 API 设计合理,是线程安全的。新的日期及时间 API 位于 java.time 包下,如下是一些该包下的关键类:
-
LocalDate:表示日期,包含:年月日。格式为:2020-01-13
-
LocalTime:表示时间,包含:时分秒。格式为:16:39:09.307
-
LocalDateTime:表示日期时间,包含:年月日 时分秒。格式为:2020-01-13T16:40:59.138
-
DateTimeFormatter:日期时间格式化类
-
Instant:时间戳类
-
Duration:用于计算 2 个时间(LocalTime,时分秒)之间的差距
-
Period:用于计算 2 个日期(LocalDate,年月日)之间的差距
-
ZonedDateTime:包含时区的时间
Optional 类
NullPointException 空指针异常问题
JDK8 以前,编写代码通常会出现 NullPointerException (空指针异常),通常情况下我们都是通过 if ... else...来对对象进行是否为空判断,然后再进行逻辑处理,代码写起来也比较冗余。
JDK8 新增了 Optional 类,使用该类可以避免我们对空指针的检查,使代码看起来比较优雅。
Optional 类介绍
Optional 类是一个没有子类的工具类,我们可以把 Optional 类看作是一个容器。这个容器它有两种情况:①要么有值 ②要么为null
创建 Optional 类对象的 3 种方式
//1.创建一个 Optional 实例 Optional.of(T t); //2.创建一个空的 Optional 实例 Optional.empty(); //3.若 t 不为 null,创建 Optional实例,否则创建空实例 Optional.ofNullable(T t); public class OptionalDemo { public static void main(String[] args) { //1.1 通过Optional.of() 方法,只能传入一个具体指,不能传入null,传入null报空指针异常 Optional<String> op1 = Optional.of("Lucy"); //Optional<Object> op2 = Optional.of(null); System.out.println(op1);//Optional[Lucy] //System.out.println(op2);//java.lang.NullPointerException //1.2 通过Optional.ofNullable()方法(可以传入具体值,也可以传入null,并不会报空指针异常) Optional<String> op3 = Optional.ofNullable("Lucy"); Optional<Object> op4 = Optional.ofNullable(null); System.out.println(op3);//Optional[Lucy] System.out.println(op4);//Optional.empty //1.3 通过 Optional.empty() 方法创建一个空 Optional,存入的是null Optional<Object> op5 = Optional.empty(); System.out.println(op5);//Optional.empty } }
Optional 类常用方法
-
isPresent() :判断是否包含值。包含值返回 true,不包含值返回 false
-
get(): 如果Optional有值则将其返回,否则抛出 NoSuchElementException: No value present 异常
-
orElse() : 如果调用对象包含值,返回该值,否则返回参数字符串str
-
orElseGet():如果调用对象包含值,返回该值。否则返回 s 获取的值
-
orElseThrows():如果存在该值,返回包含的值,否则抛出由 Supplier 继承的异常
-
map():如果有值对其处理,并返回处理后的Optional,否则返回 Optional.empty()
-
flatMap():如果有值,为其执行 mapping 函数返回 Optional 类型返回值,否则返回空Optional。
flatMap() 与 map()方法类似,区别在于 mapping 函数的返回值不同。map() 方法的 mapping 函数返回值可以是任何类型T,在 map () 方法返回之前会包装为 Optional。而 flatMap() 方法的 mapping 函数必须是 Optional。调用结束时,flatMap不会对结果用Optional封装。
-
filter(): filter()方法通过传入限定条件对 Optional 实例的值进行过滤。如果有值并且满足 Predicate判断条件,则返回包含该值的Optional,否则返回空 Optional。
-
ifPresent():如果值存在则使用该值调用consumer,否则不做任何事情
-
ifPresentOrElse():JDK9以后提供。如果值存在则使用该值调用consume,否则执行自定义的 Runnalbe 操作
-
equals():判断其他对象是否等于Optional
接口增强
在JDK8之前,JDK规定接口中只能定义 ①静态常量 ②抽象方法
修饰词 interface 接口名{ 静态常量; 抽象方法; }
在JDK8之后,对接口进行了增强。我们可以在接口中定义 ①静态常量 ②抽象方法 ③默认方法 ④静态方法
修饰词 interface 接口名{ 静态常量; 抽象方法; 默认方法; 静态方法; }
接口增强特性
-
新增的默认方法:使用 default 关键字,且实现类不必重写,可以直接使用(实现类也可以根据需要重写,这样就方便了接口的扩展)
-
新增的静态方法:实现类既不能调用,也不能重写(只属于接口本身),只能通过接口名. (接口名+ .)的方式调用
接口增强举例:
public class InterfaceDemo { public static void main(String[] args) { IStudent.work(); System.out.println(IStudent.ADDRESS); Student student = new Student(); student.study(); student.play(); } } /** * 学生接口 */ interface IStudent { //静态常量 String ADDRESS="成都"; //抽象方法(实现类必须重写) void play(); //默认方法(实现类不必重写,可以直接使用,也可以根据需要重写) default void study() { System.out.println("学习大数据"); } //静态方法(实现类既不能调用,也不能重写,只属于接口本身) static void work() { System.out.println("准备工作"); } } /** * 学生实现类 */ class Student implements IStudent { @Override public void play() { System.out.println("打球"); } }