Рубрики
Без рубрики

Проблемы в Java 8

Другая сторона медали Java 8.

Автор оригинала: baeldung.

1. Обзор

Java 8 представила некоторые новые функции, которые в основном вращались вокруг использования лямбда-выражений. В этой краткой статье мы рассмотрим недостатки некоторых из них.

И, хотя это не полный список, это субъективная коллекция наиболее распространенных и популярных жалоб на новые функции в Java 8.

2. Пул потоков и потоков Java 8

Прежде всего, параллельные потоки предназначены для облегчения параллельной обработки последовательностей, и это вполне подходит для простых сценариев.

Поток использует стандартный, общий ForkJoinPool – разбивает последовательности на более мелкие фрагменты и выполняет операции с использованием нескольких потоков.

Однако есть одна загвоздка. Нет хорошего способа указать, какой ForkJoinPool использовать , и поэтому, если один из потоков застрянет, всем остальным, использующим общий пул, придется ждать завершения длительных задач.

К счастью, для этого есть обходной путь:

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

Это создаст новый отдельный ForkJoinPool , и все задачи, созданные параллельным потоком, будут использовать указанный пул, а не общий пул по умолчанию.

Стоит отметить, что есть еще один потенциальный подвох: “этот метод отправки задачи в пул с вилочным соединением для запуска параллельного потока в этом пуле является” трюком “реализации и не гарантирует , что он будет работать” , согласно Стюарту Марксу- разработчику Java и OpenJDK из Oracle. Важный нюанс, который следует иметь в виду при использовании этой техники.

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-код, который не требует пояснений.

Если мы передадим пустую строку в качестве входных данных – в результате код выдаст исключение, и в консоли отладки мы увидим:

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

Теперь давайте перепишем тот же код с помощью Stream API и посмотрим, что произойдет, когда будет передана пустая Строка :

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)

Это цена, которую мы платим за использование нескольких уровней абстракции в нашем коде. Однако Ideas уже разработали надежные инструменты для отладки потоков Java.

4. Методы, возвращающие значение Null или Необязательные

Необязательный был введен в Java 8, чтобы обеспечить типобезопасный способ выражения необязательности.

Необязательно , явно указывает, что возвращаемое значение может отсутствовать. Следовательно, вызов метода может возвращать значение, и Необязательно используется для переноса этого значения внутрь, что оказалось удобным.

К сожалению, из-за обратной совместимости Java мы иногда сталкивались с тем, что API Java смешивали два разных соглашения. В том же классе мы можем найти методы, возвращающие значения null, а также методы, возвращающие Необязательно.

5. Слишком Много Функциональных Интерфейсов

В пакете java.util.function у нас есть коллекция целевых типов для лямбда-выражений. Мы можем различать и группировать их как:

  • Потребитель – представляет операцию, которая принимает некоторые аргументы и не возвращает результата
  • Функция – представляет функцию, которая принимает некоторые аргументы и выдает результат
  • Оператор – представляет операцию над некоторыми аргументами типа и возвращает результат того же типа, что и операнды
  • Предикат – представляет предикат ( логическую -значную функцию) некоторых аргументов
  • Поставщик – представляет поставщика, который не принимает аргументы и возвращает результаты

Кроме того, у нас есть дополнительные типы для работы с примитивами:

  • IntConsumer
  • Функция Int
  • Интпредикат
  • IntSupplier
  • IntToDoubleFunction
  • Функция IntToLongFunction
  • … и те же альтернативы для Длинных и Двойных

Кроме того, специальные типы для функций с арностью 2:

  • БиКонсумер
  • Двупредикатный
  • Двоичный оператор
  • бифункциональный

В результате весь пакет содержит 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 integers = Arrays.asList(3, 9, 7, 0, 10, 20);
integers.forEach(i -> writeToFile(i));

Один из способов преодолеть эту проблему-обернуть проверенное исключение в try-catch блок и перестроить Исключение RuntimeException :

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

Это сработает. Однако выбрасывание RuntimeException противоречит цели проверяемого исключения и делает весь код завернутым в стандартный код, который мы пытаемся уменьшить, используя лямбда-выражения. Одним из хакерских решений является полагаться на взлом sneakythrows.

Другим решением является написание пользовательского функционального интерфейса, который может выдавать исключение:

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

К сожалению, мы все еще заключаем проверенное исключение в исключение времени выполнения.

Наконец, для более глубокого решения и объяснения проблемы мы можем изучить следующее глубокое погружение: Исключения в лямбда-выражениях Java 8 .

8. Заключение

В этом кратком обзоре мы обсудили некоторые недостатки Java 8.

Хотя некоторые из них были преднамеренным выбором дизайна, сделанным архитекторами языка Java, и во многих случаях существует обходной путь или альтернативное решение; нам действительно нужно знать об их возможных проблемах и ограничениях.