Стас Пасинков
26 уровень
Киев

Популярно о лямбда-выражениях в Java. С примерами и задачами. Часть 1

Статья из группы Java Developer
участников
Для кого предназначена эта статья?
  • для тех, кто считает, что уже неплохо знает Java Core, но понятия не имеет о лямбда-выражениях в Java. Или, возможно, что-то уже слышал про лямбды, но без подробностей.
  • для тех, у кого есть какое-то понимание лямбда-выражений, но использовать их до сих пор боязно и непривычно.
Если вы не входите в одну из этих категорий, вам эта статья вполне может показаться скучной, некорректной и вообще «не айс». В таком случае либо смело проходите мимо, либо, если вы хорошо разбираетесь в теме, предложите в комментариях, как я мог бы улучшить или дополнить статью. Материал не претендует на какую-либо академическую ценность, и уж тем-более на новизну. Скорее, наоборот: в ней я попытаюсь описать сложные (для кого-то) вещи как можно проще. На написание меня подвигла просьба объяснить stream api. Я подумал, и решил, что без понимания лямбда-выражений, часть моих примеров о «стримах» будет непонятна. Так что для начала — лямбды. Популярно о лямбда-выражениях в Java. С примерами и задачами. Часть 1 - 1Какие знания требуются для понимания этой статьи:
  1. Понимание объектно-ориентированного программирования (далее ООП), а именно:
    • знание что такое классы, объекты, какая между ними разница;
    • знание что такое интерфейсы, чем они отличаются от классов, какая между ними (интерфейсами и классами) связь;
    • знание что такое метод, как его вызвать, что такое абстрактный метод (или метод без реализации), что такое параметры/аргументы метода, как их туда передавать;
    • модификаторы доступа, статические методы/переменные, финальные методы/переменные;
    • наследование (классов, интерфейсов, множественное наследование интерфейсов).
  2. Знание Java Core: обобщенные типы (generics), коллекции (списки), потоки (threads).
Ну что ж, приступим.

Немного истории

Лямбда-выражения пришли в Java из функционального программирования, а туда — из математики. В середине 20го века в Америке в Принстонском университете работал некий Алонзо Чёрч, который очень любил математику и всяческие абстракции. Именно Алонзо Чёрч и придумал лямбда-исчисление, которое поначалу было набором неких абстрактных идей и никак не касалось программирования. В то же время в том же Принстонском университете работали такие математики, как Алан Тьюринг и Джон фон Нейман. Всё сложилось воедино: Чёрч придумал систему лямбда-исчислений, Тьюринг разработал свою абстрактную вычислительную машину, нынче известную под названием «машина Тьюринга». Ну а фон Нейман предложил схему архитектуры вычислительных машин, которая легла в основу современных компьютеров (и сейчас называется «архитектура фон Неймана»). В то время идеи Алонзо Чёрча не обрели столь громкую известность, как работы его коллег (за исключением сферы «чистой» математики). Тем не менее, чуть позже некто Джон МакКарти (также выпускник Принстонского университета, на момент рассказа — сотрудник Массачусетского технологического института) заинтересовался идеями Чёрча. На их основе, в 1958 году он создал первый функциональный язык программирования Lisp. А спустя 58 лет идеи функционального программирования просочились в Java под номером 8. Не прошло и 70 лет… На самом деле — не самый долгий срок применения математической идеи на практике.

Суть

Лямбда-выражение — это такая функция. Можете считать, что это обычный метод в Java, только его особенность в том, что его можно передавать в другие методы в качестве аргумента. Да, стало возможным передавать в методы не только числа, строки и котиков, но и другие методы! Когда нам это может понадобиться? Например, если мы хотим передать какой-нибудь callback. Нам нужно, чтобы тот метод, который мы вызываем, имел возможность вызвать какой-то другой метод, который мы ему передадим. То есть, чтобы у нас была возможность в каких-то случаях передавать один callback, а в других — иной. И наш метод, который бы принимал наши callback-и, — вызывал бы их. Простой пример — сортировка. Допустим, мы пишем какую-то хитрую сортировку, которая выглядит примерно вот так:
public void mySuperSort() {
    // ... тут че-то делаем
    if(compare(obj1, obj2) > 0)
    // ... и тут че-то делаем
}
Там, где if, мы вызываем метод compare(), передаем туда два объекта, которые мы сравниваем, и хотим узнать какой из этих объектов «больше». Тот, который «больше» мы поставим перед тем, который «меньше». Я написал «больше» в кавычках, потому что мы пишем универсальный метод, который будет уметь сортировать не только по возрастанию, но и по убыванию (в таком случае «больше» будет тот объект, который по сути меньше, и наоборот). Чтобы задать правило, как именно мы хотим отсортировать, нам нужно каким-то образом передать его в наш метод mySuperSort(). В таком случае мы сможем как-то «управлять» нашим методом во время его вызова. Разумеется, можно написать два отдельных метода mySuperSortAsc() и mySuperSortDesc() для сортировки по возрастанию и убыванию. Или передавать какой-то параметр внутрь метода (допустим boolean и если true, сортировать по возрастанию, а если false — по убыванию). А что, если мы хотим отсортировать не какую-то простую структуру, а например список массивов строк? Как наш метод mySuperSort() будет знать, по какому принципу сортировать эти массивы строк? По размеру? По общей длине слов? Быть может, по алфавиту, в зависимости от первой строки в массиве? А что, если нам в каких-то случаях надо отсортировать список массивов по размеру массива, а в другом случае — по суммарной длине слов в массиве? Я думаю, вы уже слышали о компараторах и о том, что в таких случаях мы просто передаем в наш метод сортировки объект компаратора, в котором мы и описываем правила, по которым мы хотим сортировать. Поскольку стандартный метод sort() реализован по тому же принципу, что и mySuperSort(), в примерах я буду использовать именно стандартный sort().
String[] array1 = {"мама", "мыла", "раму"};
String[] array2 = {"я", "очень", "люблю", "java"};
String[] array3 = {"мир", "труд", "май"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

Comparator<String[]> sortByLength = new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
};

