文档结构  
翻译进度:已翻译     翻译赏金:2 元 (?)    ¥ 我要打赏

每隔几年,Java就会发布一个新版本,而且在JavaOne演讲上兜售其新的语言结构和API,并赞美其带来的好处。同时开发者们都激动的跃跃欲试,急于使用新功能。 这是一个乐观的画面,但是请不要忘了这样一个事实,即大多数开发商只能维护和增强现有的应用,而不是从头开始创建新的。

大多数的应用,特别是商业发行的, 需要向后兼容早期版本的java, 就无法支持那些比较优秀的新特性。最后,大多数客户和最终用户, 特别是那些在企业里的,会很谨慎的采用最新公布的Java平台,宁愿等待,直到他们相信新的平台是可靠的。

第 1 段(可获 1.49 积分)

这会导致开发人员想要使用新功能时出现问题。 你喜欢在代码中使用默认接口方法吗? 如果你的应用程序需要运行在Java 7或更早版本的Java上,那你就不走运了。 你想使用 java.util.concurrent.threadlocalrandom 类在多线程应用程序中生成伪随机数吗? 如果你的应用需要运行在 Java 6 、7、8或9,那就是不可能的了。

当新版本发布的时候,传统的开发者会像孩子一样将鼻子贴在糖果商店的窗户上想着: 他们不让我进去,这可能令人失望和沮丧。

第 2 段(可获 1.36 积分)

那么在即将到来的Java 9的发布版中有没有针对开发传统Java应用程序的东西?在接下来的一年里,有没有什么事情能使你的生活更加轻松而同时又能让你使用到那些令人兴奋的新的功能特性?幸运的是,答案是有。

传统开发人员在Java 9 之前可以做哪些事情

你可以把新平台的特性硬塞进那些需要向后兼容的传统的程序。具体地说,你可以有多种方式利用新的API,但是可能会有点不够雅观。

当你的程序需要运行在无法支持某个新API的早期版本的Java环境上时,你可以使用后期绑定来尝试使用该API。例如,你想要使用Java 8引入的java.util.stream.LongStream类,并且要使用LongStream’s anyMatch(LongPredicate)方法,但是应用程序需要在Java 7 上运行。你就可以创建如下的帮助类:

第 3 段(可获 2.13 积分)

有很多方法可以让这个实现稍微简单一些,或者更加通用一些,或者更加高效一些,但是你知道这个思路就好了。

在Java 8里,你会调用LongStream.anyMatch(thePredicate)方法,但是在其他任何版本里你就可以调用 LongStreamHelper.anyMatch(theLongStream, thePredicate) 方法了。如果在Java 8上它能够正常工作,但是如果运行在Java 7 ,它就会抛出一个NotImplementedException异常。

为什么它会如此难堪?首先,当你想要访问大量API的时候,它会变得极其的复杂和讨厌。(实际上,使用一个单独的API就已经变得讨厌了。)其次,它也是类型不安全的,因为你不能在你的代码里真正的提到LongStream或LongPredicate。最后,由于反射的开销以及额外的try-catch代码块它也变得相当低效了。因此,虽然你可以这么做,但是它并没有那么有趣,并且一不小心就很容易出错的。

第 4 段(可获 1.94 积分)

尽管你可以访问新的API而且仍然你能够让给你的代码保持向后兼容性,你还是无法通过这样来使用新的语言结构。例如,你想使用lambda表达式在你的代码中,但是它还要能在Java 7中运行。那你就不走运了。你的Java 编译器不会让你指定一个比你的目标兼容级别要高的源代码兼容级别。所以,如果我们设置了源代码兼容级别为1.8(例如,Java 8),而目标兼容级别设置为1.7 (Java 7),它就不会让你通过的。

多版本JAR文件的支持终于来了

目前为止,还是没有一种好的方式来使用最新的Java特性同时还要允许应用程序运行到不支持此应用的早期版本的Java上。Java 9 提供了一种方式可以实现这种需求,同时在新的API和新的Java语言结构层面做了支持:它被称作多版本的JAR文件

第 5 段(可获 2 积分)

多版本JAR文件看起来就像老式的JAR文件一样,只是有一个关键的增强:就是当你把使用了最新的Java 9特性的类放入这种JAR文件中时,它就会被放到一个特定的区域中。如果你运行Java 9,JVM就会识别到这个区域,然后就加载使用该区域里的类,并且忽略掉JAR文件中普通位置存放的同名的任意类。

