Pull to refresh

Comments 139

Вот теперь астрологи точно могут объявить неделю Java 8 открытой :) Спасибо за перевод и за то, что выбрали именно этот туториал — возможно, что на данный момент он один из лучших по соотношению полезной информации к количеству знаков.
Честно говоря, какой-то странный релиз: с одной стороны, вроде бы добавили то, чего все так долго ждали, а с другой — как всегда сделали это по-своему. Лямбды ввели, а функции как объекты первого рода — нет (вместо этого использовав новое понятие функционального интерфейса). Вывод типов для этих лямбд есть, а для всего остального кода — нет. Множественное наследование запретили, а методы по-умолчанию — наоборот, добавили. Синтаксис и названия тоже радуют. Двойное двоеточия для получения ссылки на метод? Вы серьёзно? .putIfAbsent? .getOrDefault? Для всех этих операций в других языках уже давно придумали более или менее устоявшийся синтаксис/названия, равно как для всех новых для Java понятий в computer science уже есть хорошие, отточенные варианты дизайна. Но в Java нужно обязательно пойти своим путём и переизобрести колесо.
да, ощущение, что получилось сложнее, чем должно быть.
UFO just landed and posted this here
Чем плохо? По-моему нормально. Ваш вариант?

В шарпе обошлись всё той же универсальной точкой. Если нет скобок после названия метода, то это считается ссылкой на метод (грубо говоря).
UFO just landed and posted this here
Обратная совместимость. Такой код бы перестал компилироваться:
int x;

int x() {
    return x;
}

@Override
public String toString() {
    return "x = " + x; // x - ссылка на поле или метод x()?
}
Очень плохой пример. Даже если заменить x на this::x, то javac Вам радостно скажет
Error:(11, 25) java: method reference not expected here


Но основная мысль, конечно же, верная. Вот это уже сломалось бы.
Runnable x;

void x() {
}

test() {
    Runnable r = x;
}
А для чего вам нужны функции, как объекты первого рода? Можете считать java.util.function.* таким объектом первого рода. То, что ввели функциональный интерфейс это просто превосходно, т.к. позволяет использовать лямбды для огромного числа старых библиотек.


Функциональные интерфейсы — это хорошо, но наличие огромного числа интерфейсов Function, Consumer, Predicate, Supplier умноженное на Bi*, Int*, Long*, Double* и множество других, при этом не покрыть все возможности, требует запоминания, вместо того, чтобы ввести функции как объекты первого рода без ненужных имен, и так же оборачивать их, чтобы они удовлетворяли функциональным интерфейсам, как это например в том же котлине сделано.

Жить и работать с этим точно можно.


Более того, это здорово, что сделали хотя бы это. И сделали в целом неплохо, хоть и много спорных моментов. Но в целом придерживаются своей очень аккуратной позиции во введении «новинок». Спасибо, что не полная стагнация.
А для чего вам нужны функции, как объекты первого рода?

Хотя бы для того, чтобы не писать Integer::valueOf. В большинстве языков программирования, где функции являются first class citizens, к ним можно обратиться напрямую, как к любым другим переменным. Их можно напрямую передавать в дргуие функции или возвращать из них. В других языках, таких как Ruby, методы автоматически или полуавтоматически конвертируются в вызываемые объекты (lambdas, procs). В Clojure методы считаются «бедными функциями» и при необходимости явно оборачиваются в «полноценные» объекты функций. В Java же получается, что есть метод, а есть указатель на метод, который на самом деле функциональный интерфейс и вообще указывает не на конкретный кусок кода, а ссылается по имени и диспетчеризируется по количеству и типу параметров. Я знаю, как работать с функциями как объектами первого класса, и даже как оборачивать методы в полноценные объекты, но я с трудом понимаю, как управлять этим новым зоопарков в Java.

java.util.function — это, пардон, вообще издевательство. Во многих языках со статической типизацией есть функциональные типы. "(Int -> Int) -> Int" в Haskell или «POINTER TO PROCEDURE (a: Integer; b: Integer)» в Oberon или "(Int, Int) => Int" в Scala. В Java же почему-то решили, что пакет с ограниченным количеством предопределённых типов покроет все потребности разработчиков. Я искренне не понимаю, почему вместо всех этих нововведений нельзя было добавить синтаксический сахар для анонимных классов — семантика бы не поменялась, совместимость бы осталась, и при этом не пришлось бы добавлять новые, ещё не проверенные временем концепции.

Есть мнение, что вывод типов для всех переменных слишком опасный инструмент и даёт ухудшение читабельности кода. Я не имею четкого собственного мнения по этому поводу. но большой проблемы в этом не вижу.

Так а в чём концептуальное различие между лямбдами и обычыми функциями? Я знаю языки, которые стараются выводить типы везде, где возможно. Я знаю языки, где типы нужно всегда объявлять. Оба варианта имеют свои плюсы и свои минусы, но они, по крайней мере, следуют своим идеям. В Java идея частичного выведения типов ставит меня в тупик. Должен я его использовать или нет? Если да, то почему не везде? Если нет, то почему кое-где это позволено?

А какие задачи вы хотите решать с множественным наследованием?

Множественное наследование (или, что по сути то же самое, mixins и traits) — это стандартный способ расширения функциональности существующих объектов. По сути оно имеет смысл только в ОО языках с сильными идеями инкапсуляции — в остальных случаях вполне можно написать внешнюю к объекту функцию. Надо оно или нет — сложный вопрос, но сначала запрещать и долго всех пугать ужасами множественного наследования, а потом вводить его (хоть и с другим названием) в язык — это как-то странно.

Чем плохо? По-моему нормально. Ваш вариант?

Даже если забыть про C++, где двойное двоеточие широко используется для доступа к элементам неймспейса, семантика оператора для получения ссылки на метод совершенно не ясна. Большинство символьных операторов во всех языках программирования имеют определённую историю, идею, стоящую за ними. Оператор «точка» (.), например, аналогичен точке в номерах глав и разделов книг (напр., глава 1.4.2) — вы спускаетесь по иерархии объектов точно так же, как по главам рукописи. Оператор «стрелка» (->) в C можно прочитать как «перейти по ссылке». «Равно» для присвоения — как «теперь эта переменная равна...», и т.д. Как прочитать двойное двоеточие для взятия ссылки на метод — я не знаю.

