Любовь и ненависть к Java 8

в 11:13, , рубрики: java, java 8, переводы, метки:

Похоже Java 8 самый ожидаемый релиз всех времен. Изначально планирующий релиз на сентябрь прошлого года, перенесли на март следующего года, предположительно для того, что бы потратить больше времени на доработки безопасности, в основном направленные на клиентскую часть Java (JavaFX/Swing).

Новая версия Java пытается “совершенствоваться” так, как понимает это слово Microsoft. Это означает кражу большой части вещей, о которых заботились другие фреймворки и языки, затем включение их в язык или runtime. В преддверии нового релиза, сообщество Java обсуждает Project Lambda, stream, functional interfaces и другие плюшки. Так давайте рассмотрим что хорошо, а что мы можем возненавидеть.

Stream


Основное нововведение это коллекция, называемая Stream, не путайте с InputStream и OutputStream. Stream не замещает ArrayLists или другие коллекции. Это нововведение позволяет управлять данными быстрее и легче. Stream — это одноразовый объект, т.е. обработать данные в нем можно один раз.

Stream обладает возможностью применить функции filter, map, reduce для его обработки. Для Stream есть два режима: последовательный и параллельный. Это позволяет задействовать возможности многоядерных процессоров. Коллекции используют fork/join параллелизм для разбиения работы на части.

Для последовательного режима:

List <Person> people = list.getStream.collect(Collectors.toList());

Для параллельного режима:

List <Person> people = list.getStream.parallel().collect(Collectors.toList());

Во время последовательной обработки Stream, каждый элемент обрабатывается по очереди. А в параллельном режиме массив разбивается на части, каждая из которых обрабатывается в отдельном потоке. Затем результаты обработки собираются в общий результат.

Обработка в параллельном режиме выглядит так:

List originalList = someData;
split1 = originalList(0, mid);
split2 = originalList(mid,end);
new Runnable(split1.process());
new Runnable(split2.process());
List revisedList = split1 + split2;

Stream может быть обработан только раз, и он возвращает другой Stream, поэтому для получения полезного результат можно применить окончательную (terminal) функцию. Например, функции sum(), collect(), toArray(). Пока к Stream не применена окончательная функция, результат обработки не вычисляется. Например:

Double result = list.getStream().mapToDouble(f -> f.getAmount()).sum();
List<Person> people = list.getStream().filter(f -> f.getAge() > 21).collect(Collectors.toList());

Большая польза от этого – возможность использовать несколько процессорных ядер для обработки коллекции. Вместо использования традиционного for-цикла, использование Stream в параллельном режиме теоретически ускоряется, с увеличением числа ядер, задействованных в обработке. Основная возможная проблема это потеря читаемости кода при большом числе операций производимых над коллекцией. Другие проблемы возникают из-за добавления поддержки новых возможностей – функциональные интерфейсы и лямбды.

Functional Interfaces

В целом это просто добавление default-методов в интерфейс с возможностью их прямого вызова из интерфейса. Так же их не обязательно переопределять в реализации интерфейса.

Это было сделано для обратной совместимости с вашими коллекциями в ваших интерфейсах. Т.е. решение проблемы помещения Stream в интерфейс без необходимости изменять все реализующие этот интерфейс классы. Поэтому, создание default-метода в интерфейсе позволяет всем реализующим интерфейс классам использовать потоки. Если default-метод не корректен, то он может быть перегружен.

По существу default-методы это форма множественного наследования. И это становится проблемой того, кто реализует интерфейс, т.к. ему всё равно потребуется переопределить метод. Так же реализующий интерфейс может выбрать, какой базовый метод (supermethod) использовать, это означает что большинство классов реализующий интерфейс могут измениться.

Об этой детали в Java 8 беспокоится много людей. Возможно, это не побеспокоит тех, кто знаком с языком Scala. Функциональные интерфейсы можно напрямую сравнить с концепцией trait-ов в Scala. Однако есть несколько различий: функциональные интерфейсы в Java 8 не могут получить ссылку на реализующий класс, однако Scala позволяет это с помощью ключевого слова self. Зануды могут возразить, что в Java 8 функциональные интерфейсы разрешают множественное наследование поведения, но запрещают наследование состояния, в то время как, в Scala разрешается и то, и то.

Lambda

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

Старый стиль:

button.addActionListener(new ActionListener() {
    public void actionPerformed(ActionEvent ae) {
      System.out.println(“Action Detected”);
    }
  }
);

Новый стиль:

button.addActionListener(e -> {
        System.out.println(“Action Detected”);
    }
);

И еще один пример.
Старый стиль:

Runnable runnable1 = new Runnable() {
@Override
public void run() {
        System.out.println("Running without Lambda");
    }
};

Новый стиль:

Runnable runnable2 = () -> { System.out.println("Running from Lambda"); };

Как вы можете видеть, использование лямбда-выражений делает код более читабельным, и он короче. Это взволновало много людей в около Java сообществе. В Scala уже есть все эти возможности. И не сюрприз, что Scala сообщество настроено скептически, потому что много нововведений в Java 8 выглядят как замена оператор => на -> в Scala. В некоторых случаях синтаксис Java 8 выглядит более многословным или менее чистым, чем в Scala. Пока не ясно, будет ли все так, как в языках на подобии Scala, построенных на лябда-выражених.

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

Java time

