Класс для возврата значения из лямбды
От: vsb Казахстан  
Дата: 13.09.22 20:16
Оценка:
Нужно вернуть значение из лямбды. Т.к. Java в 2022 году недостаточно для этого продвинута, нужно использовать что-то вроде указателя.

Можно использовать массив из одного элемента:

        String[] stringRef1 = new String[1];
        ((Runnable) () -> {
            stringRef1[0] = "result";
        }).run();
        System.out.println(stringRef1[0]);


Но это кажется не очень красивым.

Можно написать специальный класс:

        class ObjectReference<T> {
            T value;
        }

        var stringRef2 = new ObjectReference<String>();
        ((Runnable) () -> {
            stringRef2.value = "result";
        }).run();
        System.out.println(stringRef2.value);


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

Есть ли где-нибудь в JDK уместный в данном случае класс, чтобы не писать свой?
Re: Класс для возврата значения из лямбды
От: GarryIV  
Дата: 13.09.22 21:13
Оценка: :)
Здравствуйте, vsb, Вы писали:

vsb>Нужно вернуть значение из лямбды.

Ну верни. Зачем Runnable тогда тебе когда есть куча всего https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/function/package-summary.html?
И своего можно наделать в добавок.
Из Runnable понятно нельзя. Зачем тебе оно?
В качестве костыля что хочешь то и используй, но лучше таки понять как без костылей жить.
WBR, Igor Evgrafov
Re[2]: Класс для возврата значения из лямбды
От: vsb Казахстан  
Дата: 13.09.22 21:33
Оценка:
Здравствуйте, GarryIV, Вы писали:

vsb>>Нужно вернуть значение из лямбды.

GIV>Ну верни. Зачем Runnable тогда тебе когда есть куча всего https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/util/function/package-summary.html?
GIV>И своего можно наделать в добавок.
GIV>Из Runnable понятно нельзя. Зачем тебе оно?
GIV>В качестве костыля что хочешь то и используй, но лучше таки понять как без костылей жить.

Мне надо несколько вернуть. record-ы на ровном месте городить не хочу, это точно некрасиво.
Re[3]: Класс для возврата значения из лямбды
От: GarryIV  
Дата: 13.09.22 21:58
Оценка: +1 -1 :)
Здравствуйте, vsb, Вы писали:

GIV>>В качестве костыля что хочешь то и используй, но лучше таки понять как без костылей жить.

vsb>Мне надо несколько вернуть.
Так бы и сказал что тебе туплы нужны (если я правильно понял что несколько != List<T>). Тут лямбды вообще не причем.
https://www.baeldung.com/java-method-return-multiple-values
https://www.baeldung.com/java-tuples

vsb>record-ы на ровном месте городить не хочу, это точно некрасиво.

То есть чистая функция не красиво а вот функция с сайд эффектом на ровном месте красиво?
Ну ну.
WBR, Igor Evgrafov
Re: Класс для возврата значения из лямбды
От: · Великобритания  
Дата: 13.09.22 22:25
Оценка: +1
Здравствуйте, vsb, Вы писали:

vsb>Есть ли где-нибудь в JDK уместный в данном случае класс, чтобы не писать свой?

Не очень ясно как оно должно работать. Ведь никакой гарантии, что метод лямбды будет вызван, притом ровно один раз и из того же треда. Поэтому всё равно надо как-то инициализировать значение и давать понять, что оно может несколько раз изменяться.
Есть такой трюк с анонимным типом и var:
        var thing = new Runnable() {
            int r = -1;
            @Override
            public void run() {
                r = 42;
            }
        };
        thing.run();
        System.out.println(thing.r);
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 13.09.2022 22:26 · . Предыдущая версия .
Re[4]: Класс для возврата значения из лямбды
От: vsb Казахстан  
Дата: 13.09.22 22:32
Оценка:
Здравствуйте, GarryIV, Вы писали:

vsb>>record-ы на ровном месте городить не хочу, это точно некрасиво.

GIV>То есть чистая функция не красиво а вот функция с сайд эффектом на ровном месте красиво?
GIV>Ну ну.

Если бы в жаве были туплы, которым не надо давать имя, я бы их использовал.

Заводить record некрасиво. Да, я знаю, что его даже можно внутри функции объявить. Всё равно некрасиво. У него не может быть читаемого имени. Ещё и куча писанины.
Re[2]: Класс для возврата значения из лямбды
От: vsb Казахстан  
Дата: 13.09.22 22:35
Оценка:
Здравствуйте, ·, Вы писали:

·>Не очень ясно как оно должно работать. Ведь никакой гарантии, что метод лямбды будет вызван, притом ровно один раз и из того же треда.


Почему нет гарантий? Есть гарантии.

