覚えたら書く

IT関係のデベロッパとして日々覚えたことを書き残したいです。twitter: @yyoshikaw

Retrolambda - Java6・7でもラムダ式を使いたい

Java8がリリースされて結構な期間が過ぎました。実行環境が全てJava8ならいいのですが、世の中色々な制約がありますのでJava7やJava6が実行環境になっている場合も多々あります。
その実行環境で動作するJavaアプリケーションを開発する側は当然のようにJava6やJava7の開発環境でコードを書く必要が出てきます。
そうなると、Java8から導入されたラムダ式(や Stream API)の恩恵に預かることはできません。

そんな悲しい思いをしている時にはRetrolambdaの出番です。

ラムダ式で書かれたJava8用のソースコードからJava7やJava6で動作するモジュールを生成してくれる神様のようなツールです。
素敵なRetrolambda、これはもう使うしかありませんね。


Javaのラムダ式はStream APIと組み合わせることにより大きな力を発揮する部分が多いです。
が、今回紹介しているRetrolambdaはラムダ式などの解釈はうまくやってくれますがStream APIを提供してくれるものではありません。
そのためStream APIを利用したコードを書いてしまうと、Java6やJava7環境では(Stream APIのクラスやメソッドがないので)動作しません。

この問題を解決するため、今回はLightweight-Stream-APIを組み合わせます。

Lightweight-Stream-APIは、Java標準のStream APIと同等(むしろそれ以上)の機能を利用することができるようになります。
これでラムダ式+Stream APIを用いてJava6やJava7の開発を行うことができるようになります。


開発環境の構築

本エントリでは、Eclipse + Maven で開発環境を構築してサンプルコードを記述しています

プログラムをビルドする環境のイメージ

Retrolambdaを使った開発を行う場合、

Java8用のコードがコンパイル可能な状況でソースコードを記述する
 ↓
Retrolambdaを介在させて、Java7(またはJava6)用のjarを生成する

というイメージの開発の流れになります。

Java8用のコードを記述するための設定

Java8用の開発環境を構築するためにpom.xmlに以下の記述をします

<plugins>
    <plugin>
        <groupId>org.apache.maven.plugins</groupId>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
            <source>1.8</source>
            <target>1.8</target>
        </configuration>
    </plugin>
</plugins>

Retrolambdaに関する設定

開発するためのプロジェクトにおいてpom.xmlにRetrolambdaに関する以下の記述が必要となります。

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

configuration要素配下のtargetに最終的に実行するJava環境に合わせた値を設定する必要があります。

  • Java6の場合 ⇒ 1.6
  • Java7の場合 ⇒ 1.7

Lightweight-Stream-APIに関する設定

今回の開発ではStream APIも使用したいのでpom.xmlにLightweight-Stream-APIに関する以下内容を追記します

<dependency>
    <groupId>com.annimon</groupId>
    <artifactId>stream</artifactId>
    <version>1.1.3</version>
</dependency>

pom.xmlの全体

私がEclipse上で試した際のpom.xmlは以下のようになりました(必要な部分を抜粋しています)

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

  <dependencies>
    <dependency>
      <groupId>com.annimon</groupId>
      <artifactId>stream</artifactId>
      <version>1.1.3</version>
    </dependency>
  </dependencies>

  <build>
  <plugins>
      <plugin>
          <groupId>org.apache.maven.plugins</groupId>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.1</version>
          <configuration>
              <source>1.8</source>
              <target>1.8</target>
          </configuration>
      </plugin>
      <plugin>
      <groupId>net.orfjackal.retrolambda</groupId>
      <artifactId>retrolambda-maven-plugin</artifactId>
      <version>2.3.0</version>
      <executions>
          <execution>
              <goals>
                  <goal>process-main</goal>
                      <goal>process-test</goal>
              </goals>
          </execution>
      </executions>
      <configuration>
              <target>1.6</target>
          </configuration>
      </plugin>
  </plugins>
  <pluginManagement>
      <plugins>
          <plugin>
          <groupId>org.eclipse.m2e</groupId>
          <artifactId>lifecycle-mapping</artifactId>
          <version>1.0.0</version>
          <configuration>
              <lifecycleMappingMetadata>
              <pluginExecutions>
                  <pluginExecution>
                      <pluginExecutionFilter>
                      <groupId>net.orfjackal.retrolambda</groupId>
                  <artifactId>retrolambda-maven-plugin</artifactId>
                  <versionRange>[2.3.0,)</versionRange>
                  <goals>
                      <goal>process-test</goal>
                      <goal>process-main</goal>
                      </goals>
                  </pluginExecutionFilter>
                  <action>
                      <ignore></ignore>
                  </action>
                      </pluginExecution>
                      </pluginExecutions>
              </lifecycleMappingMetadata>
          </configuration>
        </plugin>
    </plugins>
  </pluginManagement>
  </build>
