1. 概要

Java 8では、ラムダ式の使用を中心に展開するいくつかの新機能が導入されました。 この簡単な記事では、それらのいくつかの欠点を見ていきます。

また、これは完全なリストではありませんが、Java8の新機能に関する最も一般的で人気のある苦情の主観的なコレクションです。

2. Java8ストリームとスレッドプール

まず第一に、Parallel Streamsは、シーケンスの簡単な並列処理を可能にすることを目的としており、単純なシナリオでは非常にうまく機能します。

ストリームは、デフォルトの一般的な ForkJoinPool を使用します–シーケンスを小さなチャンクに分割し、複数のスレッドを使用して操作を実行します。

ただし、落とし穴があります。 どのForkJoinPoolを使用するかを指定する良い方法はありません。したがって、スレッドの1つがスタックした場合、共有プールを使用して、他のすべてのスレッドがスタックし、長時間実行されるタスクが完了するのを待つ必要があります。

幸い、その回避策があります。

ForkJoinPool forkJoinPool = new ForkJoinPool(2);
forkJoinPool.submit(() -> /*some parallel stream pipeline */)
  .get();

これにより、新しい個別の ForkJoinPool が作成され、並列ストリームによって生成されるすべてのタスクは、共有のデフォルトプールではなく、指定されたプールを使用します。

別の潜在的な問題があることに注意してください。「タスクをフォーク結合プールに送信し、そのプールで並列ストリームを実行するこの手法は、実装の「トリック」であり、動作が保証されていません」、Stuart Marks –JavaおよびOracleのOpenJDK開発者によると。 このテクニックを使用するときに覚えておくべき重要なニュアンス。

3. デバッグ可能性の低下

新しいコーディングスタイルはソースコードを簡素化しますが、はデバッグ中に頭痛の種になる可能性があります

まず、この簡単な例を見てみましょう。

public static int getLength(String input) {
    if (StringUtils.isEmpty(input) {
        throw new IllegalArgumentException();
    }
    return input.length();
}

List lengths = new ArrayList();

for (String name : Arrays.asList(args)) {
    lengths.add(getLength(name));
}

これは、自明の標準的な命令型Javaコードです。

空のStringを入力として渡すと(結果として)、コードは例外をスローし、デバッグコンソールで次のように表示されます。

at LmbdaMain.getLength(LmbdaMain.java:19)
at LmbdaMain.main(LmbdaMain.java:34)

次に、Stream APIを使用して同じコードを書き直し、空のStringが渡されたときに何が起こるかを見てみましょう。

Stream lengths = names.stream()
  .map(name -> getLength(name));

呼び出しスタックは次のようになります。

at LmbdaMain.getLength(LmbdaMain.java:19)
at LmbdaMain.lambda$0(LmbdaMain.java:37)
at LmbdaMain$$Lambda$1/821270929.apply(Unknown Source)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:512)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:502)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.LongPipeline.reduce(LongPipeline.java:438)
at java.util.stream.LongPipeline.sum(LongPipeline.java:396)
at java.util.stream.ReferencePipeline.count(ReferencePipeline.java:526)
at LmbdaMain.main(LmbdaMain.java:39)

これは、コード内の複数の抽象化レイヤーを活用するために支払う代償です。 ただし、IDEは、Javaストリームをデバッグするための堅固なツールをすでに開発しています。

4. Nullまたはオプションを返すメソッド

オプションは、オプションを表現するためのタイプセーフな方法を提供するためにJava8で導入されました。

オプションは、戻り値が存在しない可能性があることを明示的に示します。 したがって、メソッドを呼び出すと値が返される場合があり、オプションを使用してその値を内部にラップします。これは便利であることがわかりました。

残念ながら、Javaの下位互換性のために、JavaAPIが2つの異なる規則を混合してしまうことがありました。 同じクラスで、nullを返すメソッドとOptionals。を返すメソッドを見つけることができます。

5. 機能インターフェイスが多すぎます

java.util.function パッケージには、ラムダ式のターゲットタイプのコレクションがあります。 それらを次のように区別してグループ化できます。

  • Consumer –いくつかの引数を取り、結果を返さない操作を表します
  • Function –いくつかの引数を取り、結果を生成する関数を表します
  • Operator –いくつかの型引数に対する演算を表し、オペランドと同じ型の結果を返します
  • Predicate –いくつかの引数の述語( boolean 値の関数)を表します
  • Supplier –引数を取らず、結果を返すサプライヤーを表します

さらに、プリミティブを操作するための追加のタイプがあります。

  • IntConsumer
  • IntFunction
  • IntPredicate
  • IntSupplier
  • IntToDoubleFunction
  • IntToLongFunction
  • …そしてLongsDoublesの同じ選択肢

さらに、2のアリティを持つ関数の特別なタイプ。

  • BiConsumer
  • BiPredicate
  • BinaryOperator
  • BiFunction

その結果、パッケージ全体に44の機能タイプが含まれているため、混乱を招く可能性があります。

6. チェックされた例外とラムダ式

チェックされた例外は、Java 8より前では、問題があり、物議を醸す問題でした。 Java 8の登場以来、新しい問題が発生しました。

チェックされた例外は、すぐにキャッチするか、宣言する必要があります。 java.util.function 機能インターフェースはスロー例外を宣言しないため、チェックされた例外をスローするコードはコンパイル中に失敗します。

static void writeToFile(Integer integer) throws IOException {
    // logic to write to file which throws IOException
}
List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> writeToFile(i));

この問題を解決する1つの方法は、チェックされた例外を try-catch ブロックでラップし、RuntimeExceptionを再スローすることです。

List<Integer> integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> {
    try {
        writeToFile(i);
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
});

これは機能します。 ただし、 RuntimeException をスローすると、チェックされた例外の目的と矛盾し、コード全体が定型コードでラップされます。これは、ラムダ式を利用して削減しようとしています。 ハッキーな解決策の1つは、卑劣なスローハックに依存することです。

別の解決策は、例外をスローする可能性のあるコンシューマー機能インターフェイスを作成することです。

@FunctionalInterface
public interface ThrowingConsumer<T, E extends Exception> {
    void accept(T t) throws E;
}
static <T> Consumer<T> throwingConsumerWrapper(
  ThrowingConsumer<T, Exception> throwingConsumer) {
  
    return i -> {
        try {
            throwingConsumer.accept(i);
        } catch (Exception ex) {
            throw new RuntimeException(ex);
        }
    };
}

残念ながら、チェックされた例外をランタイム例外でラップしています。

最後に、問題の詳細な解決策と説明について、次の詳細を調べることができます。Java8ラムダ式の例外

8。 結論

この簡単な記事では、Java8のいくつかの欠点について説明しました。

それらのいくつかはJava言語アーキテクトによって意図的に設計された選択であり、多くの場合、回避策または代替ソリューションがあります。 考えられる問題と制限を認識する必要があります。