Java 9 揭秘(20. JDK 9中API层次的改变下)

Tips
做一个终身学习的人。

Java 9 揭秘(20. JDK 9中API层次的改变下)_第1张图片
Java 9

十六. 数组比较

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()方法将整数值视为无符号。 空数组的字符拼写小于非空数组。 两个空数组相等。 以下是对于intcompare()方法的两个版本:

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 

javadoc工具位于JDK_HOME\bin目录中。 使用--help选项运行工具打印其使用说明和所有选项。

NetBeans IDE可以为项目生成Javadoc。 在项目的“属性”对话框中,选择“Build”➤“Documenting”以获取Javadoc属性页,可以在其中指定javadoc工具的所有选项。 要生成Javadoc,请从项目的右键菜单选项中选择“Generate Javadoc”。

JDK 9保留了三个Frame或无Frame的Javadoc布局。 左上角的框架包含三个链接:所有类,所有包和所有模块。 在JDK 9中添加了ALL MODULES链接,其中显示了所有模块的列表。 ALL CLASSES链接可以查看左下框架中的所有类。 其他两个链接可查看所有软件包,模块中的所有软件包以及所有模块。 下面显示了Javadoc页面的更改。

Java 9 揭秘(20. JDK 9中API层次的改变下)_第2张图片
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 }出现在Javadoc中,其中是要被索引的关键字,而是关键字的描述。 以下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,如右图所示。

Java 9 揭秘(20. JDK 9中API层次的改变下)_第3张图片
搜索结果

可以使用向上和向下箭头键浏览搜索结果。 可以通过以下两种方式查看搜索结果的详细信息:

  • 单击搜索结果以打开该主题的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());
    }
});

分别激活和停用用户会话时,会调用注册的UserSessionListeneruserSessionActivated()userSessionDeactivated()方法。 两个方法都将一个UserSessionEvent对象作为参数。 UserSessionEvent类的getReason()方法返回一个UserSessionEvent.Reason,它是一个枚举,它的常量定义了用户会话更改的原因。 枚举有四个常量:CONSOLELOCKREMOTEUNSPECIFIEDCONSOLEREMOTE常数表示用户会话分别与控制台终端和远程终端连接/断开的原因。 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()方法来实现。 如果要从字符串中的模式创建过滤器,请遵循以下规则:
有五个过滤条件,其中四个是限制。 它们是maxarraymaxdepthmaxrefsmaxbytes。 可以使用name = value来设置它们,其中name是这些关键字,value是限制。 如果模式包含等号(=),则模式必须使用这四个关键字作为名称。 第五个过滤条件用于指定类名形式的模式:

/
  • 如果一个类是未命名的模块,则该模式将与类名匹配。 如果对象是一个数组,则数组的组件类型的类名用于匹配模式,而不是数组本身的类名。 以下是匹配类名称的模式的所有规则:
  • 如果类名与模式匹配,则允许对象反序列化。
  • 以“!” 模式开头的字符被视为逻辑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()方法返回一个缓冲区,其位置将为零,容量和临界是此缓冲区中剩余的元素数量,标记不定义。 返回的缓冲区的内容从原始缓冲区的当前位置开始。 来自这些方法的返回缓冲区保持与原始缓冲区无关的位置,限定和标记。 以下代码片段显示了duplicatedsliced缓冲区的特征:

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为MathStrictMath类添加了几种方法来支持更多的数学运算,如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中得到了一个提升。在DurationLocalDateLocalTimeOffsetTime类中添加了几种方法。LocalDate类接收到一个新的datesUntil()方法,它返回两个日期之间的日期流,以一天或给定期间的增量。 Time API中有几个新的格式化符号。

Matcher类新增几个现有方法的重载版本,它们用于与StringBuffer一起工作,以支持使用StringBuilder。一个为results()的新方法返回一个StreamObjects类收到了几个新的实用方法来检查数组和集合的范围。

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()——可用于复制和拼接缓冲区。复制和分片缓冲区与原始缓冲区共享其内容。但是他们保持自己的位置,限定和标记,独立于原始缓冲区。

你可能感兴趣的:(Java 9 揭秘(20. JDK 9中API层次的改变下))