Java 8 потрясающая. Период. Но … после того, как у нас была возможность повеселиться и поиграть с этим, пришло время бросить курить, избегая зерна соли. Все хорошие вещи имеют свою цену, и в этом посте я поделюсь основными болевыми точками Java 8. Убедитесь, что вы знаете об этом, прежде чем обновлять и выпускать 7.
1. Параллельные потоки могут на самом деле замедлить вас
Java 8 приносит обещание параллелизма как одну из самых ожидаемых новых функций. Метод .parallelStream () реализует это в коллекциях и потоках. Он разбивает их на подзадачи, которые затем выполняются в отдельных потоках для обработки, они могут переходить к разным ядрам, а затем объединяться, когда они завершены. Все это происходит под капотом с использованием структуры fork / join . Хорошо, звучит круто, это должно ускорить операции с большими наборами данных в многоядерных средах, верно?
Нет, это может на самом деле сделать ваш код медленным, если не используется правильно. Примерно на 15% медленнее в этом тесте мы пробежали, но это могло быть еще хуже. Допустим, у нас уже запущено несколько потоков, и мы используем .parallelStream () в некоторых из них, добавляя все больше и больше потоков в пул. Это может легко превратиться в нечто большее, чем наши ядра, и замедлить все из-за увеличения переключения контекста
Более медленный тест, объединяющий коллекцию в разные группы (простые / не простые):
Map<Boolean, List<Integer>> groupByPrimary = numbers
.parallelStream().collect(Collectors.groupingBy(s -> Utility.isPrime(s)));
Больше замедлений может произойти и по другим причинам. Подумайте об этом, скажем, у нас есть несколько задач для выполнения, и одна из них занимает гораздо больше времени, чем другие по какой-то причине. Разбив его с помощью .parallelStream (), можно фактически задержать завершение более быстрых задач и всего процесса в целом. Проверьте это сообщение Лукасом Креканом для большего количества примеров и примеров кода.
Диагноз: Параллелизм со всеми его преимуществами также привносит дополнительные типы проблем для рассмотрения. Когда вы уже действуете в многопоточной среде, имейте это в виду и узнайте, что происходит за кулисами.
2. Обратная сторона лямбда-выражений
Лямбда. О, лямбды. Мы можем сделать почти все, что мы уже могли, без вас, но вы добавляете столько грации и избавляетесь от стандартного кода, чтобы легко влюбиться. Допустим, я встаю утром и хочу перебрать список команд чемпионата мира и отобразить их длину (забавный факт: это сумма до 254):
List lengths = new ArrayList(); for (String countries : Arrays.asList(args)) { lengths.add(check(country)); }
Теперь давайте поработаем с хорошей лямбдой:
Stream lengths = countries.stream().map(countries -> check(country));
BAAM! Это супер. Хотя… хотя в основном это воспринимается как положительный момент, добавление новых элементов, таких как лямбды, в Java отталкивает их от первоначальной спецификации. Байт-код полностью OO и с лямбдами в игре расстояние между реальным кодом и временем выполнения увеличивается. Узнайте больше о темной стороне лямбда-выражения в этом посте Тала Вейса.
В итоге все это означает, что то, что вы пишете, и то, что вы отлаживаете, — это две разные вещи. Трассировки стека становятся все больше и больше, что усложняет отладку вашего кода.
Что-то простое, например, добавление пустой строки в список, превращает эту короткую трассировку стека:
at LmbdaMain.check(LmbdaMain.java:19) at LmbdaMain.main(LmbdaMain.java:34)
В это:
at LmbdaMain.check(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)
Другая проблема, которую поднимают лямбда-выражения, связана с перегрузкой: поскольку лямбда-аргументы должны быть преобразованы во что-то при их использовании для вызова метода, и они могут быть приведены к нескольким типам, в некоторых случаях это может вызвать неоднозначные вызовы. Лукас Эдер объясняет это примерами кода прямо здесь .
Диагноз: Просто знайте об этом, следы могут время от времени причинять боль, но это не удержит нас от этих драгоценных лямбд.
3. Методы по умолчанию отвлекают
Методы по умолчанию обеспечивают реализацию функции по умолчанию в самом интерфейсе. Это, безусловно, одна из самых крутых новых возможностей, которую предлагает Java 8, но она несколько мешает тому, как мы привыкли делать вещи. Так почему же это было введено? И что с этим не делать?
Основной мотивацией методов по умолчанию было то, что если в какой-то момент нам нужно добавить метод в существующий интерфейс, мы могли бы сделать это без переписывания реализации. Сделать его совместимым со старыми версиями. Например, возьмите этот фрагмент кода из Oracle Tutorials, где они добавляют возможность указывать часовой пояс:
public interface TimeClient { // ... static public ZoneId getZoneId (String zoneString) { try { return ZoneId.of(zoneString); } catch (DateTimeException e) { System.err.println("Invalid time zone: " + zoneString + "; using default time zone instead."); return ZoneId.systemDefault(); } } default public ZonedDateTime getZonedDateTime(String zoneString) { return ZonedDateTime.of(getLocalDateTime(), getZoneId(zoneString)); } }
И это все, проблема решена. Или это? Методы по умолчанию немного смешивают разделение интерфейса и реализацию. Как будто иерархии типов не склонны запутываться сами по себе, теперь есть новое существо, которое нам нужно приручить. Подробнее об этом читайте в публикации Олега Шелаева на RebelLabs.
Диагностика: Когда вы держите молоток, все выглядит как гвоздь, имейте в виду, чтобы придерживаться своего первоначального варианта использования, эволюция существующего интерфейса, когда рефакторинг для введения нового абстрактного класса не имеет смысла.
Перейдем к некоторым вещам, которые либо отсутствуют, либо еще с нами, либо еще не совсем:
4. Зачем ты Головоломка ?
Цель проекта Jigsaw — сделать Java модульным и разбить JRE на совместимые компоненты. Первым мотивом этого является стремление к лучшей, быстрой и мощной встроенной Java. Я стараюсь не упоминать «Интернет вещей», но там я это сказал. Уменьшение размеров JAR, повышение производительности и повышение безопасности — вот еще некоторые обещания, которые выполняет этот амбициозный проект.
Так где это? Jigsaw вступил в фазу 2 совсем недавно, прошел ознакомительную фазу и теперь переключается на проектирование и реализацию качественного производства, говорит Марк Рейнхольд, главный Java-архитектор Oracle. Проект был первым планируется завершить в Java 8 и отложен до Java 9, как ожидается, будет один из своих главных новых функций.
Диагностика: если это главное, чего вы ждете , Java 9 выйдет в 2016 году. А пока поближе познакомимся и, возможно, даже включимся в список рассылки Jigsaw-dev .
5. Проблемы, которые все еще существуют
Проверенные исключения
Никто не любит шаблонный код, это одна из причин, почему лямбды стали такими популярными. Думая о стандартных исключениях, независимо от того, нужно ли вам логически перехватывать или иметь какое-то отношение к проверенному исключению , вам все равно нужно его перехватить. Даже если это никогда не случится, как это исключение, которое никогда не сработает:
try { httpConn.setRequestMethod("GET"); } catch (ProtocolException pe) { /* Why don’t you call me anymore? */ }
Примитивы
Они все еще здесь, и это боль , чтобы использовать их правильно. Единственное, что отделяет Java от чисто объектно-ориентированного языка, подвергается критике за то, что не имеет существенного снижения производительности для их удаления. Ни один из новых языков JVM не имеет их, просто говорю.
Перегрузка операторов
Джеймс Гослинг, отец Java, однажды сказал в интервью: «Я исключил перегрузку операторов как довольно личный выбор, потому что видел, как слишком много людей злоупотребляет этим в C ++». В некотором смысле имеет смысл, но есть много разногласий по этому поводу. Другие языки JVM предлагают эту функцию, но, с другой стороны, это может привести к коду, который выглядит следующим образом:
javascriptEntryPoints <<= (sourceDirectory in Compile)(base => ((base / "assets" ** "*.js") --- (base / "assets" ** "_*")).get )
Реальная строка кода из Scala Play Framework , ах, у меня сейчас кружится голова.
Диагноз: действительно ли это настоящие проблемы? У всех нас есть свои причуды, и это некоторые из Java. В будущих версиях может случиться сюрприз, и он изменится, но обратная совместимость, среди прочего, сохраняет их здесь с нами
6. Функциональное программирование — пока не совсем
Функциональное программирование было возможно с Java раньше, хотя это довольно неловко. Java 8 улучшает это с лямбдами среди прочего. Это очень приветствуется, но не такой большой сдвиг, который был изображен ранее. Определенно более элегантный, чем в Java 7, но для того, чтобы быть по-настоящему функциональным, все же необходимо немного согнуться назад.
Один из самых ярких обзоров по этому вопросу пришел от Пьера-Ива Сомона, где в серии публикаций он подробно рассматривает различия между парадигмами функционального программирования и способом их реализации в Java.
Так Java или Скала? Принятие более функциональных современных парадигм в Java — знак одобрения для Scala, которая уже некоторое время играет с лямбдами. Лямбды наделают много шума, но есть много других функций, таких как черты характера, ленивая оценка и неизменность, и это немало.
Диагностика: не отвлекайтесь на лямбды, функциональное программирование все еще остается проблемой в Java 8.