Альтернативных вариантов куча. Самый простой, который сразу приходит в голову, это поставить амперсанд или «собачку» перед методом (&Integer.valueOf / @ Integer.valueOf) — тогда название «ссылка на метод» становится вполне интуитивным.

По-моему очень хорошо переизобрели.

Так а зачем вообще переизобретать? Одно дело, когда вы хотите добавить в язык принципиально новые возможности (напр., макросы в Лиспе), или пересмотреть дизайн существующих вещей, потому что они чем-то вас не устраивают (более строгая типизация в Scala), или проверить возможность использования принципиально другого базиса (ленивые вычисления в Haskell вместо активных в большинстве языков, или lambda calculus вместо машины Тьюринга). Но переизобретать что-то просто так, да ещё и вбрасывать новые непроверенные временем идеи в мейнстримовый язык — это какое-то ну очень странно.
Даже если забыть про C++, где двойное двоеточие широко используется для доступа к элементам неймспейса, семантика оператора для получения ссылки на метод совершенно не ясна.



Альтернативных вариантов куча. Самый простой, который сразу приходит в голову, это поставить амперсанд или «собачку» перед методом (&Integer.valueOf / @ Integer.valueOf) — тогда название «ссылка на метод» становится вполне интуитивным.


Да, это просто великолепно впишется в семантику явы.
@ часто ассоциируется с адресацией (например, это короткий синтаксис для dereference в Clojure, ну и электронная почта сюда же), & — устоявшийся символ получения ссылки в C. Чем они вас смущают и почему :: лучше?

Вопрос без подколов. Я абсолютно не пытаюсь затроллить ни вас, ни Java, и мне правда интересно, какова логика за этим новым оператором (равно как и другими нововведениями).
Для начала, ява всю свою жизнь адресацию от вас прячет.
Поэтому сама запись как (& операция взятия адреса) (Integer.toString имя ссылки) в традиции явы не вписывается.

Для явы получение прямого адреса на кусок памяти — это вообще святотатство :)

@ помимо прочего занято для аннотаций.

doSomething () {
     @Annotation("Blah blah blah")
     collection.forEach(@Integer.toString); 
     //Для меня это аннотация, либо обращение ко внутренним членам аннотации.
}


:: — лучше как раз таки потому что это отсылка к неймспейсам, потому что вы указываете
очень.длинное.полное.имя.Класса (:: доступ к пространству имен дефолтных реализаций интерфейсов) имяМетода.

Т.е. эта запись для явы читается не как «дай-ка мне ссылку на область памяти где размещен метод», а как «поищи среди интерфесов которые имплементит (очень.длинное.полное.имя.Класса) дефолтную реализацию метода (имяМетода)».
Так ведь в том то и дело, что это не обращение к элементу неймспейса — для этого уже есть точка :) Здесь как раз речь идёт о взятии метода «как есть», о получении ссылки на него (на что намекает название) или, что даже более правильно, о создании объекта-обёртки для него. Какой то оператор (хоть символьный, хоть буквенный) перед методом имел бы смысл и читался просто, а вот два двоеточия после имени класса/инстанса выглядят как-то странно :)

Ну и да, Джава всю жизнь прячет адресную арифметику, сами же ссылки активно используются.
Какой то оператор (хоть символьный, хоть буквенный) перед методом имел бы смысл и читался просто…


Ага, примерно так же как именование/разименование ссылок в сях, когда их лепят по 3-4 подряд.
Ну оператор получения ссылки на метод два раза применить вы не сможете.
Да, но с чего вы решили что унарный оператор будет удобнее чем :: после имени класса?

Почему, по вашему, коннотация к оператору взятия адреса в си здесь удобнее чем коннотация к неймспейсам? Учитывая что в яве нет ни неймспейсов ни оператора для получения адреса?
Как я уже сказал, смысл этого оператора покрыт тайной. Это не отсылка к неймспейсам из C++, поскольку смысл оператора совершенно другой. И даже не определение типа функции как, в Haskell. И не добавление элемента в список, как в OCaml. Совершенно непонятно, как его «читать» и что он точно делает (это специальный метаметод над Object или внешний к нему оператор языка? он возвращает объект или встраивает вызов функции в нужное место? и почему он в середине между именем объекта/класса и самим методом?). Всё это не смертельно и, как уже было сказано выше, круто, что хоть как сделали. Но, ёлы-палы, почему опять всё по-своему и игнорируя опыт других языков.
Как я уже сказал, смысл этого оператора покрыт тайной.

Дак это и не оператор.
Некая конструкция языка позволяющая использовать метод как лямбду.

Я понимаю что это звучит как некий чит. Дескать очень похоже на оператор но мы его так не называем. Зато позволяет разным реализациям jvm реализовывать лямбды по своему.
Т.е. даже объяснить, чем по сути является эта новая фича, оказалось не так просто. В чём и был мой пойнт.
Дак суть зависит от конкретной реализации.

Причем разработчики языка уже обжигались на внесении реализации в стандарт языка с java memory model. Прошлая версия описывалась на уровне буфферов процессов, общих буфферов и т.д. нынешняя версия оперирует лишь тем кто что должен видеть и какие отношения должны сохраняться, не привязываясь к конкретной реализации.
Как раз суть конструкции языка от реализации зависеть не должна. Это абстрактное понятие, но и оно должно что-то выражать. Что-то конкретное, пусть даже и не привязанное к имплементации.
Сдаюсь.

Пишите в JCP пусть меняют на & SomeClass.someMethod
Ставить дополнительный символ перед вызовом или использовать :: для вызова — скорее дело вкуса. Вызов :: смотрится вполне нормально. Можно было бы точно так же спросить, почему не вызов через -> (Integer->valueOf) или через какой нибудь другой символ (Integer#valueOf). Для таких вопросов вполне подходит ответ «потому что гладиолус» :)
Ну вот и получается, что «потому что гладиолус». А вот отцы-основатели всё-таки как-то более ответственно подходили к именованию ключевых элементов языка :)
Если уж на то пошло, Java — это язык, где именованию, выбору синтаксиса, конвенциям и т. д. уделяют наибольшее внимание среди всех известных мне языков. Я слежу за этой темой, изучал API в Rust, Julia, Scala — полное впечатление, что авторам этих языков просто наплевать. Методы названы первым пришедшим в голову способом, порядок аргументов.

