たまたま業務で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へダウングレードするような機会が来ないことを祈ってます。。。