Comments 96
Иногда бывает так, что необходимо расширить существующий класс без изменения его внутреннего содержимого. То есть уже после создания класса мы дополняем его другими классами.
Возможно Вы выбрали не удачный пример чтобы донести мысль, потому что появление такой необходимости и предложенное Вами решение говорит о плохом дизайне кода. Как минимум, сериализация не должна быть частью поведения класса «точка», как максимум «The Open Closed Principle» диктует другой подход к реализации ожидаемого поведения.
Но что, если вызов данного метода скрыт, а мы хотим провести тестирование в однопоточной среде? Или что, если мы хотим поменять реализацию класса, убрав/заменив CompletableFuture. К сожалению, в Java мы бессильны и должны поменять API метода.
Это «мы» бессильный, если строим код таким образом, что он не позволяет отделить бизнес логику от интеграционной.
Но что, если вызов данного метода скрыт, а мы хотим провести тестирование в однопоточной среде? Или что, если мы хотим поменять реализацию класса, убрав/заменив CompletableFuture. К сожалению, в Java мы бессильны и должны поменять API метода.
Так тут не java виновата, а вы, что написали такой метод, который знает (зачем?) контекст вызова и неудобен при вызове в другом контексте.
Во-вторых, вы же согласны, что возращать контейнеры типов можно и это нормально? Тот же Optional.
Вы слишком цепляетесь к конкретному небольшому примеру, но не видите сути.
А зачем вы приводите в пример свой идеи код, который написан не правильно с точки зрения дизайна?
Ну или можете заменить CompletableFuture на Optional, если вы совсем придирчивы. Можем даже придумать требование: возвращение empty, если сложение привело к битовому переполнению.
Суть от этого не меняется, но пример усложняется.
Мне показалось очевидным странность сложения двух чисел в отдельном потоке.
Вы обращали внимание, как выглядит код, позволяющий использовать стримы в Java коде? Он позволяет вам обстрагировать бизнес логику «сложения двух чисел» от интеграционного кода, который метод с этой логикой вызывает.
И я вообще не вижу причин эти два кода мешать в один. Иначе это приведет к программистскому бессилию.
Но что, если вызов данного метода скрыт, а мы хотим провести тестирование в однопоточной среде? Или что, если мы хотим поменять реализацию класса, убрав/заменив CompletableFuture. К сожалению, в Java мы бессильны
Ну так, а что вы ответите про Optional или асинхронные вычисления??? :)
Не понял вашу мысль (
Например существует метод Math.max(), это хороший метод. Могу ли я его использовать в «асинхронных» вычислениях? Могу. Нужно ли методу в контракте иметь CompletableFuture или Optional? Нет, не нужно.
То есть, если я получаю optional с результатом запроса из базы, то мой код плох и надо было в случае отсутствия информации возвращать null?
И вы со временем хотите поменять что? реализацию spring data?
Откройте почти любую документацию и вы увидите там игрушечные примеры. Так же и здесь. Просто вы не разобрались с материалом. А доказывать, что в Java нет kind'ов мне надоело. :)
Вы это тут пишите, народ потом читает и начинает перешептываться. И в итоге мы имеем
В последнее время я часто слышу о том, что Java стала устаревшим языком, на котором сложно строить большие поддерживаемые приложения
иногда думаешь: “как бы хорошо это решилось вот этой штукой из другого языка”
А я часто, читая чужой код на Java, думаю: «как же хорошо, что это не Scala и тут я не буду ломать голову из какого implicit что-то прилетело и откуда взялся метод, которого не должно быть». И я не против Scala, если что. Это очень крутой язык. Просто он со своей философией и для другого. И вот поэтому я против того, чтобы все языки в конце концов превратились в один. Если на Котлине/Scala все круче, так может просто не использовать Java?
На Kotlin последний пример можно сделать примерно так:
interface Calculator<T> {
fun eval(x: Int, y: Int): T
}
object FutureCalculator : Calculator<CompletableFuture<Int>> {
override fun eval(x: Int, y: Int) = CompletableFuture.supplyAsync { x + y }
}
object OptionalCalculator : Calculator<Optional<Int>> {
override fun eval(x: Int, y: Int) = Optional.of(x + y)
}
fun <T> Calculator<T>.useCalculator(y: Int) = eval(1, y)
fun main() {
with (FutureCalculator) {
println(useCalculator(2))
}
with (OptionalCalculator) {
println(useCalculator(2))
}
}
Здесь тоже задаём разный контекст, но только явно, и не ломаем голову откуда он взялся.
Пример с визитором неудачный, так как для визиторов важен динамический диспатч, а методы-расширения дают статический.
Пожалуйста.
Есть ещё более интересные примеры с алгебрами.
Вы начали статью с
В последнее время я часто слышу о том, что Java стала устаревшим языком, на котором сложно строить большие поддерживаемые приложения
И правы лишь в одном — если писать код так, как описано в ваших примерах и как в примере уважаемого Beholder, то приложение далеко не поплывет.
В этом и была моя претензия к вашим примерам — вы приводите плохой код на java и говорите, что это ваша боль. Я вижу, что причина вашей боли не в java.
Методы-расширения — это совсем не расширение интерфейса. С самим интерфейсом при этом ничего не случается и семантика его не меняется. Методы-расширения может написать программист-"клиент" для своего удобства. Это просто более выразительный способ вызова внешних методов. Вместо этого вы могли бы написать утильный класс
public class CalcUtil {
public static <T> T useCalculator(Calculator<T> calculator, int y) {
return calculator.eval(1, y);
}
}
и "счастливо" его использовать. println(CalcUtil.useCalculator(calculator, 2));
Но ведь способ выше выглядит короче, к тому же есть возможность использовать неявный this.
В последнее время я часто слышу о том, что Java стала устаревшим языком, на котором сложно строить большие поддерживаемые приложения. В целом, я не согласен с этой точкой зрения. На мой взгляд, язык все еще подходит для написания быстрых и хорошо организованных приложений.
Так все-таки, java, по вашему мнению, еще подходит для написания больших поддерживаемых приложений или нет?
Конечно! Можно писать такие приложения и на С. Посмотрите на JVM. :)
У меня есть знакомые, которые после работы на C/Python вообще не понимают зачем нужно ООП. Ведь можно и на основе модулей и без классов строить поддерживаемые приложения. Классы только код тормозят и ещё по памяти дорогие.
Вопрос не в можно/нельзя, а на сколько просто решается та или иная задача. Например, я бы не взял с собой Java при написании приложения на акторах. Ведь пользоваться Scala с Akka гораздо более приятно.
Ведь пользоваться Scala с Akka гораздо более приятно.
К чему эти полумеры? Берите Эрланг.
У меня есть знакомые, которые после работы на C/Python вообще не понимают зачем нужно ООПМоя дочь тоже не знает, зачем нужен ООП, ей 4 года и она любит пони. Программист должен знать зачем ООП нужен. Иначе он лишает себя возможности использовать его тогда, когда нужно.
Ведь можно и на основе модулей и без классов строить поддерживаемые приложения.Что значит без классов? Может вы имеете ввиду без ООП? Без ООП можно, но стоимость поддержки и модификации будет дороже.
Классы только код тормозят и ещё по памяти дорогие.Писать плохой код всегда дорого. Что вы делаете, чтобы ваш код из-за классов тормозил?
Классы только код тормозят и ещё по памяти дорогие.
Писать плохой код всегда дорого.
Эмммм… В С вообще нет классов и для многих задач это является оптимальным решением. Не очень понял как это связано с плохим кодом. Вообще, складывается впечатление, что вы называете плохим кодом все в чем не разобрались. Помимо ООП есть ещё много хороших практик, которые могут упростить поддержку.
что классы тормозят код совершенно абсурдно
Почему абсурдно? Создайте переменную типа int и класс с одним полем типа int. И посмотрите сколько занимает в памяти оба варианта. Теперь представьте, что у вас коллекция на 100 млн таких объектов. Ну и еще на работу со структурой класса потребуется доп. процессорное время. Да, очень часто этим пренебрегают ради удобства, которое дает ООП и классы, но ультимативно говорить о том, что разницы нет — это странно.
Вы либо не понимаете зачем ООП нужен либо покупаете красные машины, потому что «da red goez fasta”
Так говорят люди, у которых ООП головного мозга.
Создайте переменную типа int и класс с одним полем типа int. И посмотрите сколько занимает в памяти оба варианта. Теперь представьте, что у вас коллекция на 100 млн таких объектов.
А вы же понимаете, что если программист, например, выбирает вторую реализацию, то это ему необходимо или у него по умолчанию ООП головного мозга? Т.е вы хотите сказать, что выбор реализации зависит исключительно от желания микрооптимизаций в ущерб всему остальному?
Т.е вы хотите сказать, что выбор реализации зависит исключительно от желания микрооптимизаций в ущерб всему остальному?
Я хочу сказать, что для разных задач существуют разные подходы. Помимо ООП есть и ФП и процедурное программирование. Желание везде и всюду видеть объекты и пытаться их применить — это и есть ООП головного мозга.
Но не нужно пытаться принимать желаемое за действительное — если программист пишет код таким образом, что «классы его тормозят», виноват ли ООП? Я считаю, что нет.
Но не нужно пытаться принимать желаемое за действительное — если программист пишет код таким образом, что «классы его тормозят»
Какое еще желаемое? Я не писал про тормоза. Я писал про оверхед.
виноват ли ООП
ООП не виноват, виновата его реализация. Чужеродные для железа абстракции производительности не прибавляют. Еще виновата голова, которая сует абстракции куда не нужно, только и всего.
Я возможно вас удивлю, но существует не только Энтерпрайз разработка. Например, в разработке под МК постоянные аллокации памяти часто нежелательны. Поэтому там редко делается выбор в сторону C++.
Что значит без классов? Может вы имеете ввиду без ООП?
ООП может быть и без классов.
ООП не про классы, а про объекты :)
Модули в Python
И изначально я спрашивал, чтобы уточнить что имел ввиду автор в конкретном комментарии статьи о java, а не чтобы углубляться в разнообразия языков, где фраза «ООП может быть и без классов» местами имеет право на жизнь, когда как в java нет.
А да, ещё же старый добрый js с замыканиями вместо классов. :)
ООП может быть и без классов.выглядит как вброс, могущий породить замешательство и привести к не нужному обсуждению?
что в статье про java фраза вида
Вы прекрасно поняли о чем речь и обсуждение выше про Питон это доказывает)
- Странно, что автор сравнивает Котлиновские корутины и Джавовые фьючи.
Корутины не входят в ядро Котлина. Это отдельная либа, которая имплементится в Гредл-файле. В противовес Корутинам автор мог бы использовать Rx в Джаве;
Кроме того, согласно этой статье корутины не о многопоточности, а о конкурентности. Улавливаете разницу? - Большая часть проблем решается использованием библиотек. Лямбды например уже есть в Джаве, так что уже их за преимущество/недостаток можно не считать. Про описание классов — можно использовать
AutoValue-Parcel
илиAutoValue
.
Если не брать либу clojure.core.async с горутинами, то в стдлибе асинк выглядит как-то так
(def getResultFromFirstService (promise))
(defn getResultFromSecondService [v] (println v (+ 100 v)))
(future (getResultFromSecondService @getResultFromFirstService))
(deliver getResultFromFirstService 42)
Попробуем сначала решить задачу на Java
private static CompletableFuture<Optional<String>> calcResultOfTwoServices ( Supplier<Optional<Integer>> getResultFromFirstService, Function<Integer, Optional<Integer>> getResultFromSecondService ) { return CompletableFuture .supplyAsync(getResultFromFirstService) .thenApplyAsync(firstResultOptional -> firstResultOptional.flatMap(first -> getResultFromSecondService.apply(first).map(second -> first + " " + second ) ) ); }
Зачем использовать thenApplyAsync, если мы не меняем Executor? В вашем коде вся логика будет выполняться в ForkJoinPool.commonPool(). Можно тогда и простым thenApply обойтись. К тому же, не будет теряться время на переключение контекста между окончанием работы первого сервиса и началом работы второго. Хотя, возможно, в реализации CompletableFuture есть оптимизации на этот счёт.
А как же Kotlin? Давайте попробуем сделать что-то подобное на корутинах.
val result = async { withContext(Dispatchers.Default) { getResultFromFirstService() }?.let { first -> withContext(Dispatchers.Default) { getResultFromSecondService(first) }?.let { second -> "$first $second" } } }
Тут вы, как и в примере на Java, хотите с помощью функции (не ключевое слово, кстати, как и async) withContext() насильно заставить второй сервис выполняться в отличном от исходного диспетчере, а значит и, потенциально, в другом потоке. Зачем? Ну отправит она эту задачу на выполнение в другой поток (возможно), всё равно же он прерывается до окончания выполнения.
В итоге можно прийти к более простому примеру:
val result = GlobalScope.async {
getResultFromFirstService()?.let { first ->
getResultFromSecondService(first)?.let { second ->
"$first $second"
}
}
}
Конечно, оговорюсь, что из вашего примера непонятно, на каком СoroutineScope вызывается async, но тогда этот пример тем более неудачный, так как вы про это нигде не говорили.
Не то, чтобы я придирался к мелочам, но эти мелочи мало того, что не дают выигрыша в производительности (а даже, скорее всего, наоборот), но и ещё ухудшают чтение кода.
Про Scala не скажу, так как не имел опыта работы с ней.
Более того, в java уже можно потрогать project loom, и с ним код сильно упростится и распрямится.
Конечно, вы правы в том, что использование thenApplyAsync
без специфичного Executor
кажется избыточным. Однако, стоит понимать, что демонстрация асинхронных возможностей Java с помощью CompletableFuture в данном случае была направлена на показ возможностей языка, а не на идеальную оптимизацию производительности. Не каждый пример должен быть прямым указанием к действию, иногда это просто демонстрация возможностей.
Что касается вашего комментария о Kotlin и использовании withContext
— да, конечно, можно всё сделать проще и понятнее, если использовать GlobalScope.async
. Тем не менее, использование различных контекстов исполнения служит для показа гибкости корутин. Мне показалось это очевидным, тк заголовок называется Цепочка многопоточных вычислений, что предполагает понимания причин происходящего.
Расширение существующих классов -> @ExtensionMethod в Lombok'е и @Extensions в Manifold'е
Остальное - ждём от VAVR'а и новых версий Java...
Чего мне не хватает в Java после работы с Kotlin/Scala