В Java-сообществе есть серьезные холивары, называть методы с префиксом get или нет, сначала публичные методы или приватные в классе, импорты звездочкой или каждый класс по-отдельности. Во всех остальных сообществах на это всем насрать.
Остальные сообщества не ограничиваются перечисленным списком. Посмотрите для примера на Python, в частности, на тот же мап (а вернее словарь, dict в терминологии Питона). Если вы там найдёте хоть один метод, названный нелогично или делающий не то, что обещает, сразу пишите Гвидо — он уже 20 лет с фанатизмом выпиливает все сложные и неочевидные конструкции.
Python — это мой второй основной язык, поэтому можете не рассказывать. Разумеется, там в API тоже тонны говна, имен, взятых из unix и шелл-скриптов, о качестве и согласованности которых можно промолчать. Что касается конкретно dict:

1. Почему все методы, за исключением (внезапно) has_key, пишутся маленькими буквами без пробелов, что само по себе мерзко, а не через подчеркивание, как по идее принято в языке, несмотря на то, что тут завязки на Unix никакой быть не может?

2. setdefault — сомнительное название для этой семантики. putIfAbsent и то лучше

3. Как в нормальном АПИ могли одновременно оказаться методы items, iteritems и viewitems? Аналогично с keys и values.

— Но я не виню Python. Он ведь старый, старее Java. В пример приводил свежие языки, где, казалось бы, можно было учесть весь опыт предшественников и с чистого листа сделать реально хорошо.
Разумеется, там в API тоже тонны говна, имен, взятых из unix и шелл-скриптов, о качестве и согласованности которых можно промолчать.

Если вы говорите про модули «sys» и «os», то не делать отсылок к shell скриптам и юниксам (где Python чаще всего является системным языком) в модулях, которые работают с системой, было бы как-то странно. Или вы про что?

Про dict.

1. В style guide от Гвидо довольно чётко прописано, что есть определённые рекомендации по именованию, но, если вы считаете, что альтернативное имя для переменной или функции будет смотреться лучше, то ок, используйте его. В конечно счёте вы пишете для себя и таких же как вы. Стиль имён без подчёркиваний активно используется во встроенных функциях, и подавляющим большинством питонщиков воспринимается вполне горячо. То, что не все имена написаны в одном стиле, не лишает их красоты ;)

2. «setdefault» — это типичное сокращение, подпадающее под то же правило. В полной версии метод назывался бы «getOrSetDefault», но так слишком уж страшно и неудобно. Хотя да, семантика set противоречит поведению с get, что периодически вызывает конфуз.

В целом про putIfAbsent я позже напишу, а то я вижу, что мой главный пойнт в обсуждении уже потерялся.

3. iteritems() появился как новый стиль для items(), в Py3k приставка iter была убрана и все указанные методы теперь по умолчанию возвращают итератор. А вот с view* всё и так хорошо: view объекты, как бы, не эквивалентны ни спискам, ни итераторам.
Имена из сплошных маленьких букв это вообще не стиль, а недоразумение, ноги которого растут все из тех же юниксов, где, наверное, были какие-то проблемы с поддержкой регистра букв…

Короче говоря. В Python-сообществе тоже определенно уделяется внимание к именованию, синтаксису, стилю. Но в меньшей степени, чем в Java-сообществе.
.putIfAbsent? .getOrDefault? Для всех этих операций в других языках уже давно придумали более или менее устоявшийся синтаксис/названия

Не поделитесь, какие именно? А то у словарей в шарпе метода для получения элемента с фолбэком нет, поэтому я всё время таскаю extension метод GetOrDefault. Если есть устоявшееся название, я бы переименовал. :)
Для GetOrDefault чаще всего используется что-то вроде:

x = map["foo"] or 1 # x = map.get("foo") or 1
Либо, если метод .get при отсутсвии элемента бросает эксепшен, то:
x = map.get("foo", defaultValue)
В Java Map.get всегда принимает один параметр, поэтому введение перегруженного метода со вторым параметром не поломает старого кода.

putIfAbsent — это явная отсылка к кешам (я даже погуглил другие варианты использования, но ничего внятного не нашёл). Общепринятого названия здесь нет, но обычно называется getOrCompute, getOrStore и т.д. Всё-таки метод возвращает значение, а не просто кладёт его, если раньше не было.
x = map["foo"] or 1 # x = map.get("foo") or 1

Положим, это не одно и то же. Если в мапе по ключу «foo» лежит false или 0, я бы не хотел получить единицу.

Общепринятого названия здесь нет, но обычно называется getOrCompute, getOrStore и т.д. Всё-таки метод возвращает значение, а не просто кладёт его, если раньше не было.

В шарпе GetOrAdd называется (но есть только в concurrent словаре, для обычных приходится extension-метод таскать...).

Что-то не сходится теория устоявшегося названия. :)
Возможно, я не очень точно выразился. В CS есть определённые принципы и терминология, которые позволяют, среди прочего, быстрее ориентироваться в новых незнакомых системах. Например, вызов функции принято записывать как `func(x)`, обращение к элементу массива — через `[]` и т.д. Это те названия, которые мы ожидаем увидеть, даже не владея полностью технологией. Те слова, которые мы ищем в гугле или документации, пытаясь выразить то, что нам нужно. Использование общих названий и ожидаемой нотации не обязательно, но обычно для её нарушения существуют веские причины (в Лиспе, например, пишут `(func x)` вместо `func(x)` не потому что так больше нравится, а потому что это даёт гомогенность кода и данных).

Название `putIfAbsent` я вижу первый раз (и, судя по гуглу, оно не является особо распространённым). Если я открою документацию и попробую найти метод для извлечения старого элемента или обновления хранимого значения, то я буду ожидать увидеть что-то вроде «getOrStore» или «fetchOrCompute», или «getOrAdd», как в C#, но никак не «putIfAbsent». Я вообще не буду ожидать, что метод «put*» будет возвращать значение. Я вполне уверен, что автор этого нового метода знает, что есть такой язык C#, и что в нём для отображений есть метод «GetOrAdd». Тогда мне интересно, почему автор решил не использовать это название.

Опять же, я не пытаюсь доколебаться или обвинить авторов языка. Я знаю много разных подходов, видел совершенно разные взгляды на одни и те же проблемы, и, как правило, эти взляды основывались на какой-то логической системе. В Java 1.4 я видел чёткую систему. В Java 1.5-1.7 — почти чёткую. Для 1.8 я пока не увидел концепций, которые авторы пытались в неё вложить.
Логика хотя бы в том, что методы, начинающиеся с «get», никогда не меняют состояние коллекции. На это можно полагаться. В этом смысле getOrAdd — плохое название, да и вообще, глаголы add, store в API мапы не встречаются. Тогда уж getOrPut, что уже совсем хрень.