Вот пример кода, как я сейчас пишу:
    var employeesRef = new ObjectReference<List<Employee>>();
    var appointmentsRef = new ObjectReference<List<Appointment>>();
    transactionService.executeReadOnly(() -> {
      employeesRef.set(employeeRepository.queryAll());
      appointmentsRef.set(appointmentRepository.queryAll());
    });

    var employees = employeesRef.get();
    var appointments = appointmentsRef.get();


метод transactionService.executeReadOnly выполняет переданный коллбек в текущем треде и делает это ровно один раз.
Отредактировано 13.09.2022 22:36 vsb . Предыдущая версия . Еще …
Отредактировано 13.09.2022 22:35 vsb . Предыдущая версия .
Re[3]: Класс для возврата значения из лямбды
От: · Великобритания  
Дата: 13.09.22 23:05
Оценка: 23 (2)
Здравствуйте, vsb, Вы писали:

vsb> ·>Не очень ясно как оно должно работать. Ведь никакой гарантии, что метод лямбды будет вызван, притом ровно один раз и из того же треда.

vsb> Почему нет гарантий? Есть гарантии.
Нет гарантий на уровне языка.

vsb> метод transactionService.executeReadOnly выполняет переданный коллбек в текущем треде и делает это ровно один раз.

С точки зрения JVM — завтра в transactionService подсунут другую реализацию и твой код посыпется.

Может как-то так можно:
private <T extends Runnable> T executeReadOnly(final T r)
{
    // Do whatever...
    return r;
}
...
...
    var result = transactionService.executeReadOnly(new Runnable()
    {
        List<Employee> employees;
        List<Appointment> appointments;
        public void run() {
            employees = employeeRepository.queryAll();
            appointments = ppointmentRepository.queryAll();
        }
    });
    System.out.println(result.appointments);
    System.out.println(result.employees);
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[4]: Класс для возврата значения из лямбды
От: vsb Казахстан  
Дата: 13.09.22 23:11
Оценка:
Здравствуйте, ·, Вы писали:

vsb>> метод transactionService.executeReadOnly выполняет переданный коллбек в текущем треде и делает это ровно один раз.

·>С точки зрения JVM — завтра в transactionService подсунут другую реализацию и твой код посыпется.

Ну так можно про многое сказать. Никто там другую реализацию подсовывать не будет.

·>Может как-то так можно:

·>
·>private <T extends Runnable> T executeReadOnly(final T r)
·>{
·>    // Do whatever...
·>    return r;
·>}
·>...
·>...
·>    var result = transactionService.executeReadOnly(new Runnable()
·>    {
·>        List<Employee> employees;
·>        List<Appointment> appointments;
·>        public void run() {
·>            employees = employeeRepository.queryAll();
·>            appointments = ppointmentRepository.queryAll();
·>        }
·>    });
·>    System.out.println(result.appointments);
·>    System.out.println(result.employees);

·>


Спасибо, выходит что-то вроде анонимного типа, прикольно.

Вот ещё вариант:
    public static void main(String[] args) {
        var x = exec(() -> new Object() {
            final String a = "a";
            final int b = 2;
        });
        System.out.println(x.a);
        System.out.println(x.b);
    }

    static <T> T exec(Supplier<T> supplier) {
        return supplier.get();
    }


Получается, что до того, как в язык ввели var, такой финт был невозможен?
Re[5]: Класс для возврата значения из лямбды
От: · Великобритания  
Дата: 13.09.22 23:25
Оценка:
Здравствуйте, vsb, Вы писали:

vsb> Спасибо, выходит что-то вроде анонимного типа, прикольно.

Не что-то вроде, а он и есть.

vsb> Вот ещё вариант:

vsb>
vsb>         var x = exec(() -> new Object() {
vsb>             final String a = "a";
vsb>             final int b = 2;
vsb>         });
vsb>



vsb> Получается, что до того, как в язык ввели var, такой финт был невозможен?

Да. Ибо явно приходилось указывать имя типа (в случае выше был бы Object, что никак не помогает). А у анонимного типа имени нет по определению.
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Отредактировано 13.09.2022 23:27 · . Предыдущая версия .
Re[5]: Класс для возврата значения из лямбды
От: · Великобритания  
Дата: 13.09.22 23:43
Оценка:
Здравствуйте, vsb, Вы писали:

vsb> Получается, что до того, как в язык ввели var, такой финт был невозможен?

Вот так можно было, похоже:
System.out.println(new Object()
{
    final String a = "a";
}.a);

Но таким образом удастся извлечь только одно поле из анонимного типа, что делает затею бессмысленной.
avalon/3.0.0
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[5]: Класс для возврата значения из лямбды
От: GarryIV  
Дата: 14.09.22 04:55
Оценка: -1 :)
Здравствуйте, vsb, Вы писали:

vsb>Заводить record некрасиво. Да, я знаю, что его даже можно внутри функции объявить. Всё равно некрасиво. У него не может быть читаемого имени. Ещё и куча писанины.


Раз у этой штуки нет имени то это code smell.
Количество кода обвязки такого вызова будет +- одинаковым, как не изгаляйся.
WBR, Igor Evgrafov
Re: Класс для возврата значения из лямбды
От: elmal  
Дата: 09.01.23 08:02
Оценка:
[b]Здравствуйте, vsb, Вы писали:

vsb>Есть ли где-нибудь в JDK уместный в данном случае класс, чтобы не писать свой?

Вообще не понял проблему. Вообще, если нужен указатель, я обычно использую AtomicReference и не парюсь оверхером, он делает что мне надо, а что он атомик — мне чаще всего пофиг.
Относительно возврата из лямбды — а какую задачу решаешь?
System.out.println(Stream.of("").map( any -> "result").findFirst().get());

any -> "result" — это вполне себе лямбда, разве нет? Еще в восьмерке это было.

Или ты что с асинхронщиной что мутишь? Тогда тебе нужен не Runnable, который ни черта не возвращает по определению, а Callable, который возвращает, и если выполняешь асинхронно то будет возвращено CompletableFuture, которое можно уже выстраивать в цепочку. Ну и всякие RxJava давно есть, если фичей поболее нужно.
Re[4]: Класс для возврата значения из лямбды
От: m2user  
Дата: 10.01.23 00:02
Оценка:
vsb>> ·>Не очень ясно как оно должно работать. Ведь никакой гарантии, что метод лямбды будет вызван, притом ровно один раз и из того же треда.
vsb>> Почему нет гарантий? Есть гарантии.
·>Нет гарантий на уровне языка.

vsb>> метод transactionService.executeReadOnly выполняет переданный коллбек в текущем треде и делает это ровно один раз.

·>С точки зрения JVM — завтра в transactionService подсунут другую реализацию и твой код посыпется.

·>Может как-то так можно:


Вот тут пишут, что проблема в том, что local variables на стеке, а вот если бы это были instance variables (в heap) c volatile keyword, то проблем у компилятора нет.
https://www.baeldung.com/java-lambda-effectively-final-local-variables#instance-variables

Отсюда вопрос, что мешало решить проблему на уровне компилятора (генерировать временный класс, если присутствуют captured variables)?
Всё равно программист в итоге то же самое делает.
Re[5]: Класс для возврата значения из лямбды
От: vsb Казахстан  
Дата: 10.01.23 00:15
Оценка:
Здравствуйте, m2user, Вы писали:

M>Отсюда вопрос, что мешало решить проблему на уровне компилятора (генерировать временный класс, если присутствуют captured variables)?

M>Всё равно программист в итоге то же самое делает.

Ничего не мешало, просто решили не делать и всё. Почему решили — не знаю. Вероятно это показалось слишком большим изменением, которое можно и позже добавить. Может ещё добавят.

PS там и так генерируется временный класс.
Отредактировано 10.01.2023 0:16 vsb . Предыдущая версия . Еще …
Отредактировано 10.01.2023 0:16 vsb . Предыдущая версия .
Re[2]: Класс для возврата значения из лямбды
От: vsb Казахстан  
Дата: 10.01.23 00:21
Оценка:
Здравствуйте, elmal, Вы писали:

vsb>>Есть ли где-нибудь в JDK уместный в данном случае класс, чтобы не писать свой?

E>Вообще не понял проблему.

Проблема простая — вернуть из лябмды более 1 значения.

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


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

E>Относительно возврата из лямбды — а какую задачу решаешь?


Имеется метод для работы с транзакциями. Нужно из этого метода вернуть несколько значений.

https://rsdn.org/forum/java/8360211
Автор: vsb
Дата: 14.09.22


В итоге объявил прямо в методе record с нужным числом полей и возвращаю его, как одно значение, агрегирующее остальные. Думаю, это самый подходящий вариант.
Re[3]: Класс для возврата значения из лямбды
От: elmal  
Дата: 10.01.23 04:49
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>Проблема простая — вернуть из лябмды более 1 значения.

Ну вообще, если нужно типизированно и анонимно, без создания нового класса, в других языках используются Tuple. К сожалению в Java действительно нормальной поддержки всего этого нет и приходится извращаться. В простейшейм случае можно конечно использовать Pair<T1, T2> — но даже для этого нет нормальной деструктуризации и пользоваться геморройно. Для трех значений можно Triple<T1, T2, T3> — в принципе при желании можно продолжить, но лучше без необходимости так не делать и возвращать явный класс с явными полями, а не абстрактные first second third и тем более далее, ибо даже в языках которые это нормально все поддерживают и имеют синтаксис для деструктуризации нужно очень аккуратно такое использовать. Потому Java way- это возвращать явный класс.