</project>

ビルド方法

通常のMavenプロジェクトと同様にmvn clean ⇒ mvn install の実行などでビルドしてください。
これで自動的にラムダ式などで書かれたコードであっても、Java6やJava7で実行できるモジュールが生成されます。

ちなみに、Mavenでのビルドを行うと以下のようなログがコンソールに出力されます

[INFO] Scanning for projects...
[INFO]                                                                         
[INFO] ------------------------------------------------------------------------
[INFO] Building sample.lambda 0.0.1
[INFO] ------------------------------------------------------------------------
[INFO] 
[INFO] --- maven-resources-plugin:2.6:resources (default-resources) @ sample.lambda ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\work\sample.lambda\src\main\resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:compile (default-compile) @ sample.lambda ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- retrolambda-maven-plugin:2.3.0:process-main (default) @ sample.lambda ---
[INFO] Processing classes with Retrolambda
Bytecode version: 50 (Java 6)
Default methods:  false
Input directory:  C:\work\sample.lambda\target\classes
Output directory: C:\work\sample.lambda\target\classes
Classpath:        [C:\work\sample.lambda\target\classes, C:\Users\yukiv\.m2\repository\com\annimon\stream\1.1.3\stream-1.1.3.jar]
Included files:   all
Agent enabled:    false
WARNING: The interface org/sample/lambda/ng/SampleInteface has a default method "defaultmedhod" but backporting default methods is not enabled
[INFO] 
[INFO] --- maven-resources-plugin:2.6:testResources (default-testResources) @ sample.lambda ---
[INFO] Using 'UTF-8' encoding to copy filtered resources.
[INFO] skip non existing resourceDirectory C:\work\sample.lambda\src\test\resources
[INFO] 
[INFO] --- maven-compiler-plugin:3.1:testCompile (default-testCompile) @ sample.lambda ---
[INFO] Nothing to compile - all classes are up to date
[INFO] 
[INFO] --- retrolambda-maven-plugin:2.3.0:process-test (default) @ sample.lambda ---
[INFO] Processing classes with Retrolambda
Bytecode version: 50 (Java 6)
Default methods:  false
Input directory:  C:\work\sample.lambda\target\test-classes
Output directory: C:\work\sample.lambda\target\test-classes
Classpath:        [C:\work\sample.lambda\target\test-classes, C:\work\sample.lambda\target\classes, C:\Users\yukiv\.m2\repository\junit\junit\3.8.1\junit-3.8.1.jar, C:\Users\yukiv\.m2\repository\com\annimon\stream\1.1.3\stream-1.1.3.jar]
Included files:   all
Agent enabled:    false
[INFO] 
[INFO] --- maven-surefire-plugin:2.12.4:test (default-test) @ sample.lambda ---
[INFO] 
[INFO] --- maven-jar-plugin:2.4:jar (default-jar) @ sample.lambda ---
[INFO] Building jar: C:\work\sample.lambda\target\sample.lambda-0.0.1.jar
[INFO] 
[INFO] --- maven-install-plugin:2.4:install (default-install) @ sample.lambda ---
[INFO] Installing C:\work\sample.lambda\target\sample.lambda-0.0.1.jar to C:\Users\yukiv\.m2\repository\org\sample\sample.lambda\0.0.1\sample.lambda-0.0.1.jar
[INFO] Installing C:\work\sample.lambda\pom.xml to C:\Users\yukiv\.m2\repository\org\sample\sample.lambda\0.0.1\sample.lambda-0.0.1.pom
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.331 s
[INFO] Finished at: 2016-11-04T19:51:38+09:00
[INFO] Final Memory: 14M/114M
[INFO] ------------------------------------------------------------------------

出力内容に Bytecode version: 50 (Java 6) という部分があり、Java6用のモジュールを生成していることが分かります。


Retrolambdaを用いた開発における制約

Retrolambdaを利用することでラムダ式などを利用することがOKとなりますが、最終的に実行する環境がJava8ではなくJava6やJava7であるためいくつかの制約が存在しています。

  • 制約事項の前に、OKなこととしては以下が挙げられます

    • ラムダ式を用いたコードを書く
    • メソッド参照を使用する
  • 制約事項としては以下があります

    • Java標準のStream API(java.util.streamパッケージ)を使用できない
    • CollectionのstreamメソッドやforEachメソッドは使用できない
    • Java8から増えたクラスやメソッドは使用できない(Optional, LongAdder, LocalDateTime...etc)
    • interfaceのデフォルト実装(defaultメソッド)は使用できない
    • interfaceに定義したstaticメソッドは使用できない