Comparator<String[]> sortByWordsLength = new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        int length1 = 0;
        int length2 = 0;
        for (String s : o1) {
            length1 += s.length();
        }
        for (String s : o2) {
            length2 += s.length();
        }
        return length1 - length2;
    }
};

arrays.sort(sortByLength);
Результат:
  1. мама мыла раму
  2. мир труд май
  3. я очень люблю java
Тут массивы отсортированы по количеству слов в каждом массиве. Массив, где меньше слов — тот и считается «меньшим». Вот почему он стоит в начале. Тот, где слов больше считается «больше», и оказывается в конце. Если в метод sort() мы передадим другой компаратор (sortByWordsLength) — то и результат будет другой:
  1. мир труд май
  2. мама мыла раму
  3. я очень люблю java
Теперь массивы отсортированы по общему количеству букв в словах такого массива. В первом случае 10 букв, во втором 12, и в третьем 15. Если у нас используется только один компаратор, то мы можем не заводить под него отдельную переменную, а просто создать объект анонимного класса прямо в момент вызова метода sort(). Примерно так:
String[] array1 = {"мама", "мыла", "раму"};
String[] array2 = {"я", "очень", "люблю", "java"};
String[] array3 = {"мир", "труд", "май"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
Результат будет такой же, как и в первом случае. Задача 1. Переписать этот пример так, чтобы он сортировал массивы не по возрастанию количества слов в массиве, а по убыванию. Это всё мы уже знаем. Мы умеем передавать объекты в методы, мы можем передать тот или иной объект в метод в зависимости от того, что нам в данный момент надо, и внутри того метода, куда мы передаем такой объект, будет вызван тот метод, для которого мы написали реализацию. Возникает вопрос: при чём здесь вообще лямбда-выражения? При том, что лямбда — это и есть такой объект, который содержит ровно один метод. Такой себе объект-метод. Метод, запакованный в объект. Просто у них немного непривычный синтаксис (но об этом чуть позже). Давайте еще раз взглянем на эту запись
arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
Тут мы берем наш список arrays и вызываем у него метод sort(), куда передаем объект компаратора с одним единственным методом compare() (нам не важно, как он называется, ведь он — единственный в этом объекте, тут не промахнемся). Этот метод принимает два параметра, с которыми мы дальше и работаем. Если вы работаете в IntelliJ IDEA, то наверняка видели, как она вам предлагает этот код значительно сократить:
arrays.sort((o1, o2) -> o1.length - o2.length);
Вот так шесть строк превратились в одну короткую. 6 строк переписали в одну короткую. Что-то исчезло, но я гарантирую, что не исчезло ничего важного, и такой код будет работать абсолютно так же, как и при анонимном классе. Задача 2. Догадаться, как переписать решение задачи 1 через лямбды (в крайнем случае, попросите IntelliJ IDEA превратить ваш анонимный класс в лямбду).

Поговорим об интерфейсах

В принципе, интерфейс — это просто список абстрактных методов. Когда мы создаем класс и говорим, что он будет имплементировать какой-то интерфейс — мы должны в нашем классе написать реализацию тех методов, которые перечислены в интерфейсе (или, на крайний случай, не писать, но сделать класс абстрактным). Бывают интерфейсы со множеством разных методов (например List), бывают интерфейсы только с одним методом (например, тот же Comparator или Runnable). Бывают интерфейсы и вовсе без единого метода (так называемые интерфейсы-маркеры, например Serializable). Те интерфейсы, у которых только один метод, также называют функциональными интерфейсами. В Java 8 они даже помечены специальной аннотацией @FunctionalInterface. Именно интерфейсы с одним единственным методом и подходят для использования лямбда-выражениями. Как я уже говорил выше, лямбда-выражение — это метод, завернутый в объект. И когда мы передаем куда-то такой объект — мы, по сути, передаем этот один единственный метод. Получается, нам не важно, как этот метод называется. Все, что нам важно — это параметры, которые этот метод принимает, и, собственно, сам код метода. Лямбда-выражение — это, по сути. реализация функционального интерфейса. Где видим интерфейс с одним методом — значит, такой анонимный класс можем переписать через лямбду. Если в интерфейсе больше/меньше одного метода — тогда нам лямбда-выражение не подойдет, и будем использовать анонимный класс, или даже обычный. Пришло время поковырять лямбды. :)

Синтаксис

Общий синтаксис примерно такой:
(параметры) -> {тело метода}
То есть, круглые скобки, внутри их параметры метода, «стрелочка» (это два символа подряд: минус и больше), после которой тело метода в фигурных скобках, как и всегда. Параметры соответствуют тем, что указаны в интерфейсе при описании метода. Если тип переменных может быть четко определен компилятором (в нашем случае, точно известно, что мы работаем с массивами строк, потому что List — типизирован именно массивами строк), то и тип переменных String[] можно не писать.
Если не уверены, указывайте тип, а IDEA подсветит его серым, если он не нужен.
Подробнее можно почитать в туториале оракла, например. Это называется «target typing». Имена переменным можно дать какие угодно, не обязательно именно те, которые указаны в интерфейсе. Если параметров нет — тогда просто круглые скобки. Если параметр только один —просто имя переменной без круглых скобок. С параметрами разобрались, теперь про тело самого лямбда-выражения. Внутри фигурных скобок пишете код, как для обычного метода. Если у вас весь код состоит только из одной строки, можете вообще фигурных скобок не писать (как и с if-ами, и с циклами). Если же ваша лямбда что-то возвращает, но ее тело состоит из одной строки, писать return вовсе не обязательно. А вот если у вас фигурные скобки, тогда, как и в обычном методе, нужно явно писать return.