У Java долгая история, связанная со временем. Сначала был класс java.util.Date, который быстро показал, что Sun может объявлять методы устаревшими в рекордное время, но эти методы могут остаться навечно. И не забудьте java.sql.Date, который помогал узнавать время и место для использования fully qualified names (FQNs) в коде.

Потом это был Calendar, который осознал, что код может работать, более чем с одной частью света в одной JVM. Но работа с временем и датами требовала большого числа monkey-кода и возни с разрозненным API. Поэтому рождались сторонние библиотеки, такие как JodaTime. Теперь Java, с опозданием, решила навести порядок в пакете java.time. Для меня это выглядит как API для работы со временем, который мы всегда хотели.

Nashorn

Netscape создали технологию, называемую LiveScript, позволяющую работать со скриптами на их веб сервере. Было решено портировать её в их браузер, и потребовалось более красивое имя, так что LiveScript был лицензирован под торговой маркой Java от Sun и назван JavaScript – что посодействовало путанице относительно связи между Java и JavaScript. Однако, после распада компании AOL, некоторые члены команды Netscape продолжили реализовывать план Netscape по переписыванию браузера на Java. Для того что бы это сделать, было необходимо реализацию JavaScript в Java, Netscape назвал этот проект Rhino.

Современный JavaScript, этот не тот JavaScript, который знал твой отец. Он может быть полезен как на клиентской стороне, так и на серверной, и вы можете разрабатывать приложения, которые читабельные и быстрые. В JDK 7 добавили invokeDynamic — поддержку динамических языков. А В JDK 8 предоставят более полезную реализацию JavaScript и что, возможно, сделает Nodyn (Red Hat's port of Node.js to the JVM) не очередной жуткой поделкой. Вообще у Oracle есть своя реализация Node.js, креативно названная Node.jar. В чем уверено большинство людей, так это в том, что они хотят запускать всякие штуки на JVM, но не хотят использовать для этого синтаксис Java.

Есть места, где полезен запуск JavaScript из Java. Например, можно использовать client-side validator, как server-side validator, т.е. иметь один и тот же код, работающий в двух местах. Иметь свой собственный Node.js вместе с Java — это как обзавестись милым монстриком, кто не хочет такого? Если читая этот текст, вы не уверены, серьезен я или нет, то это делает нас похожими.

Accumulators

Сначала был synchronized. Однако, если все что вам нужно делать это увеличивать счетчик из многих потоков, то synchronized тяжеловат для этой задачи. Он стал не такой тяжелый в Java 6, сделав неисчислимые блокировки дешевле. В основном это помогло старым приложениям, все ещё использующим Vector, это однопоточный хлам, который поразил каждую библиотеку в Java Activation Framework.

С появлением пакета java.util.concurrent стало лучше — пул потоков и другие сложные многопоточные конструкции, но если все, что вы хотите это просто увеличение счетчика потоками, это все было излишне. Для этого нам были даны atomic-и — быстрые и легче, чем настоящие блокировки. Однако Doug Lea и его любимая армия студентов выпускников еще не закончила. В JDK 8 нам дадут accumulators и adders. Они более легкие, чем atomic-и, и с ослабленными гарантиями, это то, что больше всего нужно параллельному коду, увеличивающему общий счетчик. Ожидаю увидеть это нововведение в реализациях map/reduce. Однако вам все еще нужны atomic-и, если вы хотите читать значение счетчика в потоках, так как порядок аккумулирования счетчика не гарантирован.

Исправления HashMap

Существует известный баг, связанный с тем, как String.hashCode() реализован в Java. Если большое число параметров имеют одинаковый хеш, это вызовет чрезмерную нагрузку на CPU при работе с HashMap. Такая ситуация может возникнуть, если приложение подвергнется denial-of-service атаке, как в этом случае.

Сейчас, корзины в HashMap используют связанный список для хранения значений. Если есть большое число коллизий, тогда сложность работы со структурой изменяется от O(1) до O(N). Теперь при достижении определенного числа элементов в корзине, корзина переключится на использование сбалансированного дерева, что снижает сложность до O(log n).

TLS SNI

SNI — это не имя персонажа Dr. Seuss, а Server Name Identification. Все любят SSL или TLS, или как это теперь называется. Много сайтов используют один и тот же IP и name-based virtual host. Что означает, что вторая строка HTTP запроса это имя хоста. Я могу сделать запрос на podcastd.infoworld.com и www.infoworld.com, находящиеся но одном и том же IP, но получить разные страницы, из-за разного имени хоста. Однако я не могу держать много сайтов на одном IP из-за SSL. Для каждого SSL сертификата я должен иметь отдельный IP адрес. А если вспомнить печальную ситуацию с нынешним числом IP адресов в IPv4, то все становится еще печальнее.

Но теперь Java поддерживает SNI. Большинство современных браузеров поддерживает SNI, Apache поддерживает и Java теперь тоже поддерживает. Это означает, то чего мы так долго ожидали — Tomcat и другие основанные на Java серверы, использующие медленную реализацию SSL от Oracle (JSSE), теперь поддерживают SNI.

Заключение

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

От переводчика

Это перевод вот этой статьи
Увидел в комментариях желание узнать про плюшки Java 8, да и сам хотел, и тут попалась эта статья, так что вот сел и перевел. Перевод не дословный, но я постарался сделать как можно ближе к тексту, нежножко пришлось выкинуть, т.к. автор статьи шутит, а его шутки переводить сложно. Поэтому знатокам английского строго рекомендую читать оригинал.

Автор: travko

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js