制約事項に書かれている内容を守らなくても、開発はJava8の環境で実施するためコンパイル等はできてしまいます。が、実行環境でエラーとなります。
Stream APIに関する制約については、Lightweight-Stream-APIを使用することで解決(代替)します。

Stream API以外でJava8から増えたクラスを使用したい場合は、対応するBackportのライブラリで代替できる場合もあります。
(例: ThreeTen Backport


ラムダ式を用いたコードを書いてJava6環境で実行してみる

ラムダ式、メソッド参照、Lightweight-Stream-APIが提供するStream APIを利用したコードを記述して(ビルドをして)、Java6環境で実行してみます

ラムダ式+メソッド参照+Stream APIのコード

■サンプルコード

ラムダ式、メソッド参照、(Lightweight-Stream-APIで代替した)Stream APIを用いた各種メソッドを記述しています。
(制約事項に引っかからないようにjava.util.streamパッケージに依存していません。代わりに、com.annimon.streamパッケージに依存するようにしています)

import java.util.List;
import java.util.Comparator;
import com.annimon.stream.Collectors;
import com.annimon.stream.Stream;


public class SampleAPI1 {

    private static final Comparator<String> LENGTH_COMPARATOR = (x, y) -> x.length() - y.length();

    public static void filterLength3(List<String> srcList) {
        Stream.of(srcList)
            .filter(s -> s.length() > 3)
            .forEach(System.out::println);
    }

    public static List<String> filterLength2toList(List<String> srcList) {
        List<String> list =
                Stream.of(srcList)
                    .filter(s -> s.length() > 2)
                    .collect(Collectors.toList());

        return list;
    }

    public static List<String> filterArrayLength2toList(String[] srcArray) {
        List<String> list =
                Stream.of(srcArray)
                    .filter(s -> s.length() > 2)
                    .collect(Collectors.toList());

        return list;
    }

    public static List<Integer> toLengthList(List<String> srcList) {
        List<Integer> list =
                Stream.of(srcList)
                    .map(s -> s.length())
                    .sorted()
                    .collect(Collectors.toList());

        return list;
    }

    public static int sumWordLength(List<String> srcList) {
        int sumLength = Stream.of(srcList)
                            .map(s -> s.length())
                            .reduce(0, (sum, i) -> sum + i);

        return sumLength;
    }

    public static List<String> sort1(List<String> srcList) {
        List<String> tmpList = new ArrayList<>(srcList);

        Collections.sort(tmpList, LENGTH_COMPARATOR);

        return tmpList;
    }

    public static List<String> sort2(List<String> srcList) {
        List<String> tmpList = new ArrayList<>(srcList);

        // 比較関数をラムダ式で記述して直接sortに渡してソート実行
        Collections.sort(tmpList, (x, y) -> x.length() - y.length());

        return tmpList;
    }
}

■実行側のコード

Java6環境で実行するコードを書いています

import java.util.Arrays;
import java.util.List;

import org.sample.lambda.SampleAPI1;

public class ExampleLauncher {

    public static void main(String[] args) {

        System.out.println("################################");
        System.out.println("Runtime Java Version: " + System.getProperty("java.version"));
        System.out.println("################################");

        List<String> srcList = Arrays.asList("zzzzzzzz", "abc", "abcde", "defg", "x", "xyz", "opqrs");

        System.out.println("\n>> invoke SampleAPI1#filterLength3");

        SampleAPI1.filterLength3(srcList);


        System.out.println("\n>> invoke SampleAPI1#filterLength2toList");

        List<String> destList = SampleAPI1.filterLength2toList(srcList);

        System.out.println("length > 2  :" + destList);


        System.out.println("\n>> invoke SampleAPI1#toLengthList");
        List<Integer> lengthList = SampleAPI1.toLengthList(srcList);

        System.out.println("lengthList: " + lengthList);


        System.out.println("\n>> invoke SampleAPI1#sumWordLength");
        int sumWordLength = SampleAPI1.sumWordLength(srcList);
        System.out.println("sumWordLength: " + sumWordLength);


        String[] srcArray = new String[] {"zzzzzzzz", "abc", "abcde", "defg", "x", "xyz", "opqrs"};

        System.out.println("\n>> invoke SampleAPI1#filterArrayLength2toList");
        List<String> destList2 = SampleAPI1.filterArrayLength2toList(srcArray);

        System.out.println("destList2: " + destList2);


        System.out.println("\n>> invoke SampleAPI1#sort1");
        List<String> sortedList1 = SampleAPI1.sort1(srcList);

        System.out.println("src List: " + srcList);
        System.out.println("sroted List: " + sortedList1);


        System.out.println("\n>> invoke SampleAPI1#sort2");
        List<String> sortedList2 = SampleAPI1.sort2(srcList);

        System.out.println("src List: " + srcList);
        System.out.println("sroted List: " + sortedList2);
    }
}

■実行結果

Java6環境で実行した結果です。(実行環境のクラスパスにはLightweight-Stream-APIのjarを追加しています)

################################
Runtime Java Version: 1.6.0_45
################################

>> invoke SampleAPI1#filterLength3
zzzzzzzz
abcde
defg
opqrs

>> invoke SampleAPI1#filterLength2toList
length > 2  :[zzzzzzzz, abc, abcde, defg, xyz, opqrs]

>> invoke SampleAPI1#toLengthList
lengthList: [1, 3, 3, 4, 5, 5, 8]

>> invoke SampleAPI1#sumWordLength
sumWordLength: 29

>> invoke SampleAPI1#filterArrayLength2toList
destList2: [zzzzzzzz, abc, abcde, defg, xyz, opqrs]

>> invoke SampleAPI1#sort1
src List: [zzzzzzzz, abc, abcde, defg, x, xyz, opqrs]
sroted List: [x, abc, xyz, defg, abcde, opqrs, zzzzzzzz]

>> invoke SampleAPI1#sort2
src List: [zzzzzzzz, abc, abcde, defg, x, xyz, opqrs]
sroted List: [x, abc, xyz, defg, abcde, opqrs, zzzzzzzz]

もともとラムダ式やメソッド参照を用いたコードだったにも関わらず、Java6環境で正常に実行できていることが分かります。


制約事項にひっかかるコードは実行環境でエラーになる

Retrolambdaの開発における制約事項を無視したコードを記述した場合、実行環境ではエラーとなります。
以下は、それを実際に試したサンプルです

defaultメソッドの利用した場合

defaultメソッド(interfaceのデフォルト実装)が絡んだコードを書くと実行環境においてエラーとなります

■サンプルコード

public interface SampleInteface {

     void notDefaultmedhod();

     default void defaultmedhod() {
         System.out.println("execute SampleInteface#defaultmedhod");
     }
}


// デフォルトメソッドを実装クラスでオーバーライドしない
public class SampleImpl1 implements SampleInteface {

    @Override
    public void notDefaultmedhod() {
        System.out.println("[override] execute SampleImpl1#notDefaultmedhod");
    }
}


// デフォルトメソッドを実装クラスでオーバーライドする
public class SampleImpl2 implements SampleInteface {

    @Override
    public void notDefaultmedhod() {
        System.out.println("[override] execute SampleImpl2#notDefaultmedhod");
    }

    @Override
    public void defaultmedhod() {
        System.out.println("[override] execute SampleImpl2#defaultmedhod");
    }

}

■利用側のコード

(1) defaultメソッドを実装クラスでオーバーライドしていない場合

import org.sample.lambda.ng.SampleImpl1;
import org.sample.lambda.ng.SampleInteface;

public class NGInterfaceLauncher3 {

    public static void main(String[] args) {
        SampleInteface api = new SampleImpl1();

        System.out.println("\n>> invoke SampleInteface#notDefaultmedhod");
        api.notDefaultmedhod();

        System.out.println("\n>> invoke SampleInteface#defaultmedhod");
        api.defaultmedhod();
    }

}

(2) defaultメソッドを実装クラスでオーバーライドしている場合

import org.sample.lambda.ng.SampleImpl2;
import org.sample.lambda.ng.SampleInteface;

public class NGInterfaceLauncher4 {

    public static void main(String[] args) {
        SampleInteface api = new SampleImpl2();

        System.out.println("\n>> invoke SampleInteface#notDefaultmedhod");
        api.notDefaultmedhod();

        System.out.println("\n>> invoke SampleInteface#defaultmedhod");
        api.defaultmedhod();
    }

}

■実行結果

結果は以下の通りですが、クラスロードの時点でClassFormatErrorが発生してしまいます。

(1) の結果

java.lang.ClassFormatError: Method defaultmedhod in class org/sample/lambda/ng/SampleInteface has illegal modifiers: 0x1
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
Exception in thread "main" 

(2) の結果

java.lang.ClassFormatError: Method defaultmedhod in class org/sample/lambda/ng/SampleInteface has illegal modifiers: 0x1
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
Exception in thread "main" 

interfaceクラスのstaticメソッド

interfaceクラスにstaticメソッドを定義した場合、Java7やJava6の環境ではそのstaticメソッドを実行するコードを書くことができません(コンパイルが通りません)

■サンプルコード インタフェースクラスにstaticメソッドを定義しています。

public interface StaticInteface {

    public static void staticMedhod1() {
        System.out.println("SampleInteface#staticMedhod1");
    }

    void method1();
}

■利用側のコード

Java6環境で対象のstaticメソッドを使用するコードを書くことはできません(コンパイルが通りません)

import org.sample.lambda.ng.StaticInteface;

public class NGStaticInterfaceLauncher1 {

    public static void main(String[] args) {

        // StaticInteface.staticMedhod1(); <- コンパイルが通らない
    }

}

Stream APIを使用した場合

Java標準のStream API(java.util.streamパッケージ)を利用したコードを書くと実行環境においてエラーとなります

■サンプルコード

以下では、java.util.stream.Streamやjava.util.stream.Collectors, Iterable#forEach 等に依存したコードを記述しています

import java.util.List;

public class NGSampleAPI1 {

    public static void ngPattern1(List<String> srcList) {
        srcList.stream()
            .filter(s -> s.length() > 3)
            .forEach(System.out::println);
    }

    public static void ngPattern2(List<String> srcList) {
        srcList.forEach(s -> System.out.println(s + ": " + s.length()));
    }

    public static List<String> ngPattern3(String[] srcArray) {
        List<String> list =
                java.util.stream.Stream.of(srcArray)
                    .filter(s -> s.length() > 2)
                    .collect(java.util.stream.Collectors.toList());

        return list;
    }
}

■実行結果

上記の各メソッドをJava6環境で実行すると、メソッド実行時に以下のような例外がスローされてエラーとなります。

################################
Runtime Java Version: 1.6.0_45
################################

>> invoke NGSampleAPI1#ngPattern1

Exception in thread "main" java.lang.NoSuchMethodError: java.util.List.stream()Ljava/util/stream/Stream;
    at org.sample.lambda.ng.NGSampleAPI1.ngPattern1(NGSampleAPI1.java:9)
    at org.sample.NGExampleLauncher1.main(NGExampleLauncher1.java:20)


>> invoke NGSampleAPI1#ngPattern2

Exception in thread "main" java.lang.NoClassDefFoundError: java/util/function/Consumer
    at java.lang.ClassLoader.defineClass1(Native Method)
    at java.lang.ClassLoader.defineClassCond(ClassLoader.java:631)
    at java.lang.ClassLoader.defineClass(ClassLoader.java:615)
    at java.security.SecureClassLoader.defineClass(SecureClassLoader.java:141)
    at java.net.URLClassLoader.defineClass(URLClassLoader.java:283)
    at java.net.URLClassLoader.access$000(URLClassLoader.java:58)
    at java.net.URLClassLoader$1.run(URLClassLoader.java:197)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    at org.sample.lambda.ng.NGSampleAPI1.ngPattern2(NGSampleAPI1.java:15)
    at org.sample.NGExampleLauncher2.main(NGExampleLauncher2.java:20)
Caused by: java.lang.ClassNotFoundException: java.util.function.Consumer
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    ... 14 more


>> invoke NGSampleAPI1#ngPattern3

Exception in thread "main" java.lang.NoClassDefFoundError: java/util/stream/Stream
    at org.sample.lambda.ng.NGSampleAPI1.ngPattern3(NGSampleAPI1.java:20)
    at org.sample.NGExampleLauncher3.main(NGExampleLauncher3.java:17)
Caused by: java.lang.ClassNotFoundException: java.util.stream.Stream
    at java.net.URLClassLoader$1.run(URLClassLoader.java:202)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.net.URLClassLoader.findClass(URLClassLoader.java:190)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:306)
    at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:301)
    at java.lang.ClassLoader.loadClass(ClassLoader.java:247)
    ... 2 more


まとめ

Retrolambdaを利用することでラムダ式やメソッド参照を使用したコードであっても、Java6やJava7で動作するモジュールを作成することが可能です。
ただし、全てのJava8の機能を使えるわけではなく一部制約もあり、それを理解した上で利用する必要があります。

また、Retrolambda自体が提供しないStream APIも使用したい場合は、Lightweight-Stream-API等を組み合わせて使用する必要があります。



関連エントリ