обзор
12-16 ноября в Бельгии , Атверп , состоялась крупнейшая в мире конференция Java, посвященная независимым производителям. В этом году это было еще больше, достигнув 3400 посетителей из 40 разных стран. Как и в прошлом году , я и небольшая группа коллег из SAP были там и мне это очень понравилось.
После впечатляющего танца роботов Nao и вступительных лекций более 200 сессий конференции были посвящены различным технологиям, от Java SE до методологии и робототехники. Одной из самых интересных тем для меня была эволюция языка Java и платформы в JDK 8.
Мой интерес был частично обусловлен тем фактом, что я уже начинал работу над Wordcounter и заканчивал работу над другой параллельной библиотекой Java под названием Evictor, о которой я буду вести блог в будущем посте.
В этой серии блогов я хотел бы поделиться несколько более подробными резюме сессий по этой теме, которые я посетил. Все эти три сеанса проходили в один и тот же день, в одной и той же комнате, один за другим, и вместе давали три разных взгляда на лямбды, параллельные коллекции и параллелизм в целом в Java 8.
- На пути к JDK 8: лямбда, параллельные библиотеки и многое другое от Джо Дарси
- Закрытия и коллекции — мир после восьми Мориса Нафталина
- Fork / Join, лямбда и параллель (): параллельные вычисления упрощены (тоже?) Хосе Паумардом
В этом посте я расскажу о первой сессии, а две другие в ближайшее время.
На пути к JDK 8: лямбда, параллельные библиотеки и многое другое
На первом занятии Джо Дарси , ведущий инженер нескольких проектов в Oracle, представил основные изменения в языке, появившемся в JDK 8, такие как лямбда-выражения и методы по умолчанию, суммировал подход к реализации и изучил параллельные библиотеки и их новые модель программирования. Слайды этой сессии доступны здесь .
Развитие платформы Java
Джо начал с того, что немного поговорил о контексте и проблемах, связанных с развитием языка.
Общая политика развития для OpenJDK:
- Не нарушайте бинарную совместимость
- Избегайте введения несовместимости источников.
- Управление изменениями поведенческой совместимости
Приведенный выше список также распространяется на развитие языка. Эти правила означают, что старые файлы классов будут всегда распознаваться, случаи, когда в настоящее время законный код прекращает компиляцию, ограничены, а также избегаются изменения в сгенерированном коде, которые вносят изменения в поведение. Цели этой политики состоят в том, чтобы поддерживать связывание и работу существующих двоичных файлов и поддерживать компиляцию существующих источников.
Это также повлияло на наборы функций, выбранных для реализации на самом языке, а также на то, как они были реализованы. Такие проблемы также были в силе при добавлении замыканий в Java. Интерфейсы, например, являются обоюдоострым мечом. С теми языковыми возможностями, которые есть у нас сегодня, они не могут развиваться со временем. Однако в действительности API стареют, так как ожидания людей в отношении их использования развиваются. Добавление замыканий к языку приводит к действительно другой модели программирования, которая подразумевает, что было бы очень полезно, если бы интерфейсы могли развиваться совместимо. Это привело к изменению, затрагивающему и язык, и виртуальную машину, известные как default methods
.
Проект Лямбда
Project Lambda представляет скоординированные изменения языка, библиотеки и ВМ. В языке есть lambda expressions
и методы по умолчанию. В библиотеках есть bulk operations on collections
и дополнительная поддержка параллелизма. В ВМ, кроме методов по умолчанию, есть также усовершенствования в invokedynamic
функциональности. Это самое большое изменение в языке, когда-либо сделанное, больше, чем другие существенные изменения, такие как дженерики.
Что такое лямбда-выражение?
lambda expression
— это анонимный метод, имеющий список аргументов, тип возвращаемого значения и тело, и способный ссылаться на значения из охватывающей области видимости:
1
2
|
(Object o) -> o.toString() (Person p) -> p.getName().equals(name) |
Помимо лямбда-выражений, существует также синтаксис method reference
на method reference
:
1
|
Object::toString() |
Основным преимуществом лямбда-кода является то, что он позволяет программисту обрабатывать код как данные, хранить его в переменных и передавать его методам.
Немного истории
Когда Java была впервые представлена в 1995 году, не многие языки имели замыкания, но они присутствуют практически во всех основных языках сегодня, даже в C ++. Для Java это был долгий и извилистый путь, чтобы получить поддержку замыканий, пока в конце 2009 года окончательно не был запущен Project Lambda. Текущее состояние таково, что JSR 335 находится на раннем рассмотрении проекта , есть доступные двоичные сборки и ожидается, что он станет очень скоро часть основной линии JDK 8 строит.
Внутренняя и внешняя итерация
Есть два способа сделать итерацию — внутренний и внешний. Во external iteration
вы вносите данные в код, тогда как во internal iteration
вы вносите код в данные. Внешняя итерация — это то, что мы имеем сегодня, например:
1
2
3
4
|
for (Shape s : shapes) { if (s.getColor() == RED) s.setColor(BLUE); } |
У этого подхода есть несколько ограничений. Одним из них является то, что вышеуказанный цикл по inherently sequential
является inherently sequential
, хотя нет фундаментальной причины, по которой он не может быть выполнен несколькими потоками.
Переписанный для использования внутренней итерации с лямбда, приведенный выше код будет:
1
2
3
4
|
shapes.forEach(s -> { if (s.getColor() == RED) s.setColor(BLUE); }) |
Это не просто синтаксическое изменение, так как теперь библиотека контролирует, как происходит итерация. Написанный таким образом код выражает гораздо больше what
и how
, как оставить в библиотеке. Авторы библиотеки могут свободно использовать параллелизм, выполнение не по порядку, лень и любые другие методы. Это позволяет библиотеке абстрагироваться от поведения, что является принципиально более мощным способом ведения дел.
Функциональные интерфейсы
Project Lambda избегал добавления новых типов, а не использовал существующие методы кодирования. Java-программисты знакомы и давно используют интерфейсы с одним методом, таким как Runnable
, Comparator
или ActionListener
. Такие интерфейсы теперь называются functional interfaces
. Будут также новые функциональные интерфейсы, такие как Predicate
Block
1
2
3
|
PredicateisEmpty = s -> s.isEmpty(); Predicate isEmpty = String::isEmpty; Runnable r = () -> { System.out.println(“Boo!”) }; |
Таким образом, существующие библиотеки совместимы с лямбдами напрямую, что приводит к «автоматическому обновлению», сохраняя значительные инвестиции в эти библиотеки.
Методы по умолчанию
В приведенном выше примере используется новый метод Collection
, forEach
. Однако добавление метода к существующему интерфейсу в Java не допускается, так как это приведет к исключению времени выполнения, когда клиент вызывает новый метод в старом классе, в котором он не реализован.
default method
— это интерфейсный метод, имеющий реализацию, которая внедряется виртуальной машиной во время соединения. В некотором смысле это multiple inheritance
, но нет причин для паники, поскольку это множественное наследование behavior
, а не состояния. Синтаксис выглядит так:
1
2
3
4
5
6
7
|
interface Collection<T> { ... default void forEach(Block<T> action) { for (T t : this ) action.apply(t); } } |
Существуют определенные inheritance rules
для разрешения конфликтов между несколькими супертипами:
- Правило 1 — предпочитайте методы суперкласса интерфейсным методам («Победы класса»)
- Правило 2 — отдавайте предпочтение более конкретным интерфейсам, чем меньшим («Подтип выигрывает»)
- Правило 3 — в противном случае действуйте так, как если бы метод был абстрактным. В случае конфликтующих значений по умолчанию конкретный класс должен обеспечивать реализацию.
Таким образом, конфликты разрешаются путем поиска unique
, most specific default-providing interface
. С этими правилами «бриллианты» не являются проблемой. В худшем случае, когда не существует единственной наиболее конкретной реализации метода, подкласс должен предоставить ее, иначе произойдет ошибка компилятора. Если этой реализации необходимо вызвать одну из унаследованных реализаций, новый синтаксис для этого — A.super.m()
.
Основной целью методов по умолчанию является развитие API, но они также полезны как механизм наследования. Еще один способ извлечь из них пользу — это optional methods
. Например, большинство реализаций Iterator
не предоставляют полезного Iterator
remove()
, поэтому его можно объявить «необязательным» следующим образом:
1
2
3
4
5
6
|
interface Iterator<T> { ... default void remove() { throw new UnsupportedOperationException(); } } |
Массовые операции над коллекциями
Массовые операции над коллекциями также позволяют отображать / уменьшать стиль программирования. Например, приведенный выше код может быть дополнительно разложен путем получения stream
из коллекции shapes
, фильтрации красных элементов и последующей итерации только по отфильтрованным элементам:
1
|
shapes.stream().filter(s -> s.getColor() == RED).forEach(s -> { s.setColor(BLUE); }); |
Приведенный выше код еще больше соответствует постановке задачи о том, что вы действительно хотите сделать. Есть также другие полезные массовые операции, такие как map
, into
или sum
. Основными преимуществами этой модели программирования являются:
- Больше компостируемости
- Ясность — каждый этап делает одно
- Библиотека может использовать параллелизм, выход из строя, лень для производительности и т. Д.
stream
— это базовая новая абстракция, добавляемая в платформу. Он заключает в себе laziness
как лучшую альтернативу «ленивым» коллекциям, таким как LazyList
. Это средство, позволяющее извлечь из него последовательность элементов, источником которых является коллекция, массив или функция. Базовая модель программирования с потоками — это модель конвейера, такая как collection-filter-map-sum
или array-map-sorted-forEach
. Поскольку потоки ленивы, они вычисляются только по мере необходимости элементов, что окупается в таких случаях, как filter-map-findFirst
.
Другое преимущество потоков состоит в том, что они позволяют использовать преимущества параллелизма fork / join, поскольку библиотеки используют fork / join за кулисами, чтобы упростить программирование и избежать шаблонов.
Техника реализации
В последней части своего выступления Джо описал преимущества и недостатки возможных методов реализации лямбда-выражений. Были рассмотрены различные варианты, такие как внутренние классы и дескрипторы методов, но они не были приняты из-за их недостатков. Лучшее решение будет включать добавление уровня косвенности, позволяя компилятору выдавать декларативный рецепт, а не императивный код, для создания лямбды, а затем позволить среде выполнения выполнить этот рецепт, как сочтет нужным (и убедиться, что он быстрый).
Это звучало как работа для invokedynamic
, нового режима вызова, введенного в Java SE 7 по совершенно другой причине — поддержка динамических языков в JVM. Оказалось, что эта функция больше не только для динамических языков, так как она обеспечивает подходящий механизм реализации для лямбд, а также намного лучше с точки зрения производительности.
Вывод
Project Lambda — это большое скоординированное обновление для языка и платформы Java. Он обеспечивает гораздо более мощную модель программирования для коллекций и использует преимущества новых функций в ВМ. Вы можете оценить эти новые функции, загрузив сборку JDK8 с поддержкой лямбды . Поддержка IDE также доступна в сборках NetBeans с поддержкой Lambda и сборках IntelliJ IDEA 12 EAP с поддержкой Lambda .
Я уже сделал свой собственный опыт с лямбдами в Java в Wordcounter . Как я уже писал, я убежден, что этот стиль программирования быстро станет распространенным в Java, поэтому, если у вас еще нет опыта работы с ним, я советую вам попробовать его.
Ссылка: Devoxx 2012: Java 8 Лямбда и параллелизм, часть 1 от нашего партнера JCG Стояна Рачева в блоге Стояна Рачева .