put возвращает предыдущее значение с самого начала, это нельзя изменить.
Ну по названию «getOrAdd» довольно очевидно, что оно может изменить коллекцию. По крайней мере здесь не будет неожиданностей для случайного пользователя. Если хочется оставить «get» для чего-то, что не модифицирует коллекцию, есть ещё куча синонимов, например, «fetch» (неоднократно видел названия типа «fetchOrAdd», «fetchOrUpdate» и т.д.).

То, что «put» возвращает старое значение, меня тоже всегда забавляло. Но это уже другой вопрос.
\troll mode on
Если вы видете putIfAbsent в первый раз за его 9.5 летнее публичное существование в стандартной либе, то увы вам. Значит вы с Java работаете эпизодически.

> Я вообще не буду ожидать, что метод «put*» будет возвращать значение
Еще одно подтверждение, что Java вы знаете поверхностно.
\troll mode off

Ответ прост: так сложилось в экосистеме Java и менять чего-то ради абстратного CS нет смысла.
И еще, автор этого метода хоть и занятой, но достаточно открытый человек, спросите почему он так назвал.
Автор тут
Спрашивать лучше тут
Если вы видете putIfAbsent в первый раз за его 9.5 летнее публичное существование в стандартной либе, то увы вам. Значит вы с Java работаете эпизодически.

Правильнее будет сказать, что с пакетом java.util.concurrent я не работаю совсем. Почему — это уже другой вопрос, связанный с транзакционной памятью, асинхронными сообщениями и многим другим, что нашей основной темы не касается.

Я вообще не буду ожидать, что метод «put*» будет возвращать значение

Я как бы в курсе, что в Java «put» со странностями. Но отойдите от конкретного языка программирования и посмотрите на название метода. Что вы ожидаете от метода «положить»? Наверное, то, что он что-то куда-то положит. А какой метод вы будете искать, чтобы что-то извлечь? Наверное «извлечь» или какой-нибудь синоним.

Ответ прост: так сложилось в экосистеме Java и менять чего-то ради абстратного CS нет смысла.

Не забывайте только, что лямбды, вывод типов и большая часть нововведений в Java также пришла из этого «абстрактного CS».
Хотя ответ «так сложилось исторически» меня устраивает, да.
> Но отойдите от конкретного языка программирования и посмотрите на название метода. Что вы ожидаете от метода «положить»? Наверное, то, что он что-то куда-то положит. А какой метод вы будете искать, чтобы что-то извлечь? Наверное «извлечь» или какой-нибудь синоним.

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

Вы бы лучше на оператор [] в С++ std::map посмотрели ;)
> Не забывайте только, что лямбды, вывод типов и большая часть нововведений в Java также пришла из этого «абстрактного CS».
Да нет, все это пришло из практического CS.
А вы аппелируете как раз к абстрактному.
Да нет, все это пришло из практического CS.
А вы аппелируете как раз к абстрактному.

И чем же моё абстрактное CS отличается от вашего практического?

Вы бы лучше на оператор [] в С++ std::map посмотрели ;)

Пример странностей в C++ не оправдывает нелогичной семантики в Java.

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

Если вам нужен метод, который вытесняет старое значение, ну так и назовите его «вытеснить». По-английски это очень красиво — «displace». Если хотите точного описания семантики, то назовите «putOrDisplace».

Так или иначе, я не против дополнительной функциональности в стандартных методах. Если «put» используется для добавления элементов в отображение, но при этом имеет ещё фишку в виде возврата предыдущего значения, то это нормально. Это добавляет удобства, а значит имеет смысл. Изначально меня возмутило название метода «putIfAbsent», который используется для получения значения. Для однопоточных коллекций это выглядит довольно странно. Для многопоточных — имеет смысл. Сохранить название метода при переносе с многопоточных в однопоточные коллекции — тоже имеет смысл. Мне это кажется некрасивым, но в этом есть какая-то логика. Однако это пока что единственное решение из перечисленных, за которым я увидел какую-то систему.
Изначально меня возмутило название метода «putIfAbsent», который используется для получения значения.

Так он так и не используется, где Вы это нашли? Спец. поискал его использование в посте, нашел единственное место, где результат вообще игнорируется. Основное назначение метода — именно put.
В статье синтетический пример, показывающий только основной смысл. А вот кусок из первого же результата в Гугле (пропуская javadocs):

AtomicLong newValue = new AtomicLong(value);
AtomicLong oldValue = map.putIfAbsent(key, newValue);
if ( oldValue != newValue ) {
    result = oldValue.addAndGet(value);
}


Т.е. мы добавляем элемент, а затем проверяем, заменился ли он. Поправьте меня, если я ошибаюсь, но по-моему это как раз типичный вариант использования «putIfAbsent» в concurrent map.
Пример странностей в C++ не оправдывает нелогичной семантики в Java.

Практика показывает следующее, что куча людей пишет про некоторую нелогичность. И хочет сделать «вот так». Начинаем разбираться, собирать фидбеки, и выясняется, что это «вот так» у разных людей практически не пересекается. Сколько людей столько и мнений. У меня есть свои места в Java, которые я считаю нелогичными, но они во первых не пересекаются (по результатам обсуждения) с вашими, а во вторых не мешают мне жить.
Можно сколько угодно пытаться изобрести идеальный велосипед, но боюсь в этом случае мы бы с вами не обсуждали бы Java8, а комментировали очередной пост Марка про поезд. ;) Знаете, лучшее враг хорошего.
Сейчас сюда прибежит 23derevo с докладом про trade-offs. :)

Если вам нужен метод, который вытесняет старое значение, ну так и назовите его «вытеснить». По-английски это очень красиво — «displace». Если хотите точного описания семантики, то назовите «putOrDisplace».

Ничего более нелогичного в жизни не видел. ;))))

Так или иначе, я не против дополнительной функциональности в стандартных методах. Если «put» используется для добавления элементов в отображение, но при этом имеет ещё фишку в виде возврата предыдущего значения, то это нормально. Это добавляет удобства, а значит имеет смысл. Изначально меня возмутило название метода «putIfAbsent», который используется для получения значения.