Примеры

Пример 1.
() -> {}
Самый простой вариант. И самый бессмысленный:).Так как ничего не делает. Пример 2.
() -> ""
Тоже интересный вариант. Ничего не принимает и возвращает пустую строку (return опущен за ненадобностью). То же, но с return:
() -> {
    return "";
}
Пример 3. Hello world на лямбдах
() -> System.out.println("Hello world!")
Ничего не принимает, ничего не возвращает (мы не можем поставить return перед вызовом System.out.println(), так как тип возвращаемого значения в методе println() — void), просто выводит на экран надпись. Идеально подходит для реализации интерфейса Runnable. Этот же пример более полный:
public class Main {
    public static void main(String[] args) {
        new Thread(() -> System.out.println("Hello world!")).start();
    }
}
Ну, или так:
public class Main {
    public static void main(String[] args) {
        Thread t = new Thread(() -> System.out.println("Hello world!"));
        t.start();
    }
}
Или даже можем сохранить лямбда-выражение как объект типа Runnable, а потом его уже передать в конструктор thread’а:
public class Main {
    public static void main(String[] args) {
        Runnable runnable = () -> System.out.println("Hello world!");
        Thread t = new Thread(runnable);
        t.start();
    }
}
Рассмотрим подробнее момент сохранения лямбда-выражения в переменную. Интерфейс Runnable нам говорит, что его объекты должны иметь метод public void run(). Согласно интерфейсу, метод run ничего не принимает в качестве параметров. И ничего не возвращает (void). Поэтому при такой записи будет создан объект с каким-то методом, который ничего не принимает и не возвращает. Что вполне соответствует методу run() в интерфейсе Runnable. Вот почему мы и смогли поместить это лямбда-выражение в переменную типа Runnable. Пример 4
() -> 42
Снова, ничего не принимает, а возвращает число 42. Такое лямбда-выражение можно поместить в переменную типа Callable, потому что в этом интерфейсе определен только один метод, который выглядит примерно так:
V call(),
где V — это тип возвращаемого значения (в нашем случае int). Соответственно, мы можем сохранить такое лямбда-выражение следующим образом:
Callable<Integer> c = () -> 42;
Пример 5. Лямбда в несколько строк
() -> {
    String[] helloWorld = {"Hello", "world!"};
    System.out.println(helloWorld[0]);
    System.out.println(helloWorld[1]);
}
Опять, это лямбда-выражение без параметров и тип возвращаемого значения у него void (так как отсутствует return). Пример 6
x -> x
Тут мы принимаем что-то в переменную х, и ее же и возвращаем. Обратите внимание, что если принимается только один параметр — то скобки вокруг него можно не писать. То же, но со скобками:
(x) -> x
А вот вариант с явным return:
x -> {
    return x;
}
Или так, со скобками и return:
(x) -> {
    return x;
}
Или с явным указанием типа (и, соответственно, со скобками):
(int x) -> x
Пример 7
x -> ++x
Принимаем х, возвращаем его же, но на 1 больше. Можно переписать и так:
x -> x + 1
В обоих случаях скобки вокруг параметра, тела метода и слово return не указываем, так как это не обязательно. Варианты со скобками и с ретурном описаны в примере 6. Пример 8
(x, y) -> x % y
Принимаем какие-то х и у, возвращаем остаток от деления x на y. Скобки вокруг параметров тут уже обязательны. Необязательны они только когда параметр всего один. Вот так с явным указанием типов:
(double x, int y) -> x % y
Пример 9
(Cat cat, String name, int age) -> {
    cat.setName(name);
    cat.setAge(age);
}
Принимаем объект Кот, строку с именем и целое число возраст. В самом методе устанавливаем Коту переданные имя и возраст. Поскольку переменная cat у нас ссылочного типа, то и объект Кот вне лямбда-выражения изменится (получит переданные внутрь имя и возраст). Немного усложненный вариант, где используется подобная лямбда:
public class Main {
    public static void main(String[] args) {
        // создаем кота и выводим на экран чтоб убедиться, что он "пустой"
        Cat myCat = new Cat();
        System.out.println(myCat);

        // создаем лямбду
        Settable<Cat> s = (obj, name, age) -> {
            obj.setName(name);
            obj.setAge(age);
        };

        // вызываем метод, в который передаем кота и лямбду
        changeEntity(myCat, s);
        // выводим на экран и видим, что состояние кота изменилось (имеет имя и возраст)
        System.out.println(myCat);
    }

    private static <T extends WithNameAndAge>  void changeEntity(T entity, Settable<T> s) {
        s.set(entity, "Мурзик", 3);
    }
}

interface WithNameAndAge {
    void setName(String name);
    void setAge(int age);
}

interface Settable<C extends WithNameAndAge> {
    void set(C entity, String name, int age);
}

class Cat implements WithNameAndAge {
    private String name;
    private int age;

    @Override
    public void setName(String name) {
        this.name = name;
    }

    @Override
    public void setAge(int age) {
        this.age = age;
    }

