Предизвикателства в Java 8

1. Общ преглед

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

И макар че това не е пълен списък, това е субективна колекция от най-често срещаните и популярни оплаквания относно нови функции в Java 8.

2. Java 8 Stream и Thread Pool

На първо място, паралелните потоци имат за цел да направят възможна лесна паралелна обработка на последователности и това работи съвсем добре за прости сценарии.

Потокът използва стандартния 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 код, който се обяснява сам по себе си.

Ако предадем празен String като вход - в резултат - кодът ще изведе изключение и в конзолата за отстраняване на грешки можем да видим:

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)

Това е цената, която плащаме за използване на множество слоеве за абстракция в нашия код. IDE обаче вече са разработили солидни инструменти за отстраняване на грешки в Java Streams.

4. Методи, връщащи нула или незадължителни

По избор е въведена в Java 8, за да осигури безопасен начин за изразяване на незадължителност.

По избор , посочва изрично, че връщаната стойност може да не присъства. Следователно, извикването на метод може да върне стойност и по избор се използва за увиване на тази стойност вътре - което се оказа удобно.

За съжаление, поради обратната съвместимост на Java, понякога стигахме до Java API, смесващи две различни конвенции. В същия клас можем да намерим методи, връщащи нули, както и методи, връщащи Optionals.

5. Твърде много функционални интерфейси

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

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

Освен това имаме допълнителни типове за работа с примитиви:

  • IntConsumer
  • IntFunction
  • IntPredicate
  • IntSupplier
  • IntToDoubleFunction
  • IntToLongFunction
  • ... и същите алтернативи за Longs и Doubles

Освен това, специални типове за функции с артерия 2:

  • BiConsumer
  • BiPredicate
  • BinaryOperator
  • BiFunction

В резултат на това целият пакет съдържа 44 функционални типа, които със сигурност могат да започнат да объркват.

6. Проверени изключения и ламбда изрази

Проверените изключения вече бяха проблемен и спорен въпрос преди Java 8. След пристигането на Java 8 възникна новият брой.

Checked exceptions must be either caught immediately or declared. Since java.util.function functional interfaces do not declare throwing exceptions, code that throws checked exception will fail during compilation:

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

One way to overcome this problem is to wrap checked exception in a try-catch block and rethrow RuntimeException:

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

This will work. However, throwing RuntimeException contradicts the purpose of checked exception and makes the whole code wrapped with boilerplate code, which we're trying to reduce by leveraging lambda expressions. One of the hacky solutions is to rely on the sneaky-throws hack.

Another solution is to write a Consumer Functional Interface – that can throw an exception:

@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 Lambda Expressions.

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

В това кратко описание ние обсъдихме някои недостатъци на Java 8.

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