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

たまたま業務でJava8で作られたアプリケーションをJava6で動かすように修正するような仕事があったので、珍しいことだと思うのでまとめようと思う。
普通このようなことをやることはないと思う…
（そして私はこんな仕事やりたくなかったｗ）

# 背景
別のあるプロジェクトで使っていたRestful Java8アプリケーションがあり、
そのアプリケーションを自分のいるプロジェクトに適用しようとしたところ、
アプリケーションの配置先サーバがRed Hat Enterprise Linux 5.1という…

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

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

ついでに[Wiki](https://ja.wikipedia.org/wiki/Red_Hat_Enterprise_Linux)によると、Red Hat Enterprise Linux 5.1は2007年11月7日にリリースされた１０年もの。
標準サポート期限は2017年3月31日までで既に標準サポート期限は切れている。
延長サポートは2020年11月30日までとなっている…

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

* Lambda式
* メソッド参照
* Stream API
* Functional interfaces
* try-with-resources
* Interface Default,staticメソッド
* 日時API（LocalDateTimeなど）

これら以外にも、多くの機能が追加されている。
詳細は[リリースノート](http://www.oracle.com/technetwork/java/javase/8-whats-new-2157071.html)を見てほしい。

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

# Java backporting tools
[Java backporting tools](https://en.wikipedia.org/wiki/Java_backporting_tools)とは、あるバージョンから古いバージョンへJava classファイルを変換するためのプログラムであり、
今回使用したのは以下の２つ。

* [Retrolambda](https://github.com/orfjackal/retrolambda)
* [streamsupport](https://github.com/streamsupport/streamsupport)

## [Retrolambda](https://github.com/orfjackal/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](https://github.com/evant/gradle-retrolambda)を参照。

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

```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を使用していてもコンパイルできてしまうことである（もちろん実行時にエラーになる）が、
それを警告するような[プラグイン](http://www.mojohaus.org/animal-sniffer/)も存在するらしい。（自分は未使用）

## [streamsupport](https://github.com/streamsupport/streamsupport)

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

* Streams API
* CompletableFuture API
* Parallel array operations
* Functional interfaces

### 使い方

#### pom.xml
pom.xmlの`dependencies`に以下を追加する。
（streamsupport.versionは`properties`に定義。）

```xml
        <!-- 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は

```java
import java.util.concurrent.CompletableFuture;
```
↓

```java
import java8.util.concurrent.CompletableFuture;
```
のようになる。

#### Stream APIの使い方

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

```java

import java8.util.stream.RefStreams;

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

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

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