Все же просто. Есть put который всегда вытесняет старое значение, и put которые никогда не вытесняет старое значение. Два полезных метода. дальше мы их называем и живем. И кстати, контракт на результат у обоих одинаковый! Консистенси однако.

Я не зря поставил ссылку на автора, упрекать Doug Lea в том, что он не знает CS по меньшей мере смешно.
Ничего более нелогичного в жизни не видел. ;))))

А чем это нелогично? Вы назваете метод тем именем, которое описывает его поведение. По-моему, вполне.

Дуга никто не упрекал. Как я уже сказал, для многопоточных коллекций, над которыми он работал, такой метод имеет смысл.
Учитывая наличие методов get и put, лично я ожидал бы метод getOrPut. С другой стороны, в джаве метод put тоже возвращает значение, так что возвращение значения методом putIfAbsent не настолько уж неожиданно.

Гм. Прочитал документацию. Внезапно, putIfAbsent возвращает существующее значение, если оно есть, и удалённое значение, если его не было (то есть null). Вот это — сюрприз.
Инженеру неплохо бы понимать, что язык с 20-и летней историей нельзя просто взять и переделать под «другие языки». Если это сделать, то это будет не java, а просто еще один новый язык программирования, в чем-то отдаленно похожий по синтаксису на C++. Ценность 20и летней экосистемы именно в том и состоит, что все за это время созданное — библиотеки, паттерны, стилистические рекомендации, опыт и сообщество — остается процентов на 90 актуальным да при выходе новой версии.
Речь не о том, чтобы переделать язык под что-то другое. Речь о том, что варианты реализации многих новых для Java фич уже хорошо известны в computer science. И они совсем необязательно должны ломать существующую экосистему — наоборот, они расширяют эту экосистему, позволяя использовать не только достижения конкретного языка с 20-летней историей, но и всех компьютерных наук с больше чем 60-летней историей. При переходе между Scala, Clojure, Python, Ruby, C++ и т.д. у меня есть готовые рецепты, паттерны решения повторяющихся задач. При переходе на Java мне придётся изобретать все эти рецепты заново.
Вы знаете, то, что сейчас выкатили в 8-ке — обсуждалось в сообществе в течении лет 3х уже. Если лично вы все это время знали решение всего комплекса проблем лучшее, чем то, что было реализовано, и если вам судьба джавы не безразлична — то где вы были все это время?

У меня пока складывается ощущение, что это претензии из категории «Как жаль, что все, кто знают как правильно управлять страной — уже работают дворниками и таксистами» (не помню откуда)
Эти изменения обсуждались гораздо больше 3-х лет, и я долгое время наблюдал за развитием идей. Вместо меня высказывались люди гораздо более уважаемые в сообществе. Однако, сообщество фрагментировано, а решение принимает коммитет под давлением крупных компаний. В итоге после выхода каждой новой версии языка можно было наблюдать, как всё больше количество людей уходило в другие языки. Та же Scala три года назад воспринималась как модная игрушка, которую можно попробовать разве что на своих домашних проектах. Сейчас же многие крупные системы пишутся полностью на Scala, а для Java создаётся только интерфейс совместимости.
putIfAbsent — давно устоявшееся в Java название из интерфейса ConcurrentMap (ещё с Java 1.5). Просто до появления default-методов нельзя было добавить такие штуки в интерфейс Map: куча стороннего кода бы сломалась. А теперь можно, и этот метод из ConcurrentMap легко перекочевал в вышестоящий интерфейс. Было бы в высшей степени странно, если б его назвали по-другому.
Вот это уже имеет смысл, да. Спасибо за разъяснение.
for (int i = 0; i < max; i++) {…

Вот за это я и не люблю Java. В XXI веке, когда в в этом высокоуровневом языке появились элементы ФП, насколько мне известно до сих пор нет нормального синтаксического сахара для коллекций/итераторов. Всё как-то через одно место с привкусом C++ и седых веков. Это как inline-стили в html в духе «мы из 90-х». Там вообще есть нормальный foreach в стиле (k, v in hash) {}? Или он есть, но все привыкли писать подобным образом?

Возможно, я не прав и Java в этом вопросе подтянулась, хотя бы, до уровня Python — просветите, пожалуйста.
Можно итерировать вот так:

for (Item item: somethingIterable) {
   ...
}

Я подозреваю, что вы даже эту статью не прочитали полностью, уже не говоря про какую-нибудь книгу по Java. Но уже за что-то не любите ее.
Даже в этой статье указано 2 красивых способа обхода колекций.
Посоветуйте хорошие книги/ресурсы для изучения, пожалуйста. Я начинал читать «какие-нибудь» книги по Java и забрасывал, доходя в них до этих уродливых «for (int i = 0; i < 10; i++) {» и не находя нормальных альтернатив. В интернете с материалами по Java похожая ситуация как с материалами по HTML — много устаревшего и неактуального шлака времён HTML 3.2, на первых страницах поисковиков.

В этой ветке обсуждений я уже увидел, что в Java всё-таки есть нормальные способы — только где о них можно почитать в концентрированном виде, чтобы иметь дело с современными best practices, а не с привычками бородатых программеров из 90-х?
… много устаревшего и неактуального шлака времён HTML 3.2, на первых страницах поисковиков.

Иногда, в этих случаях, помогает выставить в параметрах поиска ограничение на последние N лет :)
В этой ветке обсуждений я уже увидел, что в Java всё-таки есть нормальные способы — только где о них можно почитать в концентрированном виде, чтобы иметь дело с современными best practices, а не с привычками бородатых программеров из 90-х?

Java — это не C++, этот язык вполне по силам полноценно изучить. После «уродливых» циклов for дошли бы до итерации. Всё равно знать надо и то, и другое. Не страдайте фигнёй, просто прочитайте от корки до корки, а потом уже выбирайте по вкусу.
Вообще-то да, теперь вместо

for (int i = 0; i < 10; i++) {
    map.putIfAbsent(i, "val" + i);
}

можно писать

IntStream.range(1, 10).forEach(i -> map.put(i, "val" + i));
UFO just landed and posted this here
Вместо
IntStream.range(1, 10).forEach(i -> map.put(i, "val" + i));

Лучше (и безопаснее) написать:
IntStream.range(1, 10).boxed().collect(Collectors.toMap(i -> i, i -> "val" + i));

Простите, но можете пояснить?
А то я, честно признать, с лямбдами почти не работал, а понять их хочется.
В случае boxed-стрима боксинг для i произойдёт один раз.
Получение коллекторного мапа позволяет не думать, кто ещё и в каких потоках испольлзует map. Да и создавать его не приходится отдельно.
Можно не боксить.
У IntStream тоже есть метод collect, нет только стандартных заранее определенных коллекторов и придется ручками написать все 3 лямбды для mutable reduction.
> Там вообще есть нормальный foreach в стиле (k, v in hash) {}?
Теперь да:

map.forEach((k, v) -> ...);
Странно, что нормальных getter'ов setter'ов нету (или есть?), даже в js добавили уже.
Было бы замечательно что то типа:
Property int prop: read GetProp write SetProp;
Параллельная сортировка неплохо масштабируется, кстати. Запустил на 40-ядерной тачке, ускорение в 25 раз.
UFO just landed and posted this here
Там под капотом ForkJoinPool. По умолчанию берётся ForkJoinPool.commonPool(), который использует столько потоков, сколько ядер в системе по версии Runtime.getRuntime().availableProcessors(), либо сколько указано в проперти java.util.concurrent.ForkJoinPool.common.parallelism.
Однако, если запустить parallelStream() изнутри ForkJoinPool таски, он останется в этом пуле. Так что можно сделать примерно так:
ForkJoinPool pool = new ForkJoinPool(5);
long count = pool.submit(() -> values.parallelStream().sorted().count()).get();
UFO just landed and posted this here
На мой взгляд, довольно удобно. По умолчанию просто использует все доступные ресурсы, без всяких провайдеров и фабрик. Zero configuration. Если надо ресурсы ограничить, это делается одной строчкой.
Интересно, как оно будет масштабироваться в реальных условиях. Например, когда таких сортировок несколько.
UPD: собственно, согласен с vsb.
Чем больше сортировок будут постоянно сидеть на системном пуле, тем лучше он будет утилизирован :-)
Я даже боюсь представить (в хорошем смысле) что будет со Scala к тому времени как выйдет Java 15.
Не думаю. Мне кажется рано или поздно настанет момент стагнации.
Уже сейчас настал. Сейчас они двигаются в направлении улучшения стабильности и производительности. В Scala 2.11 нет ни одного значительно улучшения синтаксиса.
Ну если развитие языка определять только количеством новых фич, то можно с вами согласиться.
Да ладно. Кто же измеряет развитие, изменением синтаксиса?
// offtopic
Всегда было интересно, какими умозаключениями руководствуются люди, ставящие, запятые, где, попало =)
// offtopic
А, кто сказал, что, они, руководствуются, какими бы, то ни было, умозаключениями?
Они, просто, ставят запятые, где, им хочется)
Не получится. Я ходил на лекции ребят, которые пилили лямбды для 1.8, когда они приходили к нам в гости, и там этот вопрос поднимался отдельно. Официальный ответ был — если вам нужна Скала со всеми её наворотами, то она уже есть, а мы рассматриваем джаву как консервативный язык, требующий очень вдумчивого и аккуратного точечного осовременивания без фанатизма.
Имейте в виду, что этот код останется корректным даже если убрать аннотацию @FunctionalInterface.