    @Override
    public String toString() {
        return "Cat{" +
                "name='" + name + '\'' +
                ", age=" + age +
                '}';
    }
}
Результат: Cat{name='null', age=0} Cat{name='Мурзик', age=3} Как видно, сначала объект Кот имел одно состояние, а после использования лямбда-выражения, состояние изменилось. Лямбда-выражения отлично сочетаются с дженериками. И если нам понадобится создать класс Dog, например, который тоже будет имплементить WithNameAndAge, то в методе main() мы можем те же операции проделать и с Cобакой, абсолютно не меняя сами лямбда-выражение. Задача 3. Написать функциональный интерфейс с методом, который принимает число и возвращает булево значение. Написать реализацию такого интерфейса в виде лямбда-выражения, которое возвращает true если переданное число делится без остатка на 13. Задача 4. Написать функциональный интерфейс с методом, который принимает две строки и возвращает тоже строку. Написать реализацию такого интерфейса в виде лямбды, которая возвращает ту строку, которая длиннее. Задача 5. Написать функциональный интерфейс с методом, который принимает три дробных числа: a, b, c и возвращает тоже дробное число. Написать реализацию такого интерфейса в виде лямбда-выражения, которое возвращает дискриминант. Кто забыл, D = b^2 — 4ac. Задача 6. Используя функциональный интерфейс из задачи 5 написать лямбда-выражение, которое возвращает результат операции a * b^c. Популярно о лямбда-выражениях в Java. С примерами и задачами. Часть 2.
Комментарии (112)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
Сергей Иванов
Уровень 68
19 июня, 06:49
public class Main { public static void main(String[] args) { Runnable runnable = () -> System.out.println("Hello world!"); Thread t = new Thread(runnable); t.start(); } } Судя по этому примеру можно сделать вывод, что лямбда - выражение сокращенная конструкция, при использовании которой формируется ссылка, указывающая на объект анонимного внутреннего класса, внутри которого: - реализуется функциональный интерфейс и - переопределяется единственный абстрактный метод. Тогда логично, что какой-либо метод, принимающий лямбду в качестве параметра либо переменная, тем самым принимают ссылку на объект такого скрытого класса, внутри которого переопределен единственный метод. //Runnable runnable = () -> System.out.println("Hello world!"); Runnable runnable = new Анонимный внутренний класс(); runnable.run(); // Hello world!
Стас Пасинков Software Developer в Zipy Master
19 июня, 20:44
спасибо) честно говоря, я не фанат точных формулировок. моя цель была объяснить это все как можно проще. возможно, и упуская где-то какие-то моменты. как именно реализована лямбда на уровне языка - я тоже не знаю. возможно в байткоде там и создается реально анонимный класс на каждую лямбду, не знаю) да мне это и не очень интересно было) лично мне это проще воспринимается как метод, который передается в другой метод. просто мы его для этого "заворачиваем" в объект)) ЗЫ: там во второй части статьи есть немного про отличия между обычным анонимным классом и лямбдой)
SWK
Уровень 24
21 июля, 07:01
"я не фанат точных формулировок. моя цель была объяснить" Видимо, это была попытка объяснить неточными формулировками. Попытка так себе. Куча информации, сформулированная как попало.
Стас Пасинков Software Developer в Zipy Master
21 июля, 07:56
спасибо за отзыв) может вам встречалась статья, где не такая "куча" информации + сформирована отлично? я с удовольствием почитал бы
imik
Уровень 35, Ufa
15 июня, 18:08
баян, конечно, но многие читают, наконец-то дошло как переписать 8 строчек в 2 через stream
public int compare(String[] o1, String[] o2) {
        int length1 = 0;
        int length2 = 0;
        for (String s : o1) {
            length1 += s.length();
        }
        for (String s : o2) {
            length2 += s.length();
        }
        return length1 - length2;
    }
int length1 = Arrays.stream(o1).collect(Collectors.summingInt(x -> x.length()));
int length2 = Arrays.stream(o2).collect(Collectors.summingInt(x -> x.length()));
Ольга
Уровень 23, Н.Новгород, Russian Federation
30 мая, 11:50
"Задача 2. Догадаться, как переписать решение задачи 1 через лямбды" Можно попросить правильный ответ сюда написать
Стас Пасинков Software Developer в Zipy Master
30 мая, 15:52
чуть ниже в комментах обсуждали в деталях все
velmik
Уровень 26, Inchon
20 мая, 06:29
Доброго времени! автор статьи, если не сложно объясните как в этом примере задачи выводится результат в консоль? чет запутался... при создании вашего примера задачи в IDEA!? Задача 1. Переписать этот пример так, чтобы он сортировал массивы не по возрастанию количества слов в массиве, а по убыванию.
String[] array1 = {"мама", "мыла", "раму"};
String[] array2 = {"я", "очень", "люблю", "java"};
String[] array3 = {"мир", "труд", "май"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});
Результат будет такой же, как и в первом случае.
cnsler
Уровень 40, Калининград, Россия
20 мая, 13:50
вывода на печать в коде нет, методом sort() Вы только отсортировали список массивов, для печати можете воспользоваться методом forEach(), например, либо по старинке, так сказать, прогнав итоговый список строк arrays через циклы))
cnsler
Уровень 40, Калининград, Россия
20 мая, 13:59
полагаю, что более опытные коллеги написали бы лучше, но сам пока учусь, поэтому могу предложить Вам, например:
arrays.forEach(o -> {
            for (int i = 0; i < o.length; i++) {
                System.out.print((i != 0) ? " " + o[i] : o[i]);
            }
            System.out.println();
        });
