Advanced Language Feature¶
前置知识¶
- 基本 Java 语法。
关于JEP
目前,Java 的新特性、功能均需要先提出并起草 JEP(JDK Enhancement Proposals)草案。在 OpenJDK 社区充分审核、修改、投票后成为正式的、带编号 JEP 提案。在开发 JEP 对应的功能分支,通过所有平台测试、代码审查后,择期并入下一个发行版本的主分支。本章提到的 JEP* 都标注了并入的 Java 版本和提案原文连接,便于同学们学习参考。
Switch Expressions [JEP361, Java14]¶
Switch Expressions [JEP361, Java14] 与传统的switch
不同,使用->
连接case
子句和具体代码块,并且case
子句可以一次匹配多个,代码块也不需要break
来防止连续执行。
1 2 3 4 5 6 |
|
switch
表达式,顾名思义可以产生表达式的值:
1 2 3 4 5 6 7 8 9 |
|
如果需要复杂代码块,使用{}
包围,并使用yield
关键字产生case
子句对应的值:
1 2 3 4 5 6 7 8 9 |
|
Pattern Matching for instanceof
[JEP394, Java16]¶
使用instanceof
判断一个对象是否为一个类的实例。一般来说,如果判断出确实是某一个类的实例,那下面一般都要向下转型为那个类的对象后再使用:
1 2 3 4 5 |
|
为了简化这这一过程,引入了instanceof
的模式匹配语法:
1 2 3 4 |
|
模式匹配出的变量名的作用域会由Java自动推导:
例如:
1 2 3 4 5 |
|
1 2 3 4 5 |
|
1 2 3 |
|
Records [JEP395, Java16]¶
对于纯数据类,我们仍然要定义一大堆构造函数、赋值、Getter&Setter、toString
、hashCode()
、equals(Object)
等一大堆方法。尽管这些方法都可以让 IDE 帮忙生成,但是一大堆没营养的代码堆在那里都让人觉得异常繁琐。
记录(英语:Record) 就是为了解决这个问题而生。Record
是一个只读的数据类,并且 Java 编译器会自动生成上面说的那一堆方法。
1 |
|
这就是一个典型的Record
。它大多数行为都与普通类没有区别,除了它的 Getter 方法不叫getXXX
而直接和变量名相同。
1 2 3 4 5 6 |
|
1 2 3 4 5 6 |
|
Compact Canonical Constructor 紧凑型构造器¶
可以使用紧凑型构造器来预处理或验证输入参数:
1 2 3 4 5 6 7 |
|
紧凑型构造器(英语: Compact Canonical Constructor)运行完后,Java 会再根据改变后的参数值对成员域进行一遍初始化。
相当于传统的构造器:
1 2 3 4 5 6 7 8 9 |
|
record
的使用有以下规则:
- record 类不应该继承其他的类,因为它默认继承
java.lang.Record
; - record 类隐式为
final
,并不允许为abstract
; - record 类的成员变量隐式为
final
。
Annotation¶
注解(英语: Annotation)为代码提供了一些附加信息,如@Override
。通过提供响应的注解处理程序,可以实现以下等功能:
- 编译时代码检查;
- 编译时代码生成;
- 运行时动态发现与注册。
本文不讲解反射系统,因此也不会设计注解处理程序的设计。本节的目的在于如何使用注解语法。
Java 标准库内提供的注解有:
@Override
: 检查是否重载方法。@Deprecated
: 表示注解的类、常量、方法被废弃。@FunctionalInterface
: 表示注解的接口是函数式接口。@SuppressWarnings
: 用于让编译器忽略特定的警告。
注解也可以有参数值,如@Deprecated
有forRemoval
(警告后续版本会被移除)和since
(从哪个版本开始废弃),语法如下:
1 2 |
|
声明注解的语法如下:
1 2 3 4 5 6 7 8 |
|
@Document
: 提示这个注解应该被收入 JavaDoc。@Retention(RetentionPolicy.RUNTIME)
: 表示这个注解的信息应该保留到什么时候。RetentionPolicy.RUNTIME
: 保留至运行时,反射系统可以读取。RetentionPolicy.CLASS
: 保留至类文件。RetentionPolicy.SOURCE
: 编译后即被丢弃。-
@Target(value={ /* ... */ })
: 提示这个注解可以应用给谁。 -
@interface
: 声明注解。 <Type> name() [default <value>]
: 声明某一类型的参数,可以提供默认值。
注解系统在 Java 标准库中应用较少,但是在其他库,如 Lombok ,Spring 等中,都会起到举足轻重的作用。如果有时间,我们也会接触到这些类库。
Exception Handling¶
想要向外部抛出异常表示程序出错,使用throw
:
1 2 3 |
|
异常均支持在实例化的时候提供自定义的错误信息以便调试查错。
想要捕获异常,使用try-catch
:
1 2 3 4 5 6 |
|
使用printStackTrace()
函数打印异常栈信息,描述了异常传播的途径和对应的源代码位置等信息。
你可以利用捕获的异常去构建其他类型等异常并重新抛出,原来捕获等异常就称为新异常的原因。这样可以提升异常系统的语义,也防止单一异常栈过长:
1 2 3 4 5 |
|
你可以一次捕获多个异常:
1 2 3 4 5 6 7 8 |
|
如果有一些系统资源,如文件句柄等,无论后续读取是否正常,都应该将其关闭。这时就需要finally
子句进行资源回收:
1 2 3 4 5 6 7 8 |
|
对于实现了AutoCloseable
接口的类,如所有输入输出组件,支持try-with-resources
自动调用close()
而无需finally
:
1 2 3 4 5 |
|
异常分为两类:
java.lang.RuntimeException
: 运行时异常,又称为非检查异常。通常表示程序逻辑错误,比如数组越界,空指针等问题。- 其他
java.lang.Exception
子类但是不是java.lang.RuntimeException
子类的其他类: 受检查异常。通常表示外部环境诱导的错误,程序应该考虑这些情况并进行处理,比如文件不存在,网络连接超时等问题。
受检查异常得名的原因在于:
- 可能抛出受检查异常的代码必须被
try-catch
处理,或者向外部声明自己可能传播受检查异常。
通过throws
子句声明方法可能会抛出某些受检查异常:
1 2 3 |
|
你可以使用 Java 标准库中提供的异常类,请参见API Ref中的 Direct Known Subclasses 小节寻找是否有自己需要的异常类。
通过继承自java.lang.Exception
或java.lang.RuntimeException
,你也可以创建自定义的受检异常和非受检异常类。你可以让其包含更多调试信息或生成更清晰直观的toString()
表示。
Generic 泛型¶
通过以下的例子来了解泛型(英语: Generic)的动机:
所有的列表都是java.util.List
的实现类,而它是可以存储任意对象的,即java.lang.Object
。如果我们想存一个文件名列表,还需要进行强制类型转换:
1 2 3 4 |
|
一方面,我们不期望这个filenames
里面存在任何非字符串的对象,但是没有禁止我们插入:
1 |
|
这样在运行时,无辜的代码会执行(String)
类型转换,于是会引起java.lang.ClassCastException
。
另一方面,在每次使用的时候都进行一遍类型转化显然是非常蠢的语法。
所以有什么办法能够将我们的列表和字符串类型绑定在一起呢?这就是泛型:
1 2 3 4 5 |
|
语法¶
使用泛型的语法如上所示,在类型名后面的<>
内依次输入类型参数即可:
1 2 |
|
定义泛型类或接口的方法也和使用的方法相似,我们使用类型变量来作为需要关联的类型的占位符:
1 2 3 4 5 6 |
|
定义泛型方法的方式略有不同,你需要在返回值类型前加入类型变量列表。使用泛型方法时,需要在.
和方法名之间插入类型参数列表:
1 2 3 4 5 6 7 |
|
在几乎大多数情况下,类型参数列表可以自动推导:
1 |
|
对类型变量的限定¶
有时我们不想要所有类型都混入我们的方法中,因此需要对类型变量做一些限定,比如下面寻找数组最小值的方法:
1 2 3 4 5 6 7 8 9 10 |
|
但是并不是所有的对象都支持正确的比较方法,因此我们限定T
应当是可比较的,即Comparable
。
1 2 3 4 5 6 7 8 9 10 |
|
如果想要T
同时是多个接口的实现类,那么可以使用&
连接这些接口:
1 |
|
通配符类型¶
由于 Java 限制,下面代码是不能通过编译的:
1 2 3 4 5 6 7 8 9 10 |
|
简单来说,就是虽然Student
是Person
的子类型,但是List<Student>
不是List<Person>
的子类型。这听起来确实有点反直觉,因为学生的列表就应该是人的列表。
为了解决这种情况,Java 引入了通配符类型?
,用于表示类型参数的派生:
1 2 3 4 |
|
资源链接¶
- Switch Expressions [JEP361, Java14]
- Pattern Matching for
instanceof
[JEP394, Java16] - Records [JEP395, Java16]
- Java API ref 异常类