Тогда какой смысл в аннотации?

Но в отличии от анонимных объектов, переменная num не обязательно должна быть объявлена как final.

Почему на анонимные объекты не распространили фичу? Хотя фича странная, учитывая, что содержательно всё тот же final…

Запись в переменную num в пределах лямбда-выражения также запрещена.

А если очень хочется, то как предполагается переписать? Как этот код на шарпе перевести на джаву?

var list = new List<int> { 1, 2, 3 };
int sum = 0;
list.ForEach(i => sum += i);

JDK 1.8 содержит множество встроенных функциональных интерфейсов.

Есть аналоги дженериковых Acton<T1, ... , TN> и Func<T1, ... , TN, TResult> из шарпа, чтобы не плодить тьму бесполезных интерфейсов ради одного вызова?

P.S. Пардон, если вопросы идиотские. Я в джаве не бум-бум.
UFO just landed and posted this here
> Обычно делают извратом:
>
> int[] sum = new int[1];
> list.forEach(i -> sum[0] += i);
> return sum[0];
> 

DON't DO IT!!! проблем не оберетесь при параллелизации.

IntStream s = ....
s.sum(); // PROFIT
UFO just landed and posted this here
> Вроде так обычно реализуют везде и на первый взгляд ничего сложного.
Мы несколько лет зазывали людей в OpenJDK — приходите, пишите. Не хотите писать, так хоть в обсуждениях поучаствуйте, вопросы поднимите и т.д. А воз и ныне там.

> лямбда-объект не выходит за пределы функции
лямбда (by design) должна убегать не только за пределы функции, но и за пределы родного треда. Внутри функции — неинтересный частный случай.

Вкратце: было принятно обоснованное решение не делать лямбды с захватом контекста.
И очень зря, кстати. По сути, уперлись в один узкий юзкейс (тупая параллелизация), проигнорировав кучу остальных, где все эти ограничения на фиг не сдались (callback chains, например).

(да, я подписан на lambda-dev с момента открытия оного, и внимательно читал все дискуссии по дизайну)
> вопрос был именно в том, что делать, если очень надо модифицировать локальную переменную

Я не видел еще ни одного настоящего примера, когда действительно «надо модифицировать локальную переменную».
Все что мне «предлагалось» раньше — от лени да непонимания яыка.
Если кто-нибудь, приведет мне настоящий пример требующий модификации локала — я буду только рад.
Ну вот, например, стандартный кусок на Scala (сайт на Lift):

def render = {
  var name="";
  
  def process {
     //do something with name
     println(name);
  }

  ".input" #> SHtml.text(name, name=_) & //первая лямбда - модификация локальной переменной
  ".submit" #> SHtml.submit("Send", process _) //вторая лямбда - использует локальную переменную, сама функция см. выше
}


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

Тоже самое можно сделать и с полями объекта, но с локальными переменными как-то логичнее — поля могут использоваться всеми методами объекта, а локальные переменные — только этой группой лямбд.
Ага! Давайте разбираться.

> Lift выполняет лямбды в том порядке в котором они заданы, что дает возможность использовать локальные переменные в качестве хранилища для передачи данных из одной лямбды в другую

