Java 9 揭秘(20. JDK 9中API层次的改变下)
Java 9Tips
做一个终身学习的人。
十六. 数组比较
java.util.Arrays
类由静态实用方法组成,可用于对数组执行各种操作,例如排序,比较,转换为流等。在JDK 9中,此类已经获得了几种方法,可以比较数组和切片(slices)。 新方法分为三类:
- 比较两个数组或它们的切片是否相等性
- 按字典顺序比较两个数组
- 查找两个数组中的第一个不匹配的索引
添加到此类的方法列表是很大的。 每个类别中的方法对于所有原始类型和对象数组都是重载的。 有关完整列表,请参阅Arrays类的API文档。
equals()
方法可以比较两个数组的相等性。 如果数组或部分数组中的元素数量相同,并且数组或部分数组中所有对应的元素对相等,则两个数组被认为是相等的。 以下是int
的两个版本的equals()
方法:
boolean equals(int[] a, int[] b)
boolean equals(int[] a, int aFromIndex, int aToIndex, int[] b, int bFromIndex, int bToIndex)
第一个版本允许比较两个数组之间的相等性,并且存在于JDK 9之前。第二个版本允许将两个数组的部分进行比较,以便在JDK 9中添加相等。fromIndex
(包含)和toIndex
(不包含)参数决定要比较的两个数组的范围。 如果两个数组相等,则该方法返回true,否则返回false。 如果两个数组都为空,则认为两个数组相等。
JDK 9添加了几个compare()
和compareUnsigned()
的方法。 这两种方法都按字典顺序比较数组或部分数组中的元素。
compareUnsigned()
方法将整数值视为无符号。 空数组的字符拼写小于非空数组。 两个空数组相等。 以下是对于int
的compare()
方法的两个版本:
int compare(int[] a, int[] b)
int compare(int[] a, int aFromIndex, int aToIndex, int[] b, int bFromIndex, int bToIndex)
如果第一个和第二个数组相等并且包含相同的元素,compare()
方法返回0; 如果第一个数组在字典上小于第二个数组,则返回小于0的值; 并且如果第一个数组在字典上大于第二个数组则返回大于0的值。
mismatch()
方法比较两个数组或数组的一部分。 以下是int
的两个版本的mismatch()
方法:
int mismatch(int[] a, int[] b)
int mismatch (int[] a, int aFromIndex, int aToIndex, int[] b, int bFromIndex, int bToIndex)
mismatch()
方法返回第一个不匹配的索引。 如果没有不匹配,则返回-1。 如果任一数组为空,则抛出NullPointerException
。 下包含一个比较两个数组及其部分数组的完整程序。 该程序使用两个int
数组。
// ArrayComparision.java
package com.jdojo.misc;
import java.util.Arrays;
public class ArrayComparison {
public static void main(String[] args) {
int[] a1 = {1, 2, 3, 4, 5};
int[] a2 = {1, 2, 7, 4, 5};
int[] a3 = {1, 2, 3, 4, 5};
// Print original arrays
System.out.println("Three arrays:");
System.out.println("a1: " + Arrays.toString(a1));
System.out.println("a2: " + Arrays.toString(a2));
System.out.println("a3: " + Arrays.toString(a3));
// Compare arrays for equality
System.out.println("\nComparing arrays using equals() method:");
System.out.println("Arrays.equals(a1, a2): " + Arrays.equals(a1, a2));
System.out.println("Arrays.equals(a1, a3): " + Arrays.equals(a1, a3));
System.out.println("Arrays.equals(a1, 0, 2, a2, 0, 2): " +
Arrays.equals(a1, 0, 2, a2, 0, 2));
// Compare arrays lexicographically
System.out.println("\nComparing arrays using compare() method:");
System.out.println("Arrays.compare(a1, a2): " + Arrays.compare(a1, a2));
System.out.println("Arrays.compare(a2, a1): " + Arrays.compare(a2, a1));
System.out.println("Arrays.compare(a1, a3): " + Arrays.compare(a1, a3));
System.out.println("Arrays.compare(a1, 0, 2, a2, 0, 2): " +
Arrays.compare(a1, 0, 2, a2, 0, 2));
// Find the mismatched index in arrays
System.out.println("\nFinding mismatch using the mismatch() method:");
System.out.println("Arrays.mismatch(a1, a2): " + Arrays.mismatch(a1, a2));
System.out.println("Arrays.mismatch(a1, a3): " + Arrays.mismatch(a1, a3));
System.out.println("Arrays.mismatch(a1, 0, 5, a2, 0, 1): " +
Arrays.mismatch(a1, 0, 5, a2, 0, 1));
}
}
输出结果为:
a1: [1, 2, 3, 4, 5]
a2: [1, 2, 7, 4, 5]
a3: [1, 2, 3, 4, 5]
Comparing arrays using equals() method:
Arrays.equals(a1, a2): false
Arrays.equals(a1, a3): true
Arrays.equals(a1, 0, 2, a2, 0, 2): true
Comparing arrays using compare() method:
Arrays.compare(a1, a2): -1
Arrays.compare(a2, a1): 1
Arrays.compare(a1, a3): 0
Arrays.compare(a1, 0, 2, a2, 0, 2): 0
Finding mismatch using the mismatch() method:
Arrays.mismatch(a1, a2): 2
Arrays.mismatch(a1, a3): -1
Arrays.mismatch(a1, 0, 5, a2, 0, 1): 1
十七. Applet API已经废弃
Java applets需要Java浏览器插件才能正常工作。 许多浏览器供应商已经删除了对Java浏览器插件的支持,或者将在不久的将来删除它。 如果浏览器不支持Java插件,则不能使用applet,因此没有理由使用Applet API。 JDK 9弃用了Applet API。 但是,它将不会在JDK 10中被删除。如果计划在将来的版本中被删除,开发人员将提前发布一个通知。 以下类和接口已被弃用:
java.applet.AppletStub
java.applet.Applet
java.applet.AudioClip
java.applet.AppletContext
javax.swing.JApplet
在JDK 9中,所有AWT和Swing相关类都打包在java.desktop模块中。 这些不推荐的类和接口也在同一个模块中。
appletviewer工具随其JDK在bin目录中提供,用于测试applet。 该工具也在JDK 9中不推荐使用。在JDK 9中运行该工具会打印一个弃用警告。
十八. Javadoc增强
JDK 9引入了Javadoc的编写,生成和使用方式的一些增强功能。 JDK 9在Javadoc中支持HTML5。 默认情况下,javadoc工具仍然在HTML4中生成输出。 一个新的选项-html5
已添加到该工具中,表明希望HTML5中的输出:
javadoc -html5 <other-options>
javadoc工具位于JDK_HOME\bin目录中。 使用--hel
p选项运行工具打印其使用说明和所有选项。
NetBeans IDE可以为项目生成Javadoc。 在项目的“属性”对话框中,选择“Build”➤“Documenting”以获取Javadoc属性页,可以在其中指定javadoc工具的所有选项。 要生成Javadoc,请从项目的右键菜单选项中选择“Generate Javadoc”。
JDK 9保留了三个Frame或无Frame的Javadoc布局。 左上角的框架包含三个链接:所有类,所有包和所有模块。 在JDK 9中添加了ALL MODULES链接,其中显示了所有模块的列表。 ALL CLASSES链接可以查看左下框架中的所有类。 其他两个链接可查看所有软件包,模块中的所有软件包以及所有模块。 下面显示了Javadoc页面的更改。
javadoc考虑这种情况。 正在寻找在Java中实现某些内容的逻辑,并在Internet上找到一段代码,该代码使用类,但不显示导入该类的导入语句。 可以访问Java SE的Javadoc,并希望了解更多关于该类的信息。 如何获取类的包名,这是需要获取类的文档? 再次搜索互联网。 这次,搜索类名,这可能会获得该类的Javadoc的链接。 或者,可以将这段代码复制并粘贴到Java IDE(如NetBeans和Eclipse)中,IDE将生成导入语句,提供类的包名称。 不要担心在JDK 9中搜索类的包名称的这种不便。
右边的主Frame还有另一个补充。 此框中的所有页面都显示右上角的“搜索”框。 搜索框可搜索Javadoc。 javadoc工具准备可搜索的术语索引。 要知道可搜索的内容,需要知道索引的条款:
- 可以搜索模块,软件包,类型和成员的声明名称。 构造方法和方法的形式参数的类型被索引,但不是这些参数的名称。 所以,可以搜索形式参数的类型。 如果在搜索框中输入“(String, int, int)”,将会找到使用String,int和int三个形式参数的构造函数和方法的列表。 如果输入“util”作为搜索项,它将显示包含名称中的“util”一词的所有包,类型和成员的列表。
- JDK 9引入了一个新的内联Javadoc标签
@index
,可以用来告诉javadoc工具对关键字进行索引。 它可以作为{@index <keyword> <description>}
出现在Javadoc中,其中<keyword>
是要被索引的关键字,而<description>
是关键字的描述。 以下Javadoc标记是使用带有关键字jdojo的@index
标记的示例:jdojo: {@index jdojo Info site ( [www.jdojo.com ](http://www.jdojo.com/)) for the Java 9 Revealed book!}
。
此列表中未列出的其他所有内容都不可使用Javadoc搜索框进行搜索。 当输入搜索字词时,搜索框会将搜索结果显示为列表。 结果列表分为类别,如模块,包,类型,成员和搜索标签。 SearchTags类别包含从使用@index
标记指定的索引关键字中找到的结果。
Tips
Javadoc搜索不支持正则表达式。
下图显示了使用结果列表的Javadoc搜索框。 为com.jdojo.misc模块生成了Javadoc,并使用它来搜索jdojo。 使用Java SE 9 的Javadoc来搜索术语Module,如右图所示。
搜索结果可以使用向上和向下箭头键浏览搜索结果。 可以通过以下两种方式查看搜索结果的详细信息:
- 单击搜索结果以打开该主题的Javadoc。
- 当使用向上/向下箭头突出显示搜索结果时,按Enter打开该主题的详细信息。
Tips
可以使用-noindex
选项与javadoc
工具来禁用Javadoc搜索。 将不会生成索引,并且生成的Javadoc中不会有搜索框可用。
使用客户端JavaScript本地执行Javadoc搜索。 在服务器中没有实现计算或搜索逻辑。 如果在浏览器中禁用JavaScript,则无法使用Javadoc搜索功能。
十九. 本地桌面功能
Java SE 6通过java.awt.Desktop
类添加了特定于平台的桌面支持。 该类支持从Java应用程序执行以下操作:
- 在用户默认浏览器中打开URI
- 在用户默认邮件客户端中打开mailto URI
- 使用注册的应用程序打开,编辑和打印文件
如果Java SE 9在当前平台上可用,那么Java SE 9推出面向特定于平台的桌面支持,并为许多系统和应用程序事件通知添加公共API支持。 java.awt.Desktop
类仍然是使用平台特定的桌面功能的中心类。 为了支持这么多新的桌面功能,Java SE 9向java.desktop模块添加了一个新的包java.awt.desktop。java.awt.Desktop
类也有很多新方法。 新包包含30个类和接口。 在JDK 9中,Desktop API支持24个特定于桌面的桌面操作和通知,它们由Desktop.Action
枚举的常量定义。 举几个例子,它们如下:
- 当附件显示进入或退出节电时的通知
- 当系统进入睡眠或系统唤醒后的通知
- 用户会话更改时的通知,例如锁定/解锁用户会话
- 当应用程序的状态更改为或不是前台应用程序时的通知
- 要求应用程序显示其“关于”对话框时的通知
可以使用这些功能来优化应用程序的资源使用情况。 例如,如果系统进入睡眠模式,您可以停止动画,并在系统唤醒时恢复。 有关详细信息,请参阅java.awt.Desktop类
的API文档以及java.awt.desktop包中的类和接口。 使用桌面功能时,以下是典型的步骤:
- 使用此类的静态
isDesktopSupported()
方法检查当前平台是否支持Desktop
类。 如果该方法返回false,则不能使用任何桌面功能。 - 如果支持
Desktop
类,请使用Desktop
类的静态getDesktop()
方法获取Desktop
类的引用。 - 并非所有桌面功能都可在所有平台上使用。 在桌面对象上使用
isSupported(Desktop.Action action)
方法来检查是否支持特定的桌面操作。 受支持的桌面操作由Desktop.Action
枚举中的常量表示。 - 如果支持桌面操作,可以调用
Desktop
类的一个方法来执行诸如打开文件的操作,也可以使用addAppEventListener(SystemEventListener listener)
方法注册事件处理程序。
Tips
java.awt和java.awt.desktop包在java.desktop模块中。 当使用平台特定的桌面功能时,请确保你的模块读取java.desktop模块。
下面包含一个演示桌面功能的完整程序。 应用程序注册用户会话更改监听器。 当用户会话更改时,通知应用程序,并在标准输出上打印消息。 可以通过远程登录/注销或通过锁定和解锁计算机来更改用户会话。 可以使用此桌面通知来暂停昂贵的处理,例如用户会话被停用时的动画,并在激活该过程时重新启动该过程。 以下详细说明本程序的输出。 运行程序时,需要锁定和解锁您的计算机与用户会话相关的输出。 显示的输出是在Windows上运行这个程序,并在程序运行时锁定和解锁我的计算机一次。 你可能得到不同的输出。 两分钟后,程序自行退出。
// DeskTopFrame.java
package com.jdojo.misc;
import java.awt.Desktop;
import java.awt.desktop.UserSessionEvent;
import java.awt.desktop.UserSessionListener;
import java.util.concurrent.TimeUnit;
public class DeskTopFrame {
public static void main(String[] args) {
// Check if Desktop class is available
if (!Desktop.isDesktopSupported()) {
System.out.println("Current Platform does not support Desktop.");
return;
}
System.out.println("Current platform supports Desktop.");
// Get the desktop reference
Desktop desktop = Desktop.getDesktop();
// Check if user session event notification is supported
if (!desktop.isSupported(Desktop.Action.APP_EVENT_USER_SESSION)) {
System.out.println("User session notification is not " +
"supported by the current desktop");
return;
}
System.out.println("Lock and unlock your session to see " +
"user session change notification in action.");
// Add an event handler for a change in user session
desktop.addAppEventListener(new UserSessionListener() {
@Override
public void userSessionDeactivated(UserSessionEvent e) {
System.out.println("User session deactivated. Reason: " + e.getReason());
}
@Override
public void userSessionActivated(UserSessionEvent e) {
System.out.println("User session activated. Reason: " + e.getReason());
}
});
// Make the current thread sleep for 2 minutes
try {
TimeUnit.SECONDS.sleep(120);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
输出的结果为:
Current platform supports Desktop.
Lock and unlock your session to see user session change notification in action.
User session deactivated. Reason: LOCK
User session activated. Reason: LOCK
main()
方法检查当前平台上的Desktop
类是否可用。 如果不可用,程序退出。 如果可用,则获得其引用。
if (!Desktop.isDesktopSupported()) {
System.out.println("Current Platform does not support Desktop.");
return;
}
// Get the desktop reference
Desktop desktop = Desktop.getDesktop();
如有兴趣在用户会话更改时收到通知,因此需要检查此功能是否受支持。 如果不支持,程序退出。
// Check if user session event notification is supported
if (!desktop.isSupported(Desktop.Action.APP_EVENT_USER_SESSION)) {
System.out.println("User session notification is not " +
"supported by the current desktop");
return;
}
如果支持用户会话更改通知,则需要注册UserSessionListener
类型的事件监听器,如下所示:
// Add an event handler for a change in user session
desktop.addAppEventListener(new UserSessionListener() {
@Override
public void userSessionActivated(UserSessionEvent e) {
System.out.println("Use session activated. Reason: " + e.getReason());
}
@Override
public void userSessionDeactivated(UserSessionEvent e) {
System.out.println("User session deactivated. Reason: " + e.getReason());
}
});
分别激活和停用用户会话时,会调用注册的UserSessionListener
的userSessionActivated()
和userSessionDeactivated()
方法。 两个方法都将一个UserSessionEvent
对象作为参数。 UserSessionEvent
类的getReason()
方法返回一个UserSessionEvent.Reason
,它是一个枚举,它的常量定义了用户会话更改的原因。 枚举有四个常量:CONSOLE
,LOCK
,REMOTE
和UNSPECIFIED
。 CONSOLE
和REMOTE
常数表示用户会话分别与控制台终端和远程终端连接/断开的原因。 LOCK
常数表示指示用户会话已被锁定或解锁的原因。 顾名思义,UNSPECIFIED
常数表示用户会话更改的所有其他原因。
最后,main()
方法使当前线程休眠两分钟,所以有机会锁定和解锁会话以查看程序的工作。 如果删除程序的这一部分,程序将退出,而不等待更改用户会话。
二十. 对象反序列化过滤器
Java可以对对象进行序列化和反序列化。 为了解决反序列化带来的安全风险,JDK 9引入了可以用来验证反序列化对象的对象输入过滤器的概念,如果不通过测试,则可以停止反序列化过程。 对象输入过滤器是添加到JDK 9的新接口java.io.ObjectInputFilter
的实例。过滤器可以基于以下一个或多个条件:
- 数组的长度反序列化
- 嵌套对象的深度反序列化
- 对象引用数反序列化
- 对象的类被反序列化
- 从输入流消耗的字节数
ObjectInputFilter
接口只包含一个方法:
ObjectInputFilter.Status checkInput(ObjectInputFilter.FilterInfo filterInfo)
可以指定要用于反序列化所有对象的全局过滤器。 可以通过为对象输入流设置本地过滤器来重写每个ObjectInputStream
上的全局过滤器。 可以没有全局过滤器,并为每个对象输入流指定本地过滤器。 有几种方法来创建和指定过滤器。 本节首先介绍添加到JDK 9中的类和接口,需要使用这些类和接口来处理过滤器:
ObjectInputFilter
ObjectInputFilter.Config
ObjectInputFilter.FilterInfo
ObjectInputFilter.Status
ObjectInputFilter
接口的实例表示过滤器。 可以通过在类中实现此接口来创建过滤器。 或者,可以使用ObjectInputFilter.Config
类的createFilter(String pattern)
方法从字符串获取其实例。
ObjectInputFilter.Config
是一个嵌套的静态实用类,用于两个目的:
- 获取并设置全局过滤器
- 从指定字符串的模式中创建过滤器
ObjectInputFilter.Config
类包含以下三种静态方法:
ObjectInputFilter createFilter(String pattern)
ObjectInputFilter getSerialFilter()
void setSerialFilter(ObjectInputFilter filter)
createFilter()
方法接受一个描述过滤器的模式,并返回ObjectInputFilter
接口的实例。 以下代码片段创建一个过滤器,指定反序列化数组的长度不应超过4:
String pattern = "maxarray=4";
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
可以在一个过滤器中指定多个模式。 它们用分号(;)分隔。 以下代码片段从两种模式创建一个过滤器。 如果遇到长度大于4的数组或串行化对象的大小大于1024字节,则过滤器将拒绝对象反序列化。
String pattern = "maxarray=4;maxbytes=1024";
ObjectInputFilter filter = ObjectInputFilter.Config.createFilter(pattern);
指定过滤器模式有几个规则。 如果喜欢在Java代码中编写过滤器逻辑,可以通过创建实现ObjectInputFilter
接口的类并将其写入其checkInput()
方法来实现。 如果要从字符串中的模式创建过滤器,请遵循以下规则:
有五个过滤条件,其中四个是限制。 它们是maxarray
,maxdepth
,maxrefs
和maxbytes
。 可以使用name = value
来设置它们,其中name
是这些关键字,value
是限制。 如果模式包含等号(=),则模式必须使用这四个关键字作为名称。 第五个过滤条件用于指定类名形式的模式:
<module-name>/<fully-qualified-class-name>
- 如果一个类是未命名的模块,则该模式将与类名匹配。 如果对象是一个数组,则数组的组件类型的类名用于匹配模式,而不是数组本身的类名。 以下是匹配类名称的模式的所有规则:
- 如果类名与模式匹配,则允许对象反序列化。
- 以“!” 模式开头的字符被视为逻辑NOT。
- 如果模式包含斜杠(/),斜杠之前的部分是模块名称。 如果模块名称与类的模块名称相匹配,则斜线后面的部分将被用作匹配类名称的模式。 如果模式中没有斜线,则在匹配模式时不考虑类的模块名称。
- 以“.**”结尾的模式匹配包中的任何类和所有子软件包。
- 以“.*”结尾的模式匹配包中的任何类。
- 以“*”结尾的模式匹配任何具有模式作为前缀的类。
- 如果模式等于类名称,则它匹配。
- 另外,模式不匹配,对象被拒绝。
如果将com.jdojo.**
设置为过滤器模式,它允许com.jdojo包中的所有类及其子包都被反序列化,并将拒绝所有其他类的反序列化对象。 如果将“com.jdojo.**”设置为过滤器模式,它将拒绝com.jdojo包中的所有类及其子包以进行反序列化,并允许反序列化所有其他类的对象。
getSerialFilter()
和setSerialFilter()
方法用于获取和设置全局过滤器。 可以使用以下三种方式之一设置全局过滤器:
- 通过设置名为
jdk.serialFilter
的系统属性,该属性的值是以分号分隔的一系列过滤器模式。 - 通过在java.security文件中设置一个存储在JAVA_HOME\conf\security目录中的
jdk.serialFilter
属性。 如果正在使用JDK运行程序,请将JAVA_HOME作为JDK_HOME读取。 否则,将其读为JRE_HOME。 - 通过调用
ObjectInputFilter.Config
类的setSerialFilter()
静态方法。
以下命令在运行类时将jdk.series
属性设置为命令行选项。 不要担心这个命令的其他细节。
C:\Java9Revealed>java -Djdk.serialFilter=maxarray=100;maxdepth=3;com.jdojo.** --module-path com.jdojo.misc\build\classes --module com.jdojo.misc/com.jdojo.misc.ObjectFilterTest
下面显示了JAVA_HOME\conf\security\java.security配置文件的部分内容。 该文件包含更多的条目。 只显示一个设置过滤器的条目,这与设置jdk.serialFilter系统属性具有相同的效果,如上一个命令所示。
maxarray=100;maxdepth=3;com.jdojo.**
Tips
如果在系统属性和配置文件中设置过滤器,则优先使用系统属性中的值。
当运行具有全局过滤器的java命令时,会注意到stderr上的消息类似于此处显示的消息:
Feb 17, 2017 9:23:45 AM java.io.ObjectInputFilter$Config lambda$static$0
INFO: Creating serialization filter from maxarray=20;maxdepth=3;!com.jdojo.**
这些消息使用java.io.serialization的Logger作为平台消息记录java.base模块。 如果指定了平台Logger,这些消息将被记录到Logger中。 其中一条消息在系统属性或配置文件中打印全局过滤器集。
还可以使用ObjectInputFilter.Config
类的静态setSerialFilter()
方法在代码中设置全局过滤器:
// Create a filter
String pattern = "maxarray=100;maxdepth=3;com.jdojo.**";
ObjectInputFilter globalFilter = ObjectInputFilter.Config.createFilter(pattern);
// Set a global filter
ObjectInputFilter.Config.setSerialFilter(globalFilter);
Tips
只能设置一次全局过滤器。 例如,如果使用jdk.serialFilter系统属性设置过滤器,则在代码中调用Config.setSerialFiter()
将抛出IllegalStateException
。 当使用`Config.setSerialFiter()方法设置全局过滤器时,必须设置非空值过滤器。 存在这些规则,以确保在代码中无法覆盖使用系统属性或配置文件的全局过滤器集。
可以使用ObjectInputFilter.Config
类的静态getSerialFilter()
方法获取全局过滤器,而不考虑过滤器的设置方式。 如果没有全局过滤器,则此方法返回null。
ObjectInputFilter.FilterInfo
是一个嵌套的静态接口,其实例包装了反序列化的当前上下文。ObjectInputFilter.FilterInfo
的实例被创建并传递给过滤器的checkInput()
方法。 不必在程序中实现此接口并创建其实例。 该接口包含以下方法,将在自定义过滤器的checkInput()
方法中使用以读取当前反序列化上下文:
Class<?> serialClass()
long arrayLength()
long depth();
long references();
long streamBytes();
serialClass()
方法返回反序列化对象的类。对于数组,它返回数组的类,而不是数组的组件类型的类。在反序列化期间未创建新对象时,此方法返回null。
arrayLength()
方法返回反序列化数组的长度。它被反序列化的对象不是数组,它返回-1。
depth()
方法返回被反序列化的对象的嵌套深度。它从1开始,对于每个嵌套级别递增1,当嵌套对象返回时,递减1。
references()
方法返回反序列化的对象引用的当前数量。
streamBytes()
方法返回从对象输入流消耗的当前字节数。
对象可能根据指定的过滤条件会通过,也可能会失败。根据测试结果,应该返回ObjectInputFilter.Status
枚举的以下常量。通常,在自定义过滤器类的checkInput()
方法中使用这些常量作为返回值。
ALLOWED
REJECTED
UNDECIDED
这些常量表示反序列化允许,拒绝和未定。 通常,返回UNDECIDED
表示一些其他过滤器将决定当前对象的反序列化是否继续。 如果正在创建一个过滤器以将类列入黑名单,则可以返回REJECTED
以获取黑名单类别的匹配项,而对其他类别则为UNDECIDED
。
下面包含一个基于数组长度进行过滤的简单过滤器。
// ArrayLengthObjectFilter.java
package com.jdojo.misc;
import java.io.ObjectInputFilter;
public class ArrayLengthObjectFilter implements ObjectInputFilter {
private long maxLenth = -1;
public ArrayLengthObjectFilter(int maxLength) {
this.maxLenth = maxLength;
}
@Override
public Status checkInput(FilterInfo info) {
long arrayLength = info.arrayLength();
if (arrayLength >= 0 && arrayLength > this.maxLenth) {
return Status.REJECTED;
}
return Status.ALLOWED;
}
}
以下代码片段通过将数组的最大长度指定为3来使用自定义过滤器。如果对象输入流包含长度大于3的数组,则反序列化将失败,并显示java.io.InvalidClassException
。 代码不显示异常处理逻辑。
ArrayLengthObjectFilter filter = new ArrayLengthObjectFilter(3);
File inputFile = ...
ObjectInputStream in = new ObjectInputStream(new FileInputStream(inputFile))) {
in.setObjectInputFilter(filter);
Object obj = in.readObject();
下面包含一个Item
类的代码。为保持代码简洁,省略了getter和setter方法。 使用它的对象来演示反序列化过滤器。
// Item.java
package com.jdojo.misc;
import java.io.Serializable;
import java.util.Arrays;
public class Item implements Serializable {
private int id;
private String name;
private int[] points;
public Item(int id, String name, int[] points) {
this.id = id;
this.name = name;
this.points = points;
}
/* Add getters and setters here */
@Override
public String toString() {
return "[id=" + id + ", name=" + name + ", points=" + Arrays.toString(points) + "]";
}
}
下面包含ObjectFilterTest
类的代码,用于演示在对象反序列化过程中使用过滤器。 代码中有详细的说明。
// ObjectFilterTest.java
package com.jdojo.misc;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputFilter;
import java.io.ObjectInputFilter.Config;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjectFilterTest {
public static void main(String[] args) {
// Relative path of the output/input file
File file = new File("serialized", "item.ser");
// Make sure directories exist
ensureParentDirExists(file);
// Create an Item used in serialization and deserialization
Item item = new Item(100, "Pen", new int[]{1,2,3,4});
// Serialize the item
serialize(file, item);
// Print the global filter
ObjectInputFilter globalFilter = Config.getSerialFilter();
System.out.println("Global filter: " + globalFilter);
// Deserialize the item
Item item2 = deserialize(file);
System.out.println("Deserialized using global filter: " + item2);
// Use a filter to reject array size > 2
String maxArrayFilterPattern = "maxarray=2";
ObjectInputFilter maxArrayFilter = Config.createFilter(maxArrayFilterPattern);
Item item3 = deserialize(file, maxArrayFilter);
System.out.println("Deserialized with a maxarray=2 filter: " + item3);
// Create a custom filter
ArrayLengthObjectFilter customFilter = new ArrayLengthObjectFilter(5);
Item item4 = deserialize(file, customFilter);
System.out.println("Deserialized with a custom filter (maxarray=5): " + item4);
}
private static void serialize(File file, Item item) {
try (ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(file))) {
out.writeObject(item);
System.out.println("Serialized Item: " + item);
} catch (Exception e) {
e.printStackTrace();
}
}
private static Item deserialize(File file) {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
Item item = (Item)in.readObject();
return item;
} catch (Exception e) {
System.out.println("Could not deserialize item. Error: " + e.getMessage());
}
return null;
}
private static Item deserialize(File file, ObjectInputFilter filter) {
try (ObjectInputStream in = new ObjectInputStream(new FileInputStream(file))) {
// Set the object input filter passed in
in.setObjectInputFilter(filter);
Item item = (Item)in.readObject();
return item;
} catch (Exception e) {
System.out.println("Could not deserialize item. Error: " + e.getMessage());
}
return null;
}
private static void ensureParentDirExists(File file) {
File parent = file.getParentFile();
if(!parent.exists()) {
parent.mkdirs();
}
System.out.println("Input/output file is " + file.getAbsolutePath());
}
}
ObjectFilterTest
使用不同的过滤器序列化Item
类,随后使用相同Item
类多个反序列化。ensureParentDirExists()
方法接受一个文件,并确保其父目录存在,如果需要创建它。 该目录还打印序列化文件的路径。
serialize()
方法将指定的Item
对象序列化为指定的文件。 这个方法从main()
方法调用一次序列化一个Item
对象。
deserialize()
方法是重载的。 deserialize(File file)
版本使用全局过滤器(如果有的话)反序列化保存在指定文件中的Item
对象。 deserialize(File file, ObjectInputFilter filter)
版本使用指定的过滤器反序列化保存在指定文件中的Item
对象。 注意在此方法中使用in.setObjectInputFilter(filter)
方法调用。 它为ObjectInputStream
设置指定的过滤器。 此过滤器将覆盖全局过滤器(如果有)。
main()
方法打印全局过滤器,创建一个Item
对象并对其进行序列化,创建多个本地过滤器,并使用不同的过滤器对同一个Item
对象进行反序列化。 以下命令运行ObjectFilterTest
类而不使用全局过滤器。 可能得到不同的输出。
C:\Java9Revealed>java --module-path com.jdojo.misc\build\classes
--module com.jdojo.misc/com.jdojo.misc.ObjectFilterTest
输出结果为:
Input/output file is C:\Java9Revealed\serialized\item.ser
Serialized Item: [id=100, name=Pen, points=[1, 2, 3, 4]]
Global filter: null
Deserialized using global filter: [id=100, name=Pen, points=[1, 2, 3, 4]]
Could not deserialize item. Error: filter status: REJECTED
Deserialized with a maxarray=2 filter: null
Deserialized with a custom filter (maxarray=2): [id=100, name=Pen, points=[1, 2, 3, 4]]
以下命令使用全局过滤器maxarray = 1
运行ObjectFilterTest
类,这将防止具有多个元素的数组被反序列化。 全局过滤器是使用jdk.serialFilter系统属性设置的。 因为正在使用全局过滤器,JDK类将在stderr上记录消息。
C:\Java9Revealed>java -Djdk.serialFilter=maxarray=1
--module-path com.jdojo.misc\build\classes
--module com.jdojo.misc/com.jdojo.misc.ObjectFilterTest
输出结果为:
Input/output file is C:\Java9Revealed\serialized\item.ser
Serialized Item: [id=100, name=Pen, points=[1, 2, 3, 4]]
Feb 17, 2017 1:09:57 PM java.io.ObjectInputFilter$Config lambda$static$0
INFO: Creating serialization filter from maxarray=1
Global filter: maxarray=1
Could not deserialize item. Error: filter status: REJECTED
Deserialized using global filter: null
Could not deserialize item. Error: filter status: REJECTED
Deserialized with a maxarray=2 filter: null
Deserialized with a custom filter (maxarray=5): [id=100, name=Pen, points=[1, 2, 3, 4]]
注意使用全局过滤器时的输出。 因为Item
对象包含一个包含四个元素的数组,所以全局过滤器阻止它反序列化。 但是,可以使用ArrayLengthObjectFilter
对同一对象进行反序列化,因为此过滤器覆盖全局过滤器,并允许数组中最多有五个元素。 这在输出的最后一行是显而易见的。
二十一. Java I/O API新增方法
JDK 9向I/O API添加了一些方便的方法。 第一个是InputStream
类中的一种新方法:
long transferTo(OutputStream out) throws IOException
编写的代码从输入流读取所有字节,以便写入输出流。 现在,不必编写一个循环来从输入流读取字节并将其写入输出流。 transferTo()
方法从输入流读取所有字节,并将它们读取时依次写入指定的输出流。 该方法返回传输的字节数。
Tips
transferTo()
方法不会关闭任何一个流。 当此方法返回时,输入流将在流的末尾。
忽略异常处理和流关闭逻辑,这里是一行代码,将log.txt文件的内容复制到log_copy.txt文件。
new FileInputStream("log.txt").transferTo(new FileOutputStream("log_copy.txt"));
java.nio.Buffer
类在JDK 9中增加了两种新方法:
abstract Buffer duplicate()
abstract Buffer slice()
两种方法返回一个Buffer
,它共享原始缓冲区的内容。 仅当原始缓冲区是直接的或只读时,返回的缓冲区将是直接的或只读的。 duplicate()
方法返回一个缓冲区,其容量,临界,位置和标记值将与原始缓冲区的值相同。 slice()
方法返回一个缓冲区,其位置将为零,容量和临界是此缓冲区中剩余的元素数量,标记不定义。 返回的缓冲区的内容从原始缓冲区的当前位置开始。 来自这些方法的返回缓冲区保持与原始缓冲区无关的位置,限定和标记。 以下代码片段显示了duplicated
和sliced
缓冲区的特征:
IntBuffer b1 = IntBuffer.wrap(new int[]{1, 2, 3, 4});
IntBuffer b2 = b1.duplicate();
IntBuffer b3 = b1.slice();
System.out.println("b1=" + b1);
System.out.println("b2=" + b2);
System.out.println("b2=" + b3);
// Move b1 y 1 pos
b1.get();
IntBuffer b4 = b1.duplicate();
IntBuffer b5 = b1.slice();
System.out.println("b1=" + b1);
System.out.println("b4=" + b4);
System.out.println("b5=" + b5);
b1=java.nio.HeapIntBuffer[pos=0 lim=4 cap=4]
b2=java.nio.HeapIntBuffer[pos=0 lim=4 cap=4]
b2=java.nio.HeapIntBuffer[pos=0 lim=4 cap=4]
b1=java.nio.HeapIntBuffer[pos=1 lim=4 cap=4]
b4=java.nio.HeapIntBuffer[pos=1 lim=4 cap=4]
b5=java.nio.HeapIntBuffer[pos=0 lim=3 cap=3]
二十二. 总结
在JDK 9中,下划线(_)是一个关键字,不能将其本身用作单字符标识符,例如变量名称,方法名称,类型名称等。但是,仍然可以使用下划线多个字符的标识符名称。
JDK 9删除了限制,必须使用try-with-resource块为要管理的资源声明新变量。现在,可以使用final
或有效的final
变量来引用资源由try-with-resources块来管理。
只要推断的类型是可表示的,JDK 9就添加了对匿名类中的钻石操作符的支持。
可以在接口中具有非抽象非默认实例方法或静态方法的私有方法。
JDK 9允许在私有方法上使用@SafeVarargs注解。 JDK 8已经允许它在构造方法,stati方法和
final`方法上。
JDK 9向ProcessBuilder.Redirect
嵌套类添加了DISCARD
的新常量。它的类型是ProcessBuilder.Redirect
。当要丢弃输出时,可以将其用作子进程的输出和错误流的目标。实现通过写入操作系统特定的“空文件”来丢弃输出。
JDK 9为Math
和StrictMath
类添加了几种方法来支持更多的数学运算,如floorDiv(long x, int y)
,floorMod(long x, int y)
,multiplyExact(long x, int y)
,multiplyFull(int x, int y)
,multiplyHigh(long x, long y)
等。
JDK 9向java.util.Optional
类添加了三个方法:ifPresentOrElse()
,of()
和stream()
。 ifPresentOrElse()
方法可以提供两个备选的操作。如果存在值,则执行一个操作。否则,它执行另一个操作。如果存在值,则or()
方法返回Optional
。否则返回指定Supplier
返回的可选项。 stream()
方法返回包含可选中存在的值的元素的顺序流。如果Optional
为空,则返回一个空的流。 stream()
方法在扁平映射中(flat maps)很有用。
JDK 9向Thread
类添加了一个新的静态onSpinWai()
方法。对处理器来说,这是一个纯粹的提示,即调用者线程暂时无法继续,因此可以优化资源使用。在自旋循环中使用它。
Time API在JDK 9中得到了一个提升。在Duration
,LocalDate
,LocalTime
和OffsetTime
类中添加了几种方法。LocalDate
类接收到一个新的datesUntil()
方法,它返回两个日期之间的日期流,以一天或给定期间的增量。 Time API中有几个新的格式化符号。
Matcher
类新增几个现有方法的重载版本,它们用于与StringBuffer
一起工作,以支持使用StringBuilder
。一个为results()
的新方法返回一个Stream<MatchResult>
。 Objects
类收到了几个新的实用方法来检查数组和集合的范围。
ava.util.Arrays
新增了几种方法,可以比较数组和部分数组的相等性和不匹配性。
Javadoc在JDK 9中得到了增强。它支持HTML5。可以使用一个新的选项-html5
与javadoc工具一起生成HTML5格式的Javadoc。对所有模块,包,类型,成员和形式参数类型的名称进行索引,并使用新的搜索功能进行搜索。 Javadoc在每个主页的右上角显示一个搜索框,可用于搜索索引条款。还可以在Javadoc中使用一个新的标签@index
来创建用户定义的术语。使用客户端JavaScript执行搜索,并且不进行服务器通信。
许多浏览器供应商已经删除了对Java浏览器插件的支持,或者将在不久的将来删除它。记住这一点,JDK 9不赞成使用Applet API。 java.applet包和javax.swing.JApplet
类中的所有类型已被弃用。 appletviewer工具也已被弃用。
JDK 6通过java.awt.Desktop
类添加了对平台特定桌面功能的有限支持,例如在用户默认浏览器中打开URI,在用户默认邮件客户端中打开mailto URI,以及使用注册的应用打开,编辑和打印文件。如果Java SE 9在当前平台上可用,许多系统和应用程序事件通知都会提供特定于平台的桌面支持,并为其添加了公共API支持。为了支持这么多新的桌面功能,Java SE 9向java.desktop模块添加了一个新的包java.awt.desktop。 java.awt.Desktop
类也增加了很多新的方法。在JDK 9中,Desktop API支持24个平台特定的桌面操作和通知,例如当附加的显示进入或退出节电模式,系统进入睡眠模式或系统唤醒后的通知等。
为了解决反序列化带来的安全风险,JDK 9引入了一个对象输入过滤器的概念,可以用来验证被反序列化的对象,如果没有通过测试,则可以停止反序列化过程。对象输入过滤器是新接口java.io.ObjectInputFilter
的实例。可以指定可以在反序列化任何对象时使用的全系统全局过滤器。可以使用新的jdk.serialFilter系统属性,使用JAVA_HOME\conf\security\java.security文件中jdk.serialFilter的属性,或使用ObjectInputFilter.Config
类的setSerialFilter()
方法来指定全局过滤器。可以使用其setObjectInputFilter()
方法在ObjectInputStream
上设置本地过滤器,该方法将覆盖全局过滤器。
java.io.InputStream
类新增一个称为transferTo(OutputStream out)
的方法,可用于从输入流读取所有字节,并将它们顺序写入指定的输出流。该方法不关闭任一流。 java.nio.Buffer
类接收到两个方法,duplicate()
和slice()
——可用于复制和拼接缓冲区。复制和分片缓冲区与原始缓冲区共享其内容。但是他们保持自己的位置,限定和标记,独立于原始缓冲区。