Стас Пасинков Software Developer в Zipy Master
20 мая, 23:06
насчет того, что отсортировать массив - это одно, а вывести его на печать - это другое, тут абсолютно согласен :) а насчет вашего кода - то не очень понятно что вы пытались сделать... если просто вывести на экран - то это делается проще. метод forEach() принимает лямбду, которая принимает на вход один параметр и что-то с ним делает (в нашем случае, должна вывести его на экран). этот метод forEach() берет каждое значение из массива/стрима и отправляет это значение в лямду, которую мы ему дали. поэтому чтобы просто вывести весь массив на экран при помощи forEach() - достаточно написать ему лямду, типа такой: x -> System.out.println(x). то-есть, "взять каждый элемент массива и передать его в метод System.out.println()
arrays.forEach(x -> System.out.println(x));
или даже еще проще, с использованием ссылки на метод (method reference, немного написал об этом во второй части этой статьи)
arrays.forEach(System.out::println);
Стас Пасинков Software Developer в Zipy Master
20 мая, 23:13
если же вы хотели видеть как метод sort() выполняет сравнения двух элементов массива (какие два элемента он берет и отправляет в компаратор для сравнения/получения результата какой из элементов больше/меньше) - тогда можно было бы добавить вывод на экран внутри самого компаратора, где мы получаем два элемента и что-то с ними делаем (строка 14)
String[] array1 = {"мама", "мыла", "раму"};
String[] array2 = {"я", "очень", "люблю", "java"};
String[] array3 = {"мир", "труд", "май"};

List<String[]> arrays = new ArrayList<>();
arrays.add(array1);
arrays.add(array2);
arrays.add(array3);

arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        int result = o1.length - o2.length;
        System.out.println("Comparing elements: " + Arrays.toString(o1) + " and " + Arrays.toString(o2) + ". Result = " + result);
        return result;
    }
});
Стас Пасинков Software Developer в Zipy Master
20 мая, 23:26
этот пример отличается от примера выше лишь тем, что в этом примере у нас под объект компаратора не создается отдельная переменная, а мы сразу его передаем в метод метод sort() а вот в предыдущем примере - там создавалось два объекта компаратора, каждый в своей переменной (sortByLength сортировал бы массивы по их длине, то-есть, по количеству элементов в них. и sortByWordsLength сортировал бы массивы по сумме буковок во всех словах этого массива (у нас там два цикла, которые перебирают все слова в одном массиве, и потом в другом, и для каждого слова берем его длину и прибавляем к длине предыдущих слов в этом массиве)). после чего в методе sort() мы воспользовались компаратором sortByLength для сортировки. поэтому все отличия между первым примером и вторым - это только в том, что в одном месте мы заводили под компаратор отдельную переменную, а в другом случае - нет :) поэтому и результат работы (вывод на экран) не поменялся. о чем я там и написал :)
Стас Пасинков Software Developer в Zipy Master
20 мая, 23:43
код, который вы привели, это точная копия моего кода, который сортировал массивы по возрастанию количества слов в массивах. если вы хотели чтобы что-то изменилось - то надо было что-то изменить :) самый простой вариант - это множить результат "сравнения" на -1 в таком случае, там, где было 3 (первый массив содержит на 3 слова больше, чем второй) - станет -3. где было 0 (массивы одинаковой длинны) - так и останется 0 ну а где было -2 (первый массив содержит на 2 слова меньше, чем второй) - теперь станет просто 2. поэтому как-то так: -1 * (o1.length - o2.length) ну или открыть скобки -o1.length + o2.length но для простоты читаемости кода лучше поменять местами o2.length - o1.length тут было бы неплохо понимать как вообще работает метод sort(). и почему если массив на 10 элементов или на 1000000 элементов - методу sort() все-равно нужен компаратор, который сравнивает только два числа. если вы помните как писали методы по сортировке чего-либо, то у вас они выглядели примерно так: внешний цикл по массиву, берем первый элемент массива для сравнения внутренний цикл по этому же массиву, тут берем второй элемент массива для сравнения с первым сравниваем если первый элемент больше/меньше второго - тогда меняем элементы местами если нужно нас тут интересует именно третий шаг, где мы пишем if() с правилами сравнения двух элементов одного массива. вот именно это и есть то, что мы пишем в компараторе: как сравнить два элемента одного массива. поэтому ему и надо дать 2 параметра в этот метод. а вернуть он должен число, которое показывало условно "больше" ли первый элемент, чем второй. и в зависимости от этого мы бы потом меняли эти элементы местами в нашем алгоритме сортировки.
velmik
Уровень 26, Inchon
21 мая, 06:45
Здравствуйте, у меня почему то не выводится в консоль в idea ваш результат
Результат:
мама мыла раму
мир труд май
я очень люблю java
а выводится вот такой ответ на экран консоли в idea? почему так ? - чет не понимаю...
Comparing elements: [Ljava.lang.String;@5f184fc6 and [Ljava.lang.String;@3feba861. Result = 1
Comparing elements: [Ljava.lang.String;@5b480cf9 and [Ljava.lang.String;@5f184fc6. Result = -1
Comparing elements: [Ljava.lang.String;@5b480cf9 and [Ljava.lang.String;@5f184fc6. Result = -1
Comparing elements: [Ljava.lang.String;@5b480cf9 and [Ljava.lang.String;@3feba861. Result = 0

Process finished with exit code 0
cnsler
Уровень 40, Калининград, Россия
21 мая, 08:44
Вы наверное выводили на печать с помощью
arrays.forEach(x -> System.out.println(x))
, как предложил Стас Пасинков, но все дело в том, что у нас не список строк, а список массива строк, в каждом элементе которого хранится ссылка на массив строк, поэтому Вы с помощью такого "простого" вывода передаете в консоль адреса массивов строк.. чтобы распечатать сами массивы как раз-таки и нужно использовать немного более сложную конструкцию, которую я предлагал:
arrays.forEach(o -> {
            for (int i = 0; i < o.length; i++) {
                System.out.print((i != 0) ? " " + o[i] : o[i]);
            }
            System.out.println();
        });
при этом варианте вывода (да, пусть более "сложном"), этот вывод имеет смысл, потому что мы, поочередно перебирая объекты
o
(элементы списка -- ссылки с адресами на массивы строк), печатаем каждую строку из массивов, разделяя строки пробелом, а в конце вывода каждого массива переходим на новую строку, это необязательно, но благодаря новой строке легче воспринимается результат работы
sort()
, работе которого подвергся наш изначальный список массивов строк
arrays
ps. прошу простить, если сложно или запутанно объяснил, если же неправильно или просто не совсем корректно, то прошу более опытных коллег поправить
velmik
Уровень 26, Inchon
21 мая, 11:43
спасибо, что уделяете свое время и с выводом на консоль по вашему способу кода, я более менее понял! , а вот по лямбде так и не разобрался пока... ну надеюсь в следующих лекциях разберусь. Всем респект!
Стас Пасинков Software Developer в Zipy Master
21 мая, 15:47
да, спасибо. писал тот код без проверке в идее) это в компараторе если выводить на экран значения, которые он сравнивает - то я написал там просто чтобы выводило o1 и o2. не подумал, что это массивы, и поэтому будут выведены на экран в таком виде, как у вас. сейчас уже поправил комментарий, теперь должно быть лучше :)
Стас Пасинков Software Developer в Zipy Master
21 мая, 15:50
теперь я понял зачем вам цикл внутри лямбды :) действительно, у нас же список массивов строк) поэтому массивы там выводятся в том виде неудобном) тогда можно переписать вот так:
arrays.forEach(x -> System.out.println(Arrays.toString(x)));
Стас Пасинков Software Developer в Zipy Master
21 мая, 15:51
то, что с выводом на экран разобрались - это хорошо) так а что именно осталось не понятным по лямдам? :)
velmik
Уровень 26, Inchon
22 мая, 06:42
"осталось не понятным по лямбдам?" - логика вот этого решения по этой задаче Задача 1. Переписать этот пример так, чтобы он сортировал массивы не по возрастанию количества слов в массиве, а по убыванию. тут Засада! не могу понять как idea будет предлагать сократить вариант через лямбду? - выдержка из лекции далее.
Возникает вопрос: при чём здесь вообще лямбда-выражения?

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


arrays.sort(new Comparator<String[]>() {
    @Override
    public int compare(String[] o1, String[] o2) {
        return o1.length - o2.length;
    }
});

Тут мы берем наш список arrays и вызываем у него метод sort(), куда передаем объект компаратора с одним единственным методом compare() (нам не важно, как он называется, ведь он — единственный в этом объекте, тут не промахнемся). Этот метод принимает два параметра, с которыми мы дальше и работаем.
Если вы работаете в IntelliJ IDEA, то наверняка видели, как она вам предлагает этот код значительно сократить:
arrays.sort((o1, o2) -> o1.length - o2.length);
Вот так шесть строк превратились в одну короткую. 6 строк переписали в одну короткую. Что-то исчезло, но я гарантирую, что не исчезло ничего важного, и такой код будет работать абсолютно так же, как и при анонимном классе. Задача 2. Догадаться, как переписать решение задачи 1 через лямбды (в крайнем случае, попросите IntelliJ IDEA превратить ваш анонимный класс в лямбду). здесь тоже Засада! не понимания, и извиняюсь за свою тупую дотошность!? Спасибо.
Стас Пасинков Software Developer в Zipy Master
22 мая, 17:55
Переписать этот пример так, чтобы он сортировал массивы не по возрастанию количества слов в массиве, а по убыванию. я чуть выше расписал подробно. там где показывал, что на -1 умножить, чтобы получить "обратный" результат не могу понять как idea будет предлагать сократить вариант через лямбду? подсветит желтым цветом кусок кода. если поставите курсор на этот желтый кусок кода и нажмете Alt+Enter - то одним из вариантов, которые предложит идея - это как-раз и будет возможность упростить его используя лямбду насчет переписать решение через компаратор на решение через лямбду. у вас в компараторе только один метод. это значит, что его можно попробовать переписать в виде лямбда-функции. вам стоило бы заметить, что именно изменилось в вашем коде (в решении через явное создание объекта компаратора) когда вы переделали его с сортировки по возрастанию на сортировку по убыванию. там просто поменялись местами две переменные (снова же, я объяснил почему так чуть выше в комментах в этом же треде). и у вас есть кусок кода с лямбдой из статьи.
arrays.sort((o1, o2) -> o1.length - o2.length);
тут чисто интуитивно можно догадаться, что если для компаратора мы просто поменяли местами переменные, то и тут этого тоже будет достаточно :)
bighugemister
Уровень 15, Москва, Россия
24 мая, 07:50
o_O просто
System.out.println(arrays);
cnsler
Уровень 40, Калининград, Россия
24 мая, 08:20
так можно конечно, но какой в этом смысл? попробуйте сами, полагаю, результат Вас может удивить)))
Стас Пасинков Software Developer в Zipy Master
24 мая, 12:34
arrays - это список массивов строк. и если для списка переопределен метод toString() (который будет вызываться автоматически когда System.out.println будет пытаться вывести его на экран), и поэтому списки выводятся на экран в более-менее удобном виде. то вот для массивов - не переопределен, и поэтому будет выводиться название класса и адрес в памяти, вместо содержимого массива. поэтому я предлагаю выводить на экран перебирая все элементы списка (без разницы, обычным циклом или методом forEach(), как вам будет удобнее), но так же дополнительно перебирать и элементы в каждом из массивов из этого списка. тут тоже можно либо еще одним циклом, либо при помощи метода Arrays.toString() как я предложил выше :)
John F
Уровень 26
18 мая, 09:06
Здравствуйте всем! Если не сложно, покажите решение 3 задачи.
Стас Пасинков Software Developer в Zipy Master
18 мая, 09:18
x -> x % 13 == 0
или у вас проблема с самим интерфейсом?
John F
Уровень 26
18 мая, 13:33
Да, я не понимаю как все это собрать вместе и вывести в консоль true или false.
Стас Пасинков Software Developer в Zipy Master
18 мая, 19:30
public static void main(String[] args) {
    MyFunctionalInterface lambda = x -> x % 13 == 0;

    int i = 256;
    boolean result = lambda.mySuperMethod(i);
    System.out.println(result);
}


