Java 8での課題

1概要

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

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

2 Java 8ストリームとスレッドプール

まず第一に、Parallel Streamsはシーケンスの簡単な並列処理を可能にすることを意図しています、そしてそれは単純なシナリオのためにかなりうまく働きます。

Streamはデフォルトの共通の ForkJoinPool - シーケンスを小さいチャンクに分割し、複数のスレッドを使用して操作を実行します。 。

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

幸い、これに対する回避策があります。

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

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

別の潜在的なキャッチがあることは注目に値する:__「そのプール内で並列ストリームを実行するためにタスクをfork-joinプールに投入するこの手法は実装上の「トリック」であり、動作することは保証されない」 Stuart Marks - OracleのJavaおよび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 Streamsをデバッグするための強力なツールを開発しています。

4. Null または Optional を返すメソッド

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

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

残念ながら、Javaの下位互換性のために、2つの異なる規則を混在させたJava APIが使用されることがありました。同じクラス内で、nullを返すメソッドと、 Optionals. を返すメソッドを見つけることができます。

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

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

  • Consumer - いくつかの引数を取る操作を表します。

結果を返しません ** Function - いくつかの引数を取る関数を表します

結果を出す ** 演算子 - いくつかの型引数に対する操作を表します。

オペランドと同じ型の結果を返す ** Predicate - の述部(__ブール値関数)を表します。

いくつかの引数 ** Supplier - 引数を取らずに返品するサプライヤを表します

結果

さらに、プリミティブを扱うための追加の型があります。

  • IntConsumer

  • IntFunction

  • IntPredicate

  • IntSupplier

  • IntToDoubleFunction

  • IntToLongFunction

  • …​および Longs Doubles の同じ選択肢

さらに、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つはhttps://4comprehension.com/sneakily-throwing-exceptions-in-lambda-expressions-in-java/[卑劣な投げハックに頼ることです]。

別の解決策は、消費者機能インターフェースを書くことです - それは例外を投げることができます:

@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);
        }
    };
}

残念ながら、チェック例外を実行時例外でラップしています。

最後に、問題の詳細な解決策と説明のために、次の詳細な説明を調べることができます。

8結論

この記事では、Java 8のいくつかの欠点について説明しました。

そのうちのいくつかはJava言語アーキテクトによってなされた意図的な設計選択であり、そして多くの場合回避策あるいは代替解決策があります。我々は彼らの考えられる問題と制限を知っている必要があります。