然而,如果你运行在Java 8或更早的版本上,JVM就不会知道有这个特殊的区域。它就会忽略并且只会运行那些存在于JAR文件普通区域内的类。当Java 10发布的时候,就会有另外一个特殊的区域去存放那些使用了Java 10的新特性的类,以此类推。

第 6 段(可获 1.54 积分)

JEP 238,也就是制订了多版本JAR文件规范的Java增强建议,给了一个简单的例子。考虑一个包含了四个类的JAR文件,它们能在Java 8 或以前版本里使用:

JAR根

      - A.class
      - B.class
      - C.class
      - D.class

那么当Java 9 出来了以后,你就可以使用Java 9定义的一些新特性来重写A和B类。再往后一段时间,Java 10又出来了,你就可以使用Java 10的新特性来重新A类。而同时你的应用应该还能在Java 8上工作。新的多版本JAR文件看起来是这样的:

第 7 段(可获 1.13 积分)

JAR根
      - A.class

      - B.class
      - C.class
      - D.class
      - META-INF

           Versions

                - 9

                   - A.class
                   - B.class

               - 10
                   - A.class

除了新的结构,JAR文件的清单(manifest)还包含了一个用于说明该文件为多版本JAR的标识符。

当你在Java 8 的JVM上运行该JAR文件时,\META-INF\Versions部分会被忽略掉,因为JVM不知道它是个什么鬼而且也不会去寻找它。因此也就只有原版的A、B、C和D类会被使用到。

而当它运行在Java 9上时,位于\META-INF\Versions\9下面的类就会被用到,而且会覆盖掉A和B类的原始版本,但是位于\META-INF\Versions\10下的类就会被忽略掉。

第 8 段(可获 1.21 积分)

而当它运行在Java 10上时,\META-INF\Versions下所有的分支都会被使用到;具体来说,就是Java 10版本的类A,Java 9版本的类B以及默认的版本的类C和D会被使用到。

因此,如果你想在你的应用中使用Java 9新的ProcessBuilder API而又想让你的应用在Java 8上,那就可以把新版本的使用ProcessBuilder的类文件在JAR文件的\META-INF\Versions\9部分,而把没有使用ProcessBuilder的旧版本的类文件放在JAR文件的默认部分里。这就是使用Java 9的新特性同时又要保持向后兼容性的一个简单的方法。

第 9 段(可获 1.49 积分)

Java 9的JDK包含了一个可以支持创建多版本JAR文件的jar.exe工具。其他的非JDK工具也同样支持。

Java 9: 模块无处不在

Java 9的模块系统(也就是著名的Jigsaw项目)无疑是Java 9最大的变化。模块化的其中一个目标就是要增强Java的封装机制以使得开发者可以选择指定那些API需要暴漏给其他组件,还可以依靠JVM来实现封装。模块化的封装性要比由在类和方法上的public/protected/private访问修饰符提供的封装强大很多。

第 10 段(可获 1.25 积分)

模块化的第二个目标是,指定某个模块是被哪些其他模块所必须依赖的,并且确保在程序执行前所有必须的模块都已经存在。从这个意义上说,模块化就比传统的类路径(classpath)机制要强大很多,因为classpath不会被提前检查到,而且只有当类正常要被使用时才会将由于类未找到导致的错误抛出。那就意味着,错误的classpath只能在程序运行了很长一段时间甚至已经被运行了很多次之后才会被发现。

整个模块化系统很庞大也很复杂,而要想完整讨论它就超出本篇文章的范畴了。(这里有一篇不错且深入的解释。) 当然了,我会集中精力讲述那些用于支持统应用开发人员的模块化的一些方面。

第 11 段(可获 1.59 积分)

模块化是个好东西,开发人员应该尽可能的将他们的代码模块化,即使其余的传统应用(还)没有模块化。幸运的是,模块化规范使得这一切可以很容易实现。

首先,一个JAR文件如果在它的文件根部包含了一个名为module-info.class(由module-info.java编译而来)的文件,那么它就可以变成模块化(并且成为一个模块)的了。module-info.java文件包含有了一些元数据信息,比如模块的名称,那些包会被导出(比如,对外部可见),当前模块依赖哪些模块,还有其他一些信息。

第 12 段(可获 1.15 积分)

