• 第01篇_基础语法

    第01章_基础语法

    第一节 快速入门

    1. Java语言简介

    Java语言是美国Sun公司在1995年推出的高级编程语言,主要应用于企业应用开发,如商城系统、物流系统、网银系统等。

     

    2. JDK/JRE与JVM

    JDK (Java Development Kit):指Java程序开发工具包,包含JRE和开发人员使用的一些工具。

    JRE(Java Runtime Environment):指Java程序的运行时环境,包含JVM和运行时所需要的核心类库。

    JVM(Java Virtual Machine):指Java虚拟机,所有的Java代码,都运行在Java虚拟机之上。

    image-20221018132256210

     

    3. 安装JDK

     

    4. HelloWorld案例

    1) 编写源文件

    新建HelloWorld.java文件,输入以下代码:

    注意

    1. 这里的文件名必须和类名一致,注意大小写。

     

    2) 编译源文件

    使用javac命令对源文件进行编译,如果编译成功,将会生成一个HelloWorld.class字节码文件。

     

    3) 运行字节码

    使用java命令进行运行,注意文件名不要加.class后缀。

     

     

    第二节 数据类型

    1. 数据类型分类

    在Java中,提供了四类八种基本数据类型,分别是:

    引用数据类型包含基本数据类型之外的所有类型,如字符串、类、数组、接口、lamda表达式等。

    提示

    1. 字面量除了上述各种基本类型的值外,还有字符串null值两种。

    2. 变量必须初始化后才能使用,否则会出现编译错误。

     

     

    2. 数据类型转换

    参与的计算的数据,必须要保证数据类型的一致性,否则将发生类型的转换,转换可分为:

    特殊的,对于byteshortchar类型,在赋值时,如果右边为不超过取值范围的常量,则编译器会自动补上强制类型转换,在参与运算时,至少会被提升为int类型。

     

     

    3. 关于编码问题

    字符编码大致可分为Unicode编码非Unicode编码,不同编码对应了不同的存储格式:

     

    1) Unicode编码

    Unicode编码为全球约110万字符分配的的唯一数字编号,编号范围为ox0000~0x10FFFF

    大部分常见的字符都在ox0000~0xFFFF之间,大部分常见的中文字符在ox4E00~ox9FFF之间,如"马"的Unicode编码为U+9A6C

    Unicode编码只是给字符编号,但未说明其二进制形式的编号如何存储存储方式主要有如下三种:

    提示:

    1. 如果第一个字节存储二进制编号的最高位则称为"大端"字节序,如果存储最低位则称为"小端"字节序

    2. UTF-8-BOM:默认情况下,文件不会声明自身的编码格式,但UTF-8编码的文本文件,可以设置前三个字节为0xEF0xBB0xBF来表示当前文件为UTF-8文件,这三个字节被称为BOM(ByteOrder Mark)头,支持UTF-8-BOM格式的文件编辑器可自动识别。

     

    2) 非Unicode编码

     

    3) 乱码原因

    字符乱码主要有两种原因:

    4) Java内部字符处理

    在Java内部(内存中)进行字符处理时,采用的都是Unicode编码,具体编码格式是UTF-16BE

    进行字符处理时的基础类型是char,其它的Character、String、StringBuilder都是基于char类型的。

    char本质上是一个固定占用两个字节的无符号正整数,这个正整数对应于Unicode编号,用于表示那个Unicode编号对应的字符。

    由于char固定占用两个字节,只能表示Unicode编号在65536以内的字符,而不能表示超出范围的字符,超出范围的字符得使用两个char。

    注意

    1. 如果使用字面量'马'进行赋值,使用不同的编码格式打开文件可能看到不同的显示效果。

    2. 推荐JAVA文件采用UTF-8格式,并采用UTF-8格式进行编译。

     

     

    第三节 运算符

    1. 支持的运算符

    image-20221018165355070

     

    2.使用注意事项

    1. 整数的除法运算,将会丢弃余数,例如1/2=0

    2. 负数的取余运算,结果的正负始终与前一个操作数相同,例如-3%2==-13%-2==1

    3. 复合赋值运算符中隐含了一个强制类型转换,例如short s = 1; s = s + 1;会报错,而s += 1;却不会。

    4. 【重要】对于&和|运算符,如果两边为布尔型,则执行不短路的逻辑运算,如果为int型,则执行按位与和按位或。

     

     

    第四节 流程控制

    1. 分支语句

    注意

    1. switch语句支持的数据类型有整型字符串枚举

    2. 如果case语句的后面不写break语句,将出现穿透现象

     

    2. 循环语句

    提示

    1. do-while循环至少会被执行一次,可以用do{}while(flase)来实现goto语句

     

    3. break和continue

    break可用于跳出本层循环或结束switch语句,continue可用于结束本次循环。

     

    第五节 数组

    1. 数组的基本使用

     

    2. 多维数组

     

    3. Arrays工具类

    java.util.Arrays 类包含用来操作数组的各种方法,比如排序和搜索等。其所有方法均为静态方法,调用起来非常简单。

     

     

    第六节 方法

    1. 方法的基本使用

     

    2. 方法重载

    方法重载是指在同一个类中,允许存在一个以上的同名方法,只要它们的参数列表不同即可。参数列表不同可以是参数个数不同、参数类型不同和参数的顺序不同三种形式。

    注意:方法重载与参数的名称以及返回值类型无关。

     

    3. 可变参数

    方法的最后一个参数可以定义为可变参数同时支持数组和可变参数形式的传参

    注意

    1. 可变参数形式与数组形式的方法定义相互冲突,只能保留一个。

    2. 形参格式使用数组形式的方法则只能通过数组传参。

    第02章_面向对象

    面向对象的三大基本特征:封装继承多态

     

    第一节 类与对象

    1.基本使用

    是一组相关属性和行为的集合,对象是一类事物的具体体现。

    注意:可以通过this关键字来显示指明需要访问成员变量,来区分同名的局部变量。

     

    2. 权限修饰符

    权限修饰符可以用来修改方法属性等的访问权限,体现了面向对象的封装性。

    1. public:公有的,对所有人开放,无访问限制。

    2. protected:被保护的,对同包和其子类开放。

    3. [default]:默认值(缺省值),对同包开放。

    4. private:私有的,仅对所属类开放。

    注意:

    1. 一般将类定义为public,此时要求文件名和类名保持一致;如果定义为[default],则仅有同包中才能访问该类。

    2. 一般将成员变量定义为private,并提供相应的Getter/Setter方法和构造器。

    3. 不能对父类的private方法进行重写。

     

    3. static修饰符

    static可以用来修饰变量方法代码块内部类等,被修饰的内容保存在静态区(方法区的一部分),随着类的加载而加载,且只加载一次,加载后被所有对象共享,一般用类名进行调用。

    注意

    1. 静态属性和静态方法一般使用类名来调用,虽然也可以通过对象来调用,但不推荐。

    2. 静态方法和静态代码块只能访问类中的静态变量,不能直接访问普通成员变量或成员方法。

    3. 除静态代码块外,Java中还有构造代码块,它们的执行顺序为:静态代码块->Main函数->构造代码块->构造函数->普通代码块

    4. 在Java1.7后,静态代码块不能存在于主类中,防止干扰main函数的执行。

    5. 关于静态内部类的使用请参考内部类章节。

     

    4. final修饰符

    final表示不可改变,可以用于修饰方法变量等,分别具有不同的含义。

    注意:

    1. 被final修饰的静态变量一般称为常量,通常使用大写字母+下划线的形式命名。

    2. 必须保证类当中所有重载的构造方法,最终都会对final变量进行赋值。

     

     

    第二节 继承与多态

    1. 继承的基本使用

    继承指子类继承父类的特征和行为,使子类对象具有父类的实例域和方法,不仅复用了代码,而且使不同子类的对象能够被统一处理。

    注意:

    1. Java只支持单继承,一个类只能有一个父类,并且最终都继承自java.lang.Object类。

    2. 构造子类对象前必须先构造父类对象,如果父类没有无参构造,则必须在子类构造器的首行通过super显式调用有参构造。

    3. 不建议在构造方法中调用非private方法,特别是可被子类重写的方法,这将带来不必要的麻烦。

    4. 在子类中,可以使用super关键字来显式调用父类属性或方法。

    5. 子类初始化顺序为:基类静态代码块->子类静态代码块->基类实例代码块->基类构造方法->子类实例代码块->子类构造方法

     

     

    2. 方法重写与动态绑定

    方法重写指在子类中覆盖父类的实例方法,以此来实现子类的特异性功能,其要求方法名、参数列表和返回值完全一致,一般使用@Override注解标识。由于实例方法可能被重写,因此采用动态绑定方式,实际调用的方法取决于对象的动态类型

    注意:

    1. 使用final修饰的类不能被继承,使用final修饰的方法不能被重写,可用于加强封装性。

    2. 使用向下转型时可以先用instanceof关键字进行判断是否是某个类的子类,避免ClassCastException。

     

    3. 重名与静态绑定

    子类还可以定义与父类重名的变量静态方法,其采用静态绑定(编译期绑定)方式,实际调用取决于对象的静态类型

    注意:

    1. 此处所说的重名指方法名(变量名)与参数列表皆相同,而非方法重载的情形(重载始终优先于重写)。

     

     

     

    第三节 接口

    1. 定义接口

    接口是Java提供的一种的引用类型,其中可以定义常量抽象方法默认方法和静态方法(JDK 8)以及私有方法(JDK 9)等。

    接口也可以继承其它接口,并且能够同时继承多个,如果父接口中有重名的方法,那么子接口中只需要重写一次。

    注意:

    1. 接口中只可以定义public static final修饰的常量,且不可以定义任何变量

    2. 接口中没有静态代码块、构造方法,不能实例化对象。

     

    2. 实现接口

    一个类可以实现多个接口,一个接口也可以被多个类实现。

    类在继承基类的同时,也可以实现一个或多个接口,但要求extends关键字在implements之前

    注意:

    1. 如果实现类不是抽象类,则必须实现接口的所有抽象方法。

    2. 子接口在重写默认方法时,default关键字可以保留,而子类重写默认方法时,不可以保留,因为类中不存在默认方法。

    3. 如果继承来的方法和接口的默认方法重名,则子类会优先使用继承来的方法,即继承优于实现

     

    3. 使用接口

     

    4. 接口+组合替代继承

    继承至少有两个好处:一个是复用代码;另一个是利用多态和动态绑定统一处理多种不同子类的对象。使用组合替代继承,可以复用代码,但不能统一处理。使用接口替代继承,针对接口编程,可以实现统一处理不同类型的对象,但接口没有代码实现,无法复用代码。将组合和接口结合起来替代继承,就既可以统一处理,又可以复用代码了。

    如下例,Child复用了Base的代码,又都实现了IAdd接口,这样,既复用代码,又可以统一处理,还不用担心破坏封装。

     

    第四节 抽象类

    1. 抽象类的定义和使用

    抽象类指使用abstract修饰的类,一般存在抽象方法。抽象方法指只有方法声明,但没有方法体的方法。

    注意:

    1. 抽象类的子类,必须重写抽象父类中所有的抽象方法,除非该子类也是抽象类。

    2. 抽象类中,可以有构造方法,是供子类创建对象时,初始化父类成员使用的。

    3. 抽象类中,不一定包含抽象方法,但是有抽象方法的类必定是抽象类。

     

    2. 抽象类和接口

    抽象类和接口经常配合使用,接口定义能力,而抽象类提供默认实现,方便子类实现接口,如List接口对应AbstractList,Map接口对应AbstractMap等。它们之间的对比如下:

    image-20221022163434390

     

     

     

    第五节 内部类

    1. 成员内部类

    成员内部类指定义在其它类内部的类,如List返回的Iterator对象等,适用于与外部类关系密切且需要访问外部类实例的变量或方法的类。

    注意:

    1. 如需创建成员内部类对象,则必须先创建外部类对象,成员内部类会保存外部类对象的引用。

    2. 内部类在编译后会生成独立的.class文件,类名用$进行分隔,如Body$Heart.class

    3. 如果在内部类中访问了外部类的私有变量或方法,则在编译时会被替换为[default]权限的access$0系列方法。

    4. 外部类只可以用public或[default]修饰,但内部类还额外支持private和protected修饰符

    5. 接口也可以定义内部接口和内部抽象类等,了解即可。

     

    2. 静态内部类

    静态内部类指定义在其它类内部的静态类,如LinkedList类内部的Node类等,适用于与外部类关系密切但不依赖于外部类实例的类。

    注意

    1. 创建静态内部类对象时不依赖外部类对象,因此它只能访问外部类的静态变量和方法,不可以访问实例变量和方法。

    2. 静态内部类除了可以定义成员方法、成员变量、构造方法、静态final变量等,还额外支持静态变量和静态方法

     

    3. 方法内部类

    方法内部类指定义在方法内部的类,方法可以是成员方法或静态方法,适用于只在当前方法中使用该类的场景。

    注意:

    1. 如需在方法内部类中访问方法参数或局部变量,则该变量必须是final的,或等效final的(因为该变量是通过构造参数直接传入)。

    2. 方法内部类不能写任何权限修饰符。

     

    4. 匿名内部类

    匿名内部类指定义在方法内部的匿名类,它没有类名和构造方法,且只能在定义类时创建对象,适用于接口回调的场景。

    注意:

    1. 匿名内部类可以定义实例变量、实例方法、初始化代码块、参数列表等,其中参数列表将会传递给父类的构造方法。

    2. 与方法内部类一样,匿名内部类也可以访问外部类的变量和方法,可以访问等效final的方法参数和局部变量。

     

     

     

    第六节 枚举类

    枚举(enum)是一种特殊的类,它只有类中声明的有限个对象,对象名称一般大写。

     

    1. 基本枚举类

     

    2. 带成员的枚举类

    枚举类可以定义自己的成员变量成员方法以及继承接口等。

    注意:

    1. 枚举对象必须定义在类的前面,并且以分号结尾,再写其它代码。

    2. 不需要提供Setter方法,枚举对象不允许修改。

     

    3. 枚举的本质

    枚举本质上还是一个类,上述案例转换为普通类如下:

    第03章_常用基础类

    第一节 包装类

    1. 包装类介绍

    Java中提供了四类八种基础类型,为了适应不同场合,又分别提供了对应的包装类。

    image-20221027133900647

     

    2. 装箱与拆箱

    基本类型与对应的包装类对象之间,来回转换的过程称为”装箱“与”拆箱“。从JDK1.5开始,该动作可以自动完成。

     

    3. 包装类与字符串转换

     

    4. 扩展:整型池

    与字符串常量池类似,Integer类内部维护了一个整型池,范围为-128~127,如使用自动装箱操作,则会自动取池中对象,可用==进行地址比较,否则需要用equals比较。

     

    第二节 字符串(String)

    1. String类基本使用

    Java中使用java.lang.String类代表字符串,创建字符串的方式如下:

    String类在创建后不可修改,并且对字符串常量进行入池共享。

    注意:

    1. 通过字符串字面量构造也会创建String类对象,并且会自动入池,推荐使用。

    2. String类与字节数组之间的转换依赖字符编码,如未指明则使用系统默认字符编码:Charset.defaultCharset().name()

     

    2. String的常用方法

    image-20221029143742709

     

    3. StringBuilder

    如果对字符串操作很频繁或者在循环中使用字符串,则推荐使用StringBuilder类或其线程安全版本的StringBuffer类来处理。

    注意:

    1. StringBuilder内部采用非final修饰的字符数组,可以根据字符串的修改来进行动态扩容,而不会创建很多中间字符串对象。

    2. 如果不是在循环中使用,则编译器一般可以自动优化为StringBuilder操作,而在循环中,可能会创建多个StringBuilder对象。

     

    4. StringJoiner

    在JDK1.8+版本,添加了更适合进行字符串拼接操作的类StringJoiner,同时在String类中加了一个静态方法String.join()

     

     

    第三节 日期和时间

    关于日期和时间,有一些基本概念,包括纪元时时刻时区年历等。

    纪元时(Epoch Time):一个特殊的时刻(零点),指格林尼治标准时间1970年1月1日0时0分0秒

    时刻相对于纪元时的毫秒数(允许负数)。这个毫秒数在世界各地都相同,但是不同地区对其解读不一样(与时区和年历有关)。

    时区:全球一共有24个时区,英国格林尼治是0时区(GMT+0),北京是东八区(GMT+8:00),也就是说格林尼治凌晨1点,北京是早上9点。

    年历:描述了一年有多少月,每月有多少天,甚至一天有多少小时,在中文系统中,一般使用公历。

    注意:时刻是一个绝对时间,但对时刻的解读,则是相对的,与时区和年历相关。

     

     

    1. Date(时刻)

    java.util.Date是早期引入的日期API,主要用于表示时刻的概念,同时承担了年历的作用,但由于不支持国际化,许多方法被标记过时了,我们只需学习未过时的方法即可。

     

    2. TimeZone(时区)

    java.util.TimeZone(抽象类)表示时区,默认时区由系统属性`user.timezone属性决定。

    注意:

    1. 系统属性可通过System.getProperty("")获取,并在启动时通过java -Duser.timezone=Asia/Shanghai xxxx参数进行调整。

     

    3. Locale(地区和语言)

    java.util.Locale表示国家(或地区)和语言,中国内地的代码是CN,中国台湾地区的代码是TW,美国的代码是US,中文语言的代码是zh,英文语言的代码是en等。

     

    4. Calendar(日历)

    1) 实例化

    java.util.Calendar(抽象类)表示与年月日相关的日历信息,可以对日期和时间进行设置和修改。

     

    2) 获取日历信息

    其内部有一个与Date中类似的长整型变量protected long time,用于记录时刻(默认为当前时间),并且还有一个数组protected int fields[],表示日历中各个字段的值。这个数组的长度为17,能够表示的字段主要有:

    字段含义
    Calendar.MONTH月(0~11)
    Calendar.DAY_OF_MONTH当前月的第N日(1~xx)
    Calendar.HOUR_OF_DAY当前日的第N时(0~23)
    Calendar.MINUTE分钟(0~59)
    Calendar.SECOND秒(0~59)
    Calendar.MILLISECOND毫秒(0~999)
    Calendar.DAY_OF_WEEK当前周的第N天(1~7,1为周日)

    注意:

    1. Calendar中定义了表示各个星期、各个月的静态变量,如Calendar.SUNDAY表示周日、Calendar.JULY表示7月。

     

    3) 直接设置时间

    Calendar支持根据Date毫秒数设置时间,也支持根据年月日等日历字段设置时间。

     

    4) 增加或减少时间

    除了直接设置,也支持根据字段增加和减少时间,正数表示增加,负数表示减少,增减支持自动调整

     

    5) 转换为Date或毫秒数

     

    6) 与其它Calendar进行比较

     

     

    5. DateFormat(格式化)

    java.text.DateFormat(抽象类)用于Date与字符串之间的相互转换。我们一般用它的实现类SimpleDateFormat,它接受一个pattern作为构造参数,pattern中的英文字符(a~z|A~Z)表示特殊含义,其他字符原样输出。部分特殊字符含义如下:

    格式字符串含义格式字符串含义
    yyyy年份mm分钟
    MM月份ss
    HH小时(24小时制)E星期几
    hh小时(12小时制)a上午或下午(仅12小时制有效)

    特殊的,如果想原样输出英文字符,可以将其用单引号括起来。

     

     

    第四节 日期和时间(JDK8+)

    JDK8对日期和时间进行了增强,位于java.time包下。

     

    1. Instant(时刻)

     

    2. ZoneId/ZoneOffset(时区/时区偏移)

     

    3. LocalDateTime(时区无关日期时间)

    1) 基本使用

    注意:

    1. DayOfWeek是一个枚举,有7个取值,从DayOfWeek.MONDAY到DayOfWeek.SUN-DAY。

     

    2)LocalDate和LocalTime

    LocalDateTime由两部分组成,一部分是日期(LocalDate),另一部分是时间(LocalTime),它们三者之间可以相互转换。

     

    4. ZonedDateTime(时区相关日期时间)

    注意:

    1. LocalDateTime只记录了年月日等相关信息,不记录时区信息,其它方法一般都要传递时区。

    2. ZonedDateTime不仅记录了时刻,还记录了时区,ZonedDateTime ≈ LocalDateTime + 时区。

     

    5. DateTimeFormatter

     

    6. 修改日期和时间

    1) 直接设置或增减方式

    修改日期和时间有两种方式,一种是直接设置绝对值,另一种是在现有值的基础上进行相对增减操作。

    注意:

    1. ChronoField是一个枚举,里面定义了很多表示日历的字段,MILLI_OF_DAY表示在一天中的毫秒数,值从0到(246060*1000) -1。

     

    2)TemporalAdjuster接口方式

    针对复杂的时间调整,JDK8专门定义了一个函数式接口TemporalAdjuster,Instant、LocalDateTime和LocalDate等都实现了它。与此相关的还有TemporalAdjusters类,里面提供了很多TemporalAdjuster的实现。

     

    7. 计算时间段

    JDK8中用Period表示日期差,差值为N年N月N日;用Duration表示时间差,单位可以为天(1天24小时)、时、分、秒、毫秒等。

     

    8. 与Date/Calendar对象的转换

     

     

    第五节 其它

    1. 根类(Object/Objects)

    java.lang.Object类是Java语言中的根类,即所有类的父类,java.util.Objects是与之配套的工具类。

    方法名说明
    protected Object clone()创建并返回对象的浅拷贝
    boolean equals(Object obj)比较两个对象是否相等,默认按地址进行比较,可进行重写
    protected void finalize()在对象被回收前由垃圾回收器调用
    Class<?> getClass()获取对象的运行时对象的类
    int hashCode()获取对象的 hash 值
    void notify()唤醒在该对象上等待的某个线程
    void notifyAll()唤醒在该对象上等待的所有线程
    String toString()返回对象的字符串表示形式
    void wait()让当前线程进入无限等待状态,直到其他线程调用此对象的 notify() 方法或 notifyAll() 方法
    void wait(long timeout)让当前线程进入超时等待状态
    void wait(long timeout, int nanos)让当前线程进入超时等待状态,支持纳秒级别控制(0-999999)

    注意:

    1. 重写equals的同时一般也需要重写hashcode()方法。

    2. 对象比较推荐使用Objects.equals(Object obj1, Object obj2)方法,支持NULL值比较。

     

    2. 系统与运行时(System/Runtime)

    java.lang.System类中提供了大量的静态方法,可以获取与系统相关的信息或进行系统级操作。java.lang.Runtime描述的是运行时状态,在每一个JVM进程中都会提供唯一的一个Runtime类实例化对象,开发者可以通过Runtime类对象获取与JVM有关的运行时状态。

    注意:

    1. System和Runtime都可以调用gc方法,但不建议在代码中手动干预,并且代码中的gc操作是可以通过JVM的运行参数来屏蔽的。

     

    3. 随机数(Random)

    Random类的实例用于生成伪随机数流。下面是一个生成范围1-n随机数的示例:

    注意:

    1. 如果随机数种子相同,那么它们将生成并返回相同的数字序列。

    2. Random类是线程安全的,但如果并发太高,会产生竞争,这时候可以考虑ThreadLocalRandom类或Math.random()方法。

    3. Java类库中还有一个随机类SecureRandom,可以产生安全性更高、随机性更强的随机数,用于安全加密等领域。

     

    4. 数学运算(Math)

    java.lang.Math类包含了一些数学常量和基本数学运算,如初等指数、对数、平方根和三角函数等。

     

    5. 大数运算(BigDecimal)

    java.math.BigDecimal用于对浮点数进行精确的运算,一般用于金额、利率计算等场景。

    BigDecimal是对象类型,不能使用传统的+-*/等算术运算符直接对其对象进行数学运算,而必须调用其相对应的方法,方法中的参数也必须是BigDecimal的对象。

    注意:

    1. 不推荐基于浮点数构建BigDecimal对象,因为双精度浮点型(double)的最大有效位为16位。

    2. 但如果必须使用,则可以先转换为字符串:Double.toString(double),再基于数值字符串构建BigDecimal对象。

    3. 如果进行除法运算的时候,结果不能整除(有余数),这个时候会报java.lang.ArithmeticException,需要设置精度和舍入模式。

    4. 如果需要处理的数为大整数,可以使用java.math.BigInteger来替代。

     

    6. 文本扫描器(Scanner)

    java.util.Scanner类是一个可以使用正则表达式来解析基本类型和字符串的简单文本扫描器。

     

    7. 比较器(Comparator)

    java.util.Comparator接口用于比较两个对象的大小,小于时返回负数,等于时返回0,大于时返回正数。

     

    8. 关闭钩子(AutoCloseAble)

    JDK1.7引入了AutoCloseAble接口,配合try(获取资源){}语法进行使用,这样获取的资源可以自动关闭,无需手动释放。

     

    9. 空值处理(Optional)

    java.util.Optional是Java 8引入的一个泛型容器类,内部只有一个类型为T的单一变量value,可能为null,也可能不为null。

    Optional有什么用呢?它用于准确地传递程序的语义,它清楚地表明,其代表的值可能为null,程序员应该进行适当的处理。