Java

Java8 で作られたアプリケーションをJava6で動かす

More than 1 year has passed since last update.

たまたま業務でJava8で作られたアプリケーションをJava6で動かすように修正するような仕事があったので、珍しいことだと思うのでまとめようと思う。

普通このようなことをやることはないと思う…

(そして私はこんな仕事やりたくなかったw)


背景

別のあるプロジェクトで使っていたRestful Java8アプリケーションがあり、

そのアプリケーションを自分のいるプロジェクトに適用しようとしたところ、

アプリケーションの配置先サーバがRed Hat Enterprise Linux 5.1という…

配置先のサーバのOSをバージョン上げてほしかったが、他のアプリケーションが動いているからムリとのこと…

そんなことからJava8 → Java6へのダウングレードを行うことになった。

ついでにWikiによると、Red Hat Enterprise Linux 5.1は2007年11月7日にリリースされた10年もの。

標準サポート期限は2017年3月31日までで既に標準サポート期限は切れている。

延長サポートは2020年11月30日までとなっている…


Java8での仕様変更や追加

Java8で追加された機能の代表的なものをいくつかあげよう。


  • Lambda式

  • メソッド参照

  • Stream API

  • Functional interfaces

  • try-with-resources

  • Interface Default,staticメソッド

  • 日時API(LocalDateTimeなど)

これら以外にも、多くの機能が追加されている。

詳細はリリースノートを見てほしい。

このようにJava8では多くのAPIや記述方法の変更があり、Javaとして非常に変化が大きかった。


Java backporting tools

Java backporting toolsとは、あるバージョンから古いバージョンへJava classファイルを変換するためのプログラムであり、

今回使用したのは以下の2つ。


Retrolambda

RetroLambdaはMaven,Gradleのプラグインで、

ビルドするときにJava8の記述を下位のバージョンへ変換してくれる。

ビルドされたclassファイルを見ると、XXX.classとは別に、XXX$$Lambda$9.classというようなclassファイルが出力されており、

Lambda式などのJava 8の記述は別のクラスファイルとして処理されていることが分かる。

以下は、Retrolambdaで対応しているもの。


  • lambda式

  • メソッド参照

  • try-with-resources

  • Interface Default,staticメソッド(限定サポート)

新しい記述はほぼ全て対応していることになる。


使い方

ここではMavenでの使い方を解説する。Gradleの方はこちらのReadMeを参照。

pom.xmlに以下のように記述する。

            <plugin>

<groupId>net.orfjackal.retrolambda</groupId>
<artifactId>retrolambda-maven-plugin</artifactId>
<version>2.5.1</version>
<executions>
<execution>
<goals>
<goal>process-main</goal>
<goal>process-test</goal>
</goals>
</execution>
</executions>
<configuration>
<target>1.6</target>
</configuration>
</plugin>

これでmvnのライフサイクル上で、自動でビルドされるようになる。

configuration.targetは変換後のJavaバージョンを記述する。

開発するとき(IDEなどの設定)は、Java8 SDKを使用して開発し、

ビルドしてできたJarの実行はJava6で行う。

この方法の問題点はJava8のAPIを使用していてもコンパイルできてしまうことである(もちろん実行時にエラーになる)が、

それを警告するようなプラグインも存在するらしい。(自分は未使用)


streamsupport

streamsupportはJava8の新APIを多くカバーしている。

カバーしているものは以下。


  • Streams API

  • CompletableFuture API

  • Parallel array operations

  • Functional interfaces


使い方


pom.xml

pom.xmlのdependenciesに以下を追加する。

(streamsupport.versionはpropertiesに定義。)

        <!-- To use Java8 stream,functional API in Java6 -->

<dependency>
<groupId>net.sourceforge.streamsupport</groupId>
<artifactId>streamsupport</artifactId>
<version>${streamsupport.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.streamsupport</groupId>
<artifactId>streamsupport-cfuture</artifactId>
<version>${streamsupport.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.streamsupport</groupId>
<artifactId>streamsupport-flow</artifactId>
<version>${streamsupport.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.streamsupport</groupId>
<artifactId>streamsupport-atomic</artifactId>
<version>${streamsupport.version}</version>
</dependency>
<dependency>
<groupId>net.sourceforge.streamsupport</groupId>
<artifactId>streamsupport-literal</artifactId>
<version>${streamsupport.version}</version>
</dependency>


基本的な使い方

基本的にはjava8 APIと異なるパッケージ名で、同じクラス名、同じメソッド名で呼び出すことができる。

例えばCompletableFuture APIは

import java.util.concurrent.CompletableFuture;

import java8.util.concurrent.CompletableFuture;

のようになる。


Stream APIの使い方

RefStreams.ofでstreamインスタンスを作成できる。


import java8.util.stream.RefStreams;

RefStreams.of("a", "b", "c")
.map(String::toUpperCase)
.forEach(System.out::println);

Java 8では以下のようにListクラスにstreamメソッドが追加されている。

この場合は以下のようになる。

import java.util.Arrays;

import java.util.List;

List<String> list = Arrays.asList("a", "b", "c");

list.stream()
.map(String::toUpperCase)
.forEach(System.out::println);

import java.util.Arrays;

import java.util.List;
import java8.util.stream.StreamSupport;

List<String> list = Arrays.asList("a", "b", "c");

StreamSupport.stream(list)
.map(String::toUpperCase)
.forEach(System.out::println);


所感

これを読んでいる人には、Java8のアプリケーションをJava6へダウングレードするような機会が来ないことを祈ってます。。。