module-info.class里的信息只有在JVM寻找它的时候才是可见的,也就是说模块化的JAR文件在老版本的Java上运行时会被当成是普通的JAR文件(假定代码是以早期版本的Java作为目标环境进行编译的。严格的说,你需要耍个小聪明,仍然将Java 9作为module-info.class的目标环境,这确实是可操作的)。

这也意味着你仍然可以在Java 8 或者早期版本上运行你的模块化JAR文件,当然前提是它们与早期版本的Java是兼容的。还要注意的是module-info.class文件可以在有限制的条件下被放置在多版本JAR文件的版本区域内

第 13 段(可获 1.48 积分)

在Java 9中,同时存在着类路径和模块路径两种东西。 类路径就还是像以前那样工作。如果一个模块化的JAR文件被放在了类路径里,它会像其他任何JAR文件一样看待。这意味着如果你已经模块化了一个JAR文件,但是你还没有做好准备将它看成一个模块来对待,那么你就可以把它放在类路径中,那它就可以像往常一样工作。你现有的代码也应该能够正确处理它。

还有需要注意的是,所有在类路径上的JAR文件的集合会被看作是一个单独的未命名模块的一部分。 这个未命名的模块会被当作是一个普通的模块,但是它会将所有的东西都导出给其他模块,并且它能够访问所有其他模块。这就是说,如果你有一个模块化的Java应用,但是同时还有一些旧的类库还没有被模块化(也许永远都不会),那么你就可以将它们放在类路径里,这样一切照样都会工作的很好。

第 14 段(可获 2.16 积分)

Java 9 包含了一个模块路径,跟类路径一样会起作用。在模块路径下使用模块,JVM能够在编译和运行时同时检查并确保所有必须的模块能够找到,并且如果有丢失的会报告错误。类路径中的所有JAR文件,作为未命名模块的成员可以被模块路径中的模块所访问到,反之亦然。

将一个JAR文件从类路径转移到模块路径中是很容易的,这样可以得到模块化带来的好处。首先,你可以添加一个module-info.class文件到JAR文件里,然后将模块化的JAR文件移到模块路径中。新创建的模块仍然可以访问到被留在类路径中的JAR文件,因为它们属于未命名模块中的一部分,而它们是可以访问的。

第 15 段(可获 1.76 积分)

当然也有可能你并不想将一个JAR文件进行模式化,或者那个JAR文件是其他人写的,因此你没办法亲自将之模块化。在这种情况下,你仍然可以将该JAR文件放入模块路径中;它会变成一个自动的模块。

一个自动的模块会被认为成模块即时它没有包含一个module-info.class文件。这个模块的名字跟JAR文件的名字一样,而且可以明确的被其他模块所依赖。它会自动的导出所有可公开访问的API, 并且能够读取(也就是依赖)其他每一个命名过和未命名的模块。

第 16 段(可获 1.43 积分)

这也就意味着可以什么都不用做就把一个未模块化的类路径下的JAR文件转换成一个模块:原有的JAR文件自动会变成模块,但是还少了一些信息,比如判断是否所有必须依赖的模块真的存在,或者什么东西是缺失的。

并不是没一个未被模块化的JAR文件都可以被移到模块路径之后就变成了一个自动的模块。有个规则是一个包只能成为一个命名的模块的一部分。所以如果一个包存在于不止一个JAR文件里,那么只有其中一个含有该包的JAR文件会被变成一个自动的模块——其他的仍被留在了类路径下,并成为未命名模块的一部分。

第 17 段(可获 1.55 积分)

这些我描述的机制听起有点复杂,但是其实它是相当简单的。它的真正含义是你可以将你的旧JAR文件留在类路径里,或者你可以将它们移到模块路径中。你可以对他们进行模块化,或者不用进行模式化。并且一旦你的旧JAR文件被模块化了之后,你依然可以将它们留在类路径里或者放在模块路径里。

在大部分情形下,一切都如往常一样正常工作。你的既有JAR文件在新的模块系统里应该就跟在家里一样(即不会显得陌生)。你模块化的东西越多,那么更多的依赖信息就可以被检查到,并且缺失的模块和API能够在开发周期更早期的时候就能被发现,因此极可能节省你不少的工作量。

第 18 段(可获 1.55 积分)

自已动手做Java 9:模块化的JDK 和 Jlink

遗留的Java应用会有一个问题,就是终端用户可能没有使用正确的Java运行环境。可以保证该Java应用能够运行的一种方式是随着应用一起提供Java环境。Java允许创建一个私有的或者可以再发行的JRE,可以随着应用一起发行。JDK/JRE 的安装里面提到了一些关于如何创建一个私有的JRE的说明。通常情况下,你可以获取到JDK安装创建的JRE文件层次结构,,保留需要的文件以及那些你的应用需要的功能的可选文件。

第 19 段(可获 1.34 积分)

这个过程有点麻烦:你需要保持安装文件的层次结构,而且还得必须得小心点以免你遗漏了什么可能需要的文件或目录,并且你总不会想留下一些不需要的东西吧,虽然留下一些没什么坏处,但它们还是会占用一些不必要的空间的。这可是一个很容易犯的错误。

那么会什么不让JDK帮你做这份工作呢?

有了Java 9,现在要给你的应用以及任何它运行所需的东西创建一个独立的环境,都成为可能。 因此就不用担心用户的电脑上错误的Java环境了,也不用担心你创建不了正确的私有JRE环境了。

第 20 段(可获 1.65 积分)

创建这些自包含的运行时镜像的关键是模块化系统。不仅你可以模块化你自己的代码,连Java 9 JDK本身现在也是模块化了的。Java的类库现在是模块的集合,同样的JDK自身的工具也是如此。模块化系统要求你指定你的代码所要求的基类的类模块,而这反过来则需要指定你所需要的JDK的哪部分。

为了能将一切放到一起,你可以使用Java 9的一个新工具,叫做jlink。当你运行jlink时,你会得到你需要的确切的文件层次结构来运行你的应用——不多也不少,刚刚好。它会比标准的JRE还要小很多,并且是基于平台定制的 (意思是说,特定于操作系统和机器),因此如果你想要为不同的平台创建运行时镜像,你就需要在你想要构建镜像的每个平台的安装上下文环境上运行jlink命令来完成。

第 21 段(可获 2.11 积分)

还要注意的是,如果你在一个没有任何东西进行过模块化的应用上运行jlink的话,那就没有足够的信息来将JRE进行缩小精简,因此jlink将在没有选择的情况下不得不将整个JRE进行打包。即便如此,你还是有机会使用jlink将JRE本身进行打包,因此你也不用担心是否正确复制了所需的文件层次结构。

有了jlink,打包你的应用程序以及所有它运行所需的东西就变得相当容易了,都不用担心会出错,而且只会打包你的应用所需的那些运行时部分。这样,你的遗留Java应用就有了一个保证可以运行的环境了。

第 22 段(可获 1.5 积分)

当旧的东西遇到了新的

要维护一个遗留的Java应用有一个问题,就是当新版本的Java推出的时候,你就没法享受它带来的所有的那些乐趣了。Java 9,跟他的前任们一样,有一大堆优秀的新的API和语言特性,但是那些拥有过去经验的开发人员,可能就认为如果不打破对早期版本的Java的兼容性,就没有办法使用这些新的特性。

值得赞扬的是,Java 9的设计者们似乎已经意识到了这点,并且他们已经通过努力的工作,使得开发人员们可以使用这些新的特性,而不用担心支持老版本的Java。

第 23 段(可获 1.36 积分)

多版本的JAR文件使得开发人员能够利用Java 9的新特性进行工作, 并且将它们隔离到JAR文件的某一部分里,这样老版本的Java就无法看到它们。这样使得开发者很容易写出适合Java 9的代码,保留为Java 8或更早版本所写的代码,并且允许运行时环境选择它可以运行的类。

Java 模块使得开发人员通过以模块化方式书写任意新的JAR文件来得到更好的依赖检查,同时旧有的代码保持非模块化即可。该系统是相当宽容的,是为了渐进迁移而设计的,而且几乎能和完全不知道模块化系统的遗留代码很好的在一起工作。

第 24 段(可获 1.4 积分)

模块化的JDK和jlink使得用户很容易的创建自包含的运行时镜像,这样一个应用就可以保证和它能够运行的运行时环境一起发布,并且它所需要的一切东西都保证会在那里。在此之前,这个是相当容易出错的过程,但是在Java 9 里,这个工具的存在就是为了使这个过程能够工作。

不像以往版本的Java一样,Java 9的新特性已经准备好了,可以为你所用,即使你有一个旧的Java应用并且需要保证客户能够运行它——不用考虑他们是否和你一样急切的想要升级到最新版本的Java。

第 25 段(可获 1.4 积分)

文章评论