private static interface MyFunctionalInterface {
    boolean mySuperMethod(int number);
}
можно было бы еще добавить над интерфейсом MyFunctionalInterface аннотацию @FunctionalInterface, но и без нее будет работать :)
Стас Пасинков Software Developer в Zipy Master
18 мая, 19:39
или вы могли бы воспользоваться уже готовым интерфейсом Predicate из пакета с уже готовыми функциональными интерфейсами java.util.function. этот интерфейс сделан так, что умеет принимать любые типы данных в свой метод test(), а не только числа. а вот возвращает он всегда boolean, что нам и надо. :) но тогда надо будет явно указать, что ваша лямбда будет работать с интеджерами, например.
public static void main(String[] args) {
    Predicate<Integer> lambda = x -> x % 13 == 0;

    int i = 256;
    boolean result = lambda.test(i);
    System.out.println(result);
}
ну это так, просто чтобы вы знали, что в самой джаве уже есть всякие готовые интерфейсы для такого, и ими можно свободно пользоваться в своих нуждах, а не пилить свои велосипеды)) хотя в самой задаче я просил именно создать свой интерфейс для этого, а не использовать готовый. но это просто тестовая задачка)) в реальной жизни старайтесь пользоваться уже готовыми решениями, так проще будет жить :)
John F
Уровень 26
19 мая, 14:00
Очень благодарен за вашу отзывчивость. Белых пятен остается все меньше и меньше. Спасибо! 🤝
Nik
Уровень 29, Днепр, Ukraine
15 мая, 19:35
Я 4 часа не могу понять как вывести на экран первую задачу . Я перепробовал все что только можно но не смог применить заготовленую сортировку. Нельзя было вывод написать ?
Стас Пасинков Software Developer в Zipy Master
16 мая, 01:02
какой вывод? о чем вы? просто смотрите на то, что у вас выводит - и понимаете, то оно выводит на экран или нет. я не думал, что поменять местами o1 и o2 может занять 4 часа времени. или может вы просто впервые столкнулись с интерфейсом Comparator и его методом compare()? (если да - тогда не удивительно, что застряли. ведь я исходил из того, что читатель уже с ним хорошо знаком) UPD: в комментах чуть выше постарался описать примерно как он работает там же в описании метода все написано: что такое o1 и o2, и что метод должен возвращать. прочитали описание метода. посмотрели как он реализован у меня. поняли почему так (тут ничего сложного нет и не придется прям "стараться" понять, там все просто: от одного отняли другое и получили результат), и сразу становится понятно что сделать чтобы добиться обратного результата.
Стас Пасинков Software Developer в Zipy Master
16 мая, 01:06
я думаю, вы просто себе усложнили сами задачу, а потому и застряли на ней. попробуйте другую, а эту оставьте на время когда голова будет посвежее)
cnsler
Уровень 40, Калининград, Россия
20 мая, 13:54
скорее всего Nik имеет в виду то, что у Вас в коде нет вывода на печать полученного списка массивов строк дабы проверить, что получилось))
cnsler
Уровень 40, Калининград, Россия
20 мая, 13:57
Nik, Вам нужно, применив имеющиеся знания и навыки, касающиеся уже пройденного материала по коллекциям, добавить вывод списка массивов строк arrays хоть через циклы уже после того, как Вы "обработаете" этот список методом сортировки через sort()
Sergey Kornilov
Уровень 39, Petropavlovsk, Казахстан
22 октября 2021, 07:09
Кусок кода, который сложен для читаемости и понимания : private static <T extends WithNameAndAge> void changeEntity(T entity, Settable<T> s) { s.set(entity, "Мурзик", 3); } Проще читается и вызов конструктора или 2 сеттеров класса.
Стас Пасинков Software Developer в Zipy Master
22 октября 2021, 15:28
вы привели пример кода из статьи? потому что отличий я не заметил. а как тогда предлагаете сделать?
Sergey Kornilov
Уровень 39, Petropavlovsk, Казахстан
22 октября 2021, 15:39
Статью не критикую. Отмечаю, что вышеуказанный код сложен для понимания студентов данного курса 19 уровня, без опыта использования лямбда-выражений и дженериков. Возможно к повторному чтению статьи вернусь позже.
Стас Пасинков Software Developer в Zipy Master
22 октября 2021, 15:46
я понял. да, этот код явно не для продакшена. это просто пример того, что лямбды можно использовать не только с примитивами и в простых каких-то кейзах, но также и в более сложных случаях. прошу расценивать этот код только в качестве примера возможностей :) пусть и не очень удачного))
БелК в труселях
Уровень 35, Покровск, Ukraine
24 октября 2021, 11:47
СПАСИБО ЗА СТАТЬЮ!!! Я вверху два раза перечитывал думал, а внизу Результат будет такой же, как и в первом случае. Статья очень понравилась особенно примеры. Я хоть и КОР не изучал, но наверное понял. Вы писали насчет улучшений если бы еще задачи можно было решать и увидеть правильно ты решил или нет то вообще пожар!
Стас Пасинков Software Developer в Zipy Master
24 октября 2021, 18:15
ну на 19м уровне уже должны быть основы кора) а примеры - вроде такие, что при запуске можно увидеть получилось или нет :) к тому же идея много чего подскажет (подсветит желтым то, что можно написать проще) а насчет задач - то в новом курсе джавараша вроде будут задания по лямдам/стримам (я сам не проходил, но вроде в списке тем их там видел). так что может еще и порешаете в дальнейшем :)
БелК в труселях
Уровень 35, Покровск, Ukraine
24 октября 2021, 18:45
Понятно. Еще раз спасибо за статью.
Anonymous #2848645
Уровень 35
28 октября 2021, 20:09
единственное, что студент на 19-м уровне бы не понял из этого отрывка кода бы был момент с Bounded Type Parameters (T extends). дженерики до этого были пройдены, но довольно поверхностно
Redas Shuliakas Java Developer в Google Expert
1 октября 2021, 10:22
Прочитал всё, но ответа на вопрос "Что такое лямбда?", так и не нашёл, вода вода вода бесконечная вода, так до сути и не добрался.
Стас Пасинков Software Developer в Zipy Master
1 октября 2021, 12:41
жаль, что вам не понравилось. возможно, вы найдете нужные вам ответы в других статьях в интернете. эта статья писалась не для того чтобы ответить на вопрос "Что такое лямбда?", поэтому и не удивительно, что ответа этого в ней нет. это больше статья-туториал, как с ними работать в джаве.
Redas Shuliakas Java Developer в Google Expert
2 октября 2021, 07:46
"Для кого предназначена эта статья? для тех, кто считает, что уже неплохо знает Java Core, но понятия не имеет о лямбда-выражениях в Java. Или, возможно, что-то уже слышал про лямбды, но без подробностей." Те кто "понятия не имеет о лямбдах" не сможет вынести ничего полезного из данной статьи, так как дана куча примеров, без базового обьяснения что такое лямбда и как она работает. Советую обновить статью с базовым понятием лямбды, типа "Лямбда это как обычный метод, но так и так, сюда это сюда, чтоб было то или то", профит.
Стас Пасинков Software Developer в Zipy Master
2 октября 2021, 18:27
спасибо за совет, но комментарии говорят об обратном, как мне кажется и еще раз, если кому-то моя статья показалась слишком сложной/непонятной - всегда можно найти статью для своего уровня.
Redas Shuliakas Java Developer в Google Expert
3 октября 2021, 08:18
Причём здесь уровень? Ты вначале статьи врёшь людям в глаза, написал "но понятия не имеет о лямбда-выражениях", тебе не кажется что в такой статье должно быть определение лямбды? Я на Java пишу 5+ лет, мне твоя статья не показалась сложной, она показалась мне бессмысленной, куча ненужных примеров, которые нужны ни за чем, без собственно определения что такое лямбда.
Ivan Smirnov
Уровень 33, Санкт-Петербург
3 сентября 2021, 09:16
Пример 4: возврат 42 - отсылка к автостопом по галактике? 😋😋😋
Стас Пасинков Software Developer в Zipy Master
3 сентября 2021, 13:01
отчасти) потом оно стало просто интернет-мемом, например но каких-то явных отсылок я не делал. просто надо было выбрать какое-то число :)
Ivan Smirnov
Уровень 33, Санкт-Петербург
3 сентября 2021, 14:36
Но получилось забавно)) За статью, кстати, огромное спасибо! И отдельный плюс за задания. Очень помогла с пониманием и закреплением материала.
Gaius Plinius
Уровень 28, Россия
21 ноября 2021, 02:36
42 - это же Главный вопрос жизни, вселенной и всего такого)
Artem
Уровень 30, Москва, Россия
12 мая 2021, 10:52
Для упрощения понимания что тут происходит вот вам самый подробный разбор лямбда-выражения
Settable<Cat> s = (obj, name, age) -> {
            obj.setName(name);
            obj.setAge(age);
        };
public  static class myClass implements Settable<Cat>
   {
       @Override
       public void set(Cat entity, String name, int age)
       {
           entity.setName(name);
           entity.setAge(age);
       }
   }
Settable<Cat> catSettable = new myClass();
        catSettable.set(myCat,"Мурзик",3);
Автор во всю использует дженерики(параметры-типы), которые в основном квесте рассматривались очень поверхностно и без использования в интерфейсах. Советую изучить данные примеры очень подробно.
Стас Пасинков Software Developer в Zipy Master
12 мая 2021, 14:02
ну там вроде 1-2 уровня было на дженерики, если не ошибаюсь. но мне вроде хватило чтобы немного разобраться что это и как :) (конечно же, потом еще сотни тонн информации в интернете по дженерикам перечитал и несколько десятков раз пытался что-то такое заимплементить, и только после этого появилось более-менее понимание как с ними работать) а вот насчет интерфейсов - то они вроде как-раз таки в них чаще всего и используются :) те же List<T> и Map<K, V>