То вы написали — реализация ака «как».
Скажите на более верхнем уровне — что требуется сделать?
Если я правильно нашел источник, то задача ассоциировать элементы форм с действиями на серверной стороне. С точки зрения скалы мне кажется странным такой подход, потому что передаваемые методы (process в примере) по определению работают через сайд эффекты.
Глобальная задача — обработать введенные пользователем в HTML-форму данные на сервере.

Для этого в Lift с каждым элементом формы связывается лямбда, которая выполняется при отправке формы на сервер (в том порядке, в котором создавались элементы формы) и обрабатывает введенные в этот элемент данные. Плюс лямбда на кнопку submit, которая обрабатывает всю форму в целом.

Конечно можно под каждую форму создавать класс и делать все красиво и правильно, но на это потребуется в 2-3 раза больше времени чем на описанное решение (я пробовал). Сайдэффекты в указанном случае не страшны т.к. все локализовано в небольшом фрагменте кода.

То есть в данном случае модификация локальной переменной — удобный способ написать код. Можно сделать без них, можно сделать даже, наверное, правильнее, с точки зрения чистоты кода, но указанный способ мне кажется довольно оптимальным в плане удобства.
Еще раз, почему вы не можете обмениваться инфой через кучу? В чем реальная необходимость локала?

Java8 Stream API задизайнено для parallel + unordered обработки. Да, можно аккуратно все заограничивать и сделать sequential + ordered обрабоку и тогда можно работать с локалами. Но тогда на 1 разработчика, который все аккуратно сделает sequential + ordered, найдется 100500 кодеров, которые огребут в полный рост работая с parallel + unordered через локалы.

И потом, вы же работаете со Scala. Прекрасно, я вообще считаю, что для современного программиста считающего себя специалистом — знание Scala это must have (даже если он на ней не пишет). У Java нет задачи вытеснить Scala.
Я могу сделать аналогичную вещь через кучу, не проблема. Сильно сомневаюсь что можно найти случай, когда нельзя заменить запись в локальную переменную работой с кучей, если понимать реальную необходимость как невозможность решить какую-то задачу другим способом, то её в данном случае нет. Но ведь синтаксический сахар вводится не ради реальной реобходимости, а ради упрощения разработки. Почему компилятор не может заменить работу с локальной переменной работой с кучей автоматически?

Окей, заменяем запись в локальную переменную работой с кучей, как это видится наиболее прямолинейным способом — разве в этом случае кодеры (parallel+unordered) ничего не огребут? Возможность выстрелить себе в ногу сохраняется в любом случае.

Почему компилятор не может заменить работу с локальной переменной работой с кучей автоматически?

Может, но не хочет. ;)
Куча она всегда куча, а автохипинг (хиппинг? ;)) локала вещь неоднозначная и меняющая семантику. Просматривать весь код метода только для того чтобы понять есть у меня там лямбда меняющая поведение локала или нет — увольте.
Когда у тебя язык где все переменные в куче — вопросов вообще нет, но Java не такой язык. И да, кстати, в байткоде никаких локальных переменных нет.

Окей, заменяем запись в локальную переменную работой с кучей, как это видится наиболее прямолинейным способом — разве в этом случае кодеры (parallel+unordered) ничего не огребут? Возможность выстрелить себе в ногу сохраняется в любом случае.

Выстрелить в ногу можно всегда. Просто хипом дуло торчит чуть побольше и более заметно.
Если я могу записать то же самое короче и понятнее с модификацией локальной переменной, а параллелизация мне при этом абсолютно не нужна — это не лень, это разумный выбор кратчайшего пути.
Это, конечно, немного звиздец (использовано недостаточно фабрик для сложения пяти чисел), но не суть важно. Я тоже могу написать list.Sum(). Вопрос был именно в использовании изменяемой локальной переменной.
Можно использовать AtomicInteger или LongAdder вместо локальной переменной типа int. Кстати, это будет потокобезопасно, и можно суммировать параллельный стрим.
По-моему, так гораздо лучше:

list.stream().reduce((x, y) -> x + y);
Мне оба решения не очень нравятся. Первое выпячивает проблемы конвертации между объектами и примитивными типами. Второе скрывает за страшной конструкцией банальный sum («очевидно» это только для функциональщиков). Оба решения слишком далеко от лаконичного list.Sum() из шарпа.

Реально ли сделать метод а-ля FooLibrary.sum(list.stream()) (если он уже не сделан)?
Тогда лучше

list.stream().reduce(Reducers::sum);
Такое точно реализуемо? Просто не знаю, как в джаве, а в шарпе в чистом виде (без рефлексии и динамических типов) такое невозможно: operator+ не определён для Object, а в ограничениях на тип это требование не сформулировать.
Могу ошибиться т.к. еще не пробовал. Но если записать с дженериками — то должон уметь.
(Точнее ошибка уже есть т.к. в reduce должен быть еще 1 аргумент)

Stream<Integer> stream = (List<Integer> ).stream();
stream.reduce(0/*Должен заавтобоксится в Integer*/,  BinaryOperator<Integer> Reducers::sum);

//Если подставить дженерики будет
@FunctionalInterface
BinaryOperator <Integer> {
   public Integer operate(Integer a, Integer b);
}

//Соответсвенно sum должен быть:

Integer sum(Integer a, Integer b) {
    /*Интеджеры мы складывать умеем, если что анбокснем*/
    return a + b;
}


Но вообще вроде как у IntegerStream есть просто метод sum();
Ты не ошибся, пробовал как ты написал.
Можно вообще сделать метод ListExtensions.sum(list). Для этого вам даже Java 8 не нужна.
UFO just landed and posted this here
>> Имейте в виду, что этот код останется корректным даже если убрать аннотацию @FunctionalInterface.
> Тогда какой смысл в аннотации?
Проверка, что интерфейс действительно функциональный. Выдаст ошибку при несоостветсвии требованию на функциональный интерфейс.
Тогда какой смысл в аннотации?

Если аннотацию @ Override убрать, код тоже останется корректным, однако она сильно упрощает жизнь. Тут, видимо, аналогичная ситуация.
Имейте в виду, что этот код останется корректным даже если убрать аннотацию @FunctionalInterface.


Тогда какой смысл в аннотации?