vsb>Имеется метод для работы с транзакциями. Нужно из этого метода вернуть несколько значений.

Ага, а вот тут уже проще. Ты просто декорируешь функцию по сути, у тебя на входя декоратора транзакционности Function<T, R>, на выходе будет тоже Function<T, R>. В твоем случае у тебя даже не фунция на входе, в Supplier<R>, так как взода у тебя нет,только результат.
То есть код будет:
public class Test {
    public static <T> T executeReadOnly(Supplier<T> supplier) {
        //do something before
        var result = supplier.get();
        //do something after
        return result
    }

    public static <T> Supplier<T> wrapExecuteReadOnly(Supplier<T> supplier) {
        return () -> {
            //do something before
            var result = supplier.get();
            //do something after
            return result;
        };
    }

    public static void main() {
        var employesToAppoitments = executeReadOnly( () -> new Pair<>("resEmplouees", "retAppoitments"));
        //или так - здесь декорируемую функцию можно где сохранить и уже повторно использовать, а потом дернуть get.
        var employesToAppoitments1 = wrapExecuteReadOnly( () -> new Pair<>("resEmplouees", "retAppoitments")).get();
    }
}

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

PS — блин, совсем синтаксис Java забыл, так сейчас ужасают эти все new, точки с запятой в конце.
Re[5]: Класс для возврата значения из лямбды
От: · Великобритания  
Дата: 10.01.23 12:37
Оценка:
Здравствуйте, m2user, Вы писали:

M>https://www.baeldung.com/java-lambda-effectively-final-local-variables#instance-variables

volatile вообще не в тему.

M>Отсюда вопрос, что мешало решить проблему на уровне компилятора (генерировать временный класс, если присутствуют captured variables)?

M>Всё равно программист в итоге то же самое делает.
"Обычное" решение не гарантирует коррекности кода. Вот такое:
int val;
f(1, x -> val = x);
assert val == 43;
...
private void f(int p, final Consumer<Integer> c) {
    c.accept(42 + p);
}

Всё типа красиво. Но... теперь пришёл другой программист и поменял (не забывай, что метод может быть определён в совершенно другом месте, в какой-то либе внешней даже):
private void f(int p, final Consumer<Integer> c) {
    if(x > 1) {
        c.accept(42 + p);
        c.accept(666 + p);
    }
}

Теперь нам надо как-то изобретать правила для неинициализированных значений на стеке и правила final initialized once (т.е. если у нас объявлен final int val)... А ещё и c.accept() может выполняться из другого треда и проблемы с happens-before...
Т.е. на пустом месте усложняем ЯП, делаем кучу особых случаев, неявных дейсвтий и/или вносим лишнюю пессимизацию неявной расстановкой volatile или синхронизации например.

В общем, я считаю, что в java сделали всё правильно — максимально осторожно и явно.
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Re[5]: Класс для возврата значения из лямбды
От: gyraboo  
Дата: 10.01.23 12:46
Оценка: +1
Здравствуйте, vsb, Вы писали:

vsb>>>record-ы на ровном месте городить не хочу, это точно некрасиво.

GIV>>То есть чистая функция не красиво а вот функция с сайд эффектом на ровном месте красиво?
GIV>>Ну ну.

vsb>Если бы в жаве были туплы, которым не надо давать имя, я бы их использовал.


Туплы — это разве не антипаттерн в бизнесовом исходном коде? Особенно когда начинаются триплы и прочие n-туплы, код становится нечитаемой кашей, потому что туплы не содержат семантики, одно только нагромождение этих элементов с бессмысленными названиями. Не станет ли код понятнее, если использовать семантический паттерн Parameter Object?
Re[3]: Класс для возврата значения из лямбды
От: · Великобритания  
Дата: 10.01.23 12:49
Оценка:
Здравствуйте, vsb, Вы писали:

vsb>В итоге объявил прямо в методе record с нужным числом полей и возвращаю его, как одно значение, агрегирующее остальные. Думаю, это самый подходящий вариант.

А чем анонимные типы-то не подошли? Вроде же неплохо работает. Что-то вроде:

var result = executeWithTxn(txn -> new Object(){
    final String val1 = "val1 in " + txn;
    final String val2 = "val2 in " + txn;
});
System.out.println(result.val1);
System.out.println(result.val2);

...

private <T> T executeWithTxn(final Function<String, T> action) {
    return action.apply("txnId");
}
но это не зря, хотя, может быть, невзначай
гÅрмония мира не знает границ — сейчас мы будем пить чай
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.