она в компайл-тайм проверяет, что у вашего интерфейса ровно один абстрактный метод. Если ноль или несколько — будет ошибка компиляции.
Хорошая статья, спасибо! Про потоки стоит еще добавить важное замечание, что все промежуточные операции над потоками — ленивые. Т.е. они не будут выполнены пока не вызвана терминальная операция.
Нужно бы напомнить, что в текущем релизе Java 8 есть серьёзные ошибки. Есть тонкости работы с reflection и самое главное — не работают множественные catch внутри лямбда функций (кстати, про множественный catch, Вы как-то умолчали). Причём не работает оно страшно — внешне всё в порядке, просто код для catch не генерируется.
Обещают исправить в ближайших релизах.
Не холивара ради, а из чистого любопытства: т.е. в Java лямбды появились только сейчас, спустя 10 лет после .net? Чем вызван столь огромный разрыв во времени (для IT-индустрии просто целая эпоха) — в Java какая-то своя специфика, что ни лямбды, ни атрибуты, ни Linq просто не нужны (компенсируется возможностями библиотек, иной спектр задач....)? В каком тогда направлении эти годы развивалась Java?

Опять же, не хочу принижать Java, наверняка это замечательный язык, просто не представляю уже современную разработку без лямбд, позволяющих писать очень лаконичный и выразительный код, в стиле, близком к декларативному.
Я подозреваю, что у Сан/Оракл и Микрософт сильно разные подходы к развитию. Микрософт пытается впихнуть весь мир в один язык, Сан/Оракл же скорее видят яву как один из элементов экосистемы. Т.е. «java is a new C», а если вам нужно что-то более высокоуровневое — так есть Scala/Closure/JRuby/напишите свое.

Мне кажется, что в мире .net людей, которые реально глубоко знают свою платформу — в разы меньше, чем в мире явы. Просто потому, что .net-разработчики больше вынуждены гнаться за поверхностными новинками, чем за глубиной (об этом еще Спольски хорошо писал). Во всяком случае, статьи про устройство и особенности работы .net VM которые я вижу последние годы — в мире явы были опубликованы лет 5-7 назад.
а если вам нужно что-то более высокоуровневое — так есть Scala/Closure/JRuby/напишите свое

Насколько это работает? Та же скала набрала достаточно популярности, чтобы конкурировать с джавой?
Я сейчас в основном работаю с двумя довольно большими распределёнными фреймворками — Spark и Kafka. Оба вполне популярны и претендуют на то, чтобы занять лидирующие позиции в нише распределённых систем. Оба написаны на Scala.
>Та же скала набрала достаточно популярности, чтобы конкурировать с джавой?

Популярности у нее хоть отбавляй. Всякие яндексы и прочий крупняк пишут на ней свои сервисы.
А вот конкуренция с Java вообще бессмысленна. И не ставится целью.
У джавы в основе идеологии минимализм, чистый ООП, уход в абстракции и подход «это не нужно» (язык родился из идеи «возьмём C++ и выкинем всё лишнее»). Шарп — полная противоположность: давайте напихаем всего что можно, побольше, намажем сахаром и сверху положим вишенку (язык родился из идеи «возьмём Java и вернём напрасно выкинутое»).

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

Для джавы обратная совместимость — это святое. Майкрософт же время от времени позволяет себе ломать совместимость во имя развития (впрочем, от авторов шарпа тоже временами слышно: «Эх, жаль, что сделали так, теперь уже не изменить»). Джава старше, поэтому наследия царского режима больше, поэтому проблем больше и развивать сложнее.

в Java какая-то своя специфика, что ни лямбды, ни атрибуты, ни Linq просто не нужны

А в других мейнстримовых языках есть все перечисленные вами фичи? В JS замыкания есть, но синтаксис длинный; атрибутов нет, да и сами классы ещё не ввели; сторонние реализации LINQ есть, но из-за первого пункта выразительность хромает. В PHP замыкания работают через пень-колоду; атрибуты парсят как текст в комментариях, если вдруг понадобится; LINQ в такой же ситуации, как в JS. В C++ такие же тормоза с новыми фичами, как в джаве. Вот питон и руби ворвались в мейнстрим как раз на подобных фичах в частности и выразительности в общем, это да.

Это когда программируешь на локомотивах прогресса, кажется, что всё вокруг изменилось, расцвела функциональщина, асинхронность поросла сахаром и прочее. Но остальной мир-то не так торопится.
По крайней мере по лямбдам, Java оказалась замыкающей из всех мэйнстримных языков. Кивать на плюсы здесь не получится — я писал на них код с лямбдами еще в 2009-м (когда работал над/в VS 2010), а в финальном стандарте они появились три года назад.
Спасибо за перевод!
но все-таки на мой взгляд перевод Map = Ассоциативный массив не очень удачен
не убедили!
In Smalltalk, Objective-C, .NET,[7] Python, and REALbasic they are called dictionaries; in Perl and Ruby they are called hashes; in C++, Java, Go, Clojure, Scala, OCaml, Haskell they are called maps
(they — Associative array)

В языке Java ассоциативный массив именуется картой(map)


То есть, правильно (хоть и тоже режет глаз :( ) было бы написать «карта».

«Карта» это перевод по промту. Правильных переводов два: «ассоциативный массив» и «отображение», но они оба слишком длинные, поэтому я бы уже официально пополнил русский язык заимствованным словом «мапа».
Мапа, дикта, хаша, словаря…
Преимущество английского языка в том, что там слова очень лихо сокращаются. Map же тоже от mapping пошло, а существительного такого не было, только глагол to map. Поэтому в английском dictionary -> dict это нормально, а у нас словарь -> слов? — и получается хрень.

Хеш-таблицы — это множество, пересекающееся с мапами. Есть хеш-множества, не являющиеся мапами, и есть мапы на основе деревьев, не являющиеся хеш-таблицами.
в принципе не только словом «мапа», а и однокоренными — «маппинг»/«маппирование»
«Ассоциативный массив» — это все-таки associative array, а map — это именно «отображение».
Мы создаем ссылку на конструктор с помощью Person::new. Компилятор автоматически выбирает подходящий конструктор, сигнатура которого совпадает с сигнатурой PersonFactory.create.

Тут важно упомянуть, что работать такая конструкция будет только с функциональными интерфейсами. В противном случае получим Compilation error.
Multiline string literals так и не добавили?
а промежуточные операции возвращают тот же поток

Посмотрите Stream javadoc для filter,map, mapToInt, flatMap, и прочих промежуточных операций:
@return the new stream


А также в коде: return new StatelessOp<P_OUT, R>(this, ...
Те возвращается обертка над текущим потоком

Sign up to leave a comment.

Articles