Сортировка по двум параметрам java лямбда
Первый метод простой — выдача всех адресов. Но другие методы сложнее, например:
Каждый раз надо придумывать новые имена, писать новые методы. Может это и не так сложно, но суть в том, что можно сделать проще с помощью лямбда-выражений.
Заметьте, что во всех вышеприведенных примерах мы используем какое-то условие,а значит можно написать функцию, которая возвращает boolean и ее передавать.
Именно здесь и можно применить лямбда-выражение — это блок кода, описывающий функцию интерфейса; функция эта передается как параметр в метод и вызывается при надобности (в нашем случае в тот момент, когда надо проверить условие см. функцию filter.test()).
В нашем случае код с лямбда-выражением будет выглядеть так, что вместо четырех методов появится один, принимающий условие как аргумент:
А уж при вызове метода мы будем передавать лямбда-выражение:
Выше мы получили все электронные адреса.
Аналог — анонимный класс
Лямбда выражения — это не что-то принципиально новое, появившееся в Java 8, а просто синтаксический сахар для анонимного класса. Раньше бы вместо вышеприведенного лямбда-выражения в параметр бы передавалась вот такая громоздкая конструкция:
Выглядит страшно, поэтому распространение получили такие вещи с Java 8 именно благодаря сахару.
В лямбда-выражениях можно опускать слово return и точку с запятой, если правая часть состоит из одной строки. Также, не нужно указывать типы параметров, обычно компилатор их определяет из кода.
В аргументе getEmail() мы передаем интерфейс Predicate. Но как компилятор определяет, какой именно метод интерфейса передается?
Немного о функциональных интерфейсах
Predicate — это функциональный интерфейс, что означает, что он состоит только из одного метода (не считая статических и методов по умолчанию). Этот метод и вызывается для тестирования нашего условия. Мы можем передавать непосредственно содержимое этого метода, не указывания его имени. Компилятор и так догадается, что это за метод, поскольку в интерфейсе Predicate он всего один:
Если бы из было два, то компилятор выдал бы ошибку. Лямбда-выражение можно передавать, только если оно реализует метод функционального интерфейса.
Обратите внимание, что функциональный интерфейс помечен аннотацией @FunctionalInterface. Она нужна прежде всего для разработчика, потому что и без этой аннотации любой интерфейс с единственным методом может служить лямбда-выражением. Просто другие разработчики, видя аннотацию, сразу поймут, что интерфейс не предполагает добавления новых методов.
Еще раз: поскольку метод единственный, и не надо уточнять его название, можно передать в параметр типа Predicate саму реализацию метода и аргумент. Idea сама предлагает заменить такой навороченный участок кода кратким лямбда-выражением.
При этом необязательно использовать интерфейс Predicate, можно создать свой интерфейс с другим названием, но такой же сигнатурой метода. Пример будет работать, просто Predicate уже есть в jdk, и плодить другие интерфейсы не рекомендуется для чистоты кода.
Допишем остальные методы
Допишем вызовы с лямбда-выражениями оставшихся двух методов.
Этот получает электронные адреса из группы1:
Подробнее о функциональных интерфейсах
Теперь закрепим идею функционального интерфейса. Как вы уже поняли из предыдущего примера, это интерфейс, который содержит ровно один абстрактный (в смысле не реализованный метод).
Вот примеры, угадайте является ли следующий интерфейс функциональным:
Ответ нет, в интерфейсе Movable нет ни одного абстрактного метода, а надо один.
Ответ да, в интерфейсе Runnable ровно один метод (из Movable не наследуется ни одного).
Интерфейс FastRunnable функциональный, поскольку он наследует один метод из Runnable, а default-метод fastrun() не учитывается.
Интерфейс Swimming не является функциональным, поскольку из Movable методы не наследуются, а метод ss() статический, а значит не учитывается.
Можно поупражняться в IDE в написании интерфейсов, проверяя ответ постановкой аннотации @FunctionalInterface вверху. Если интерфейс не фукциональный, компилятор выдаст ошибку.
Например, Runnable — функциональный, а значит можно поставить аннотацию @FunctionalInterface без проблем:
Подробнее о синтаксисе лямбда-выражения
Вернемся к примеру с электронными адресами. В нем среди прочих мы использовали такое лямбда-выражение:
Выглядит оно не как полноценная функция, потому что в лямбда-выражених многие части опускаются. Вышеприведенный код эквивалентен следующему:
Слева от стрелки параметры, а справа тело метода.
Параметр метода здесь один, он имеет тип String. Возвращаемое значение тоже имеет тип String.
Это значит, что данное лямбда-выражение подойдет к любому функциональному интерфейсу, имеющему метод с одним параметром типа String и возвращающим значение такого же типа. Например, вместо встроенного в JDK 8 интерфейса Predicate мы могли бы определить и использовать любой собственный интерфейс:
Название тут не важно, лишь бы было совпадение по количеству и типам аргументов.
Но обычно предпочтительнее использовать уже существующий интерфейс, а не писать свой, тем более почти все мыслимые интерфейсы с одним и двумя аргументами в JDK 8 уже есть и являются стандартными, как мы увидим ниже.
Когда можно опустить круглые скобки
Рассмотрим внимательнее левую часть двух вышеприведенных выражений:
Краткая формаё
А это полная форма того же лямбда-выражения:
Полная форма
Во втором выражении присутствует тип аргумента и круглые скобки, а в первом нет.
Опустить круглые скобки слева можно в том и только том случае, если аргумент один, и его тип явно не задан (опущен).
Это означает, что например , если метод без аргументов, то круглые скобки должны присутствовать.
Вот примеры корректно написанных лямбда-выпажений:
А вот примеры некорректных лямбда-выражений:
- В первом выражении задан тип, а значит нужны скобки.
- Во втором аргументов два, а не один, что означает, снова нужны скобки.
- В третьем снова два аргумента, а не один.
Фигурные скобки справа
Теперь сравним правую часть выражений, во втором выражении есть фигурные скобки <>.
Фигурные скобки дают возможность написать в правой части более одной строки кода. Но при этом нельзя опускать точку с запятой и оператор return, именно этому они во втором выражении и есть.
Как уже понятно, в первом выражении все это опущено потому, что одна строка кода позволяет все это опустить.
Приведем еще примеры корректный лямбда-выражений:
Найдите ошибки в лямбда-выражениях
А теперь некорректные, угадайте, что с ними не так:
- В первом нужны круглые скобки слева, поскольку аргументов два.
- Во втором — фигурные скобки справа.
- В третьем пропущена точка с запятой (справа).
Привильный вариант такой:
А почему некорректны вот эти выражения, еще не упоминалось:
Ошибка в том, что если хотя бы один тип параметра слева указан, то остальные тоже должны быть указаны.
Чтобы сделать выражения корректными, надо либо убрать все типы (компилятор обычно их может определить из кода, не переживайте), либо, наоборот, указать все типы:
Вот еще некорретное выражение:
Представьте его как обычный метод и поймете, что если есть аргумент а, то определять локальную переменную с тем же именем а нельзя.
Вот так будет правильно:
Ссылки на методы (Method references)
Но это еще не все, синтаксический сахар простирается дальше.
Рассмотрим случай, когда все, что мы делаем в лямбда-выражении — это вызываем другой метод. Например:
Мы передаем аргумент a в метод интерфейса Consumer (стандартный интерфейс из jdk) и ничего не возвращаем (void). Этот аргумент передается дальше в метод println, который тоже ничего не возвращает. Сигнатуры методов одинаковые — и вышеприведенного метода интерфейса Consumer, и метода println:
На всякий случай вот код метода стандартного функционального интерфейса Consumer из jdk, в нем один параметр и возвращаемый тип void:
Поскольку лямбда-выражение — это всего лишь код, описывающий входные параметры и тело метода, а входные параметры как лямбда выражения, так и метода, использованного внутри лямбда выражения одинаковые, нам достаточно указать один метод. Так что вместо такой записи:
можно использовать сокращенную запись — ссылку на метод println:
Записи (1) и (2) эквивалентны.
Стандартные функциональные интерфейсы и примеры их использования в JDK 8
Если вы хотите передать в параметр функцию, то наверняка свой функциональный интерфейс писать не придется, так как уже есть куча стандартных интерфейсов с различными сигнатурами функций.
В пакете java.util.function их целых 43.
Но запомнить надо всего шесть, остальные интерфейсы производные от остальных. Например, мы выше использовали интерфейс Predicate, который возвращает boolean и принимает один аргумент. А есть BiPredicate — он такой же, но принимает два аргумента.
Вот эти шесть интерфейсов (в третьей колонке примеры с method reference, который мы рассматривали выше):
Интерфейс | Сигнатура | Пример из JDK |
UnaryOperator | T apply(T t) | String::toLowerCase |
BinaryOperator | T apply(T t1, T t2) | BigInteger::add |
Predicate | boolean test(T t) | Collection::isEmpty |
Function | R apply(T t) | Arrays::asList |
Supplier | T get() | Instant::now |
Consumer | void accept(T t) | System.out::println |
По сигнатуре методов из третьего столбца легко понять сигнатуру и предназначение методов из второго, даже не вникая в буквы T, R, поскольку эти сигнатуры одинаковы.
А название из первого поясняет суть той или иной сигнатуры.
Например, BigInteger::add — бинарный оператор.
А вот (почти) все интерфейсы из java.util.function, которые поместились на скриншот:
java.util.function
Проще зайти и посмотреть их сигнатуру, чем перечислять тут. Но они производятся по таким принципам:
- Есть дополнительные три интерфейса BiFunction, BiPredicate, BiConsumer, принимающие два аргумента, а не один, в отличие от исходных Function, Predicate, Consumer. Пример:
- Для всех интерфейсов, где это имеет смысл, есть производные с префиксом Double, Int, Long — они принимают конкретный примитивный тип, а не Generic, как исходные например:
- Для интерфейса Function помимо производных, уточняющих примитивный тип аргумента, есть производные с ..To.., уточняющие возвращаемый примитивный тип, например:
- Особняком стоит BooleanSupplier, возвращающий boolean:
Замыкания в Java
Еще один интересный (но не рекомендуемый) способ использования лямбда-выражений — это замыкания. Те, кто знает JavaScript, понимают о чем речь. Возьмем пример:
К удивлению, результат в консоли будет таким:
На первый взгляд кажется магией, ведь локальные переменные в методах живут только во время метода, а потом забываются. А тут arr[] определенно запоминается.
Но на самом деле arr[] находится не в методе, а в синтетическом final поле некоторого класса, который генерируется jvm и содержит нашу функцию () -> ++arr[0]; Так что все нормально.
Итоги
Пока итогов нет, статья будет дополняться. Исходный код некоторых примеров доступен на GitHub.
Для начала давайте посмотрим пример сортировки примитивных типов и пример сортировки массива объектом в программе ниже:
В результате получим следующий вывод в консоль:
Так, а теперь давайте попробуем отсортировать массив объектов нашего собственного класса:
Ниже показан фрагмент кода для сортировки массива объектов Employee:
System . out . println ( "Стандартная сортировка для массива объектов Employee:\n" + Arrays . toString ( empArr ) ) ;
Теперь запустим программу и получим ошибку:
Exception in thread "main" java . lang . ClassCastException : ua . com . prologistic . Employee cannot be cast to java . lang . Comparable
В чем причина? Дело в том, что если пользовательский класс в Java хочет использовать встроенные методы сортировки классов Arrays или Collections , то он должен реализовать интерфейс Comparable. Этот интерфейс предоставляет метод compareTo(T obj) , который используется методами сортировки. Вы можете убедиться в этом сами, просто посмотрев на реализацию классов String или Date .
После реализации интерфейса Comparable класс Employee изменится следующим образом:
return "[id crayon-h"> + this . id + ", name crayon-h"> + this . name + ", age crayon-h"> + this . age + ", salary crayon-h"> + this . salary + "]" ;
Теперь, когда мы запустим этот код, то получим в консоль вывод отсортированного массива в читабельном виде:
[ [ id = 1 , name = Alex , age = 32 , salary = 50000 ] , [ id = 5 , name = Viktor , age = 35 , salary = 5000 ] , [ id = 10 , name = Andrew , age = 25 , salary = 10000 ] , [ id = 20 , name = Dmitriy , age = 29 , salary = 20000 ] ]
Как видно из вывода в консоль, массив объектов Employee отсортирован по id в порядке возрастания.
Но в большинстве реальных проектов нужно несколько вариантов сортировки с различными параметрами, а не только id. Например можно отсортировать служащих по уровню заработной платы или по возрасту.
Метод компаратора compare(Object o1, Object o2) принимает два объекта в качестве аргумента и должен быть реализован таким образом, чтобы возвращать отрицательное число — если первый аргумент меньше второго, ноль — если они равны и положительное число, если первый аргумент больше, чем второй.
Лямбда-выражение может быть передано в метод в качестве аргумента. Объявление такого метода должно содержать ссылку на соответствующий функциональный интерфейс. Эта ссылка получается параметром метода, который обрабатывает лямбда-выражение.
Существует два способа передачи лямбда-выражения в метод:
- непосредственная передача лямбда-выражения. Этот способ хорошо подходит для лямбда-выражений с малым количеством операторов;
- передача ссылки на функциональный интерфейс, который связан с лямбда-выражением. В этом случае предварительно объявляется ссылка на интерфейс. Затем этой ссылке присваивается код лямбда-выражения. Этот способ уместен, когда лямбда-выражение становится слишком длинным для встраивания в вызов метода.
2. Пример, демонстрирующий способы передачи лямбда-выражения в метод
Пусть требуется передать в метод лямбда-выражение, которое находит среднее арифметическое трех чисел. Для этого выполняется следующая последовательность шагов.
Способ 1. Сначала объявляется функциональный интерфейс IAverage , содержащий метод, который получает три параметра и возвращает результат типа double
Следующим шагом нужно объявить метод, который будет получать ссылку на интерфейс IAverage в качестве параметра. Этот метод может быть размещен в некотором классе как показано ниже
Способ 2. При этом способе предварительно формируется ссылка на интерфейс. Затем этой ссылке присваивается лямбда-выражение. После этого эта ссылка передается в метод PrintAverage() . Такой подход полезен, когда код лямбда-выражения слишком большой и усложняет восприятие вызова метода. Далее приведен измененный код предыдущего примера.
3. Примеры решения задач, в которых лямбда-выражение передается в качестве аргумента метода
3.1. Максимальное значение в массиве чисел. Шаблонный функциональный интерфейс. Передача лямбда-выражения в статический метод
Условие задачи. Разработать лямбда-выражение, которое находит максимальное значение в массиве чисел, имеющих некоторый числовой тип T . Реализовать передачу лямбда-выражения в статический метод. В методе вывести максимальное значение на экран.
Решение. В задаче объявляется обобщенный (шаблонный) функциональный интерфейс IArray . В интерфейсе определен один метод, который получает массив чисел типа T и возвращает значение типа T .
Класс Lambda является основным классом в программе, который содержит функцию main() . В функции main() формируются:- лямбда-выражение, вычисляющее максимальное значение в массиве A , которое является входным параметром. Следует обратить внимание, что массив A указывается параметром лямбда-выражения без скобок [ ] ;
- массив целых чисел, который передается в статический метод Solution() .
С целью демонстрации, в классе Lambda реализовано статический шаблонный метод Solution() , который получает два параметра:
- массив обобщенного типа T , в котором нужно найти максимальный элемент;
- лямбда-выражение, которое передается в метод как ссылка ref на функциональный интерфейс IArray .
Текст решения задачи следующий
Результат работы программы
3.2. Сортировка массива строк методом вставки. Алгоритм сортировки передается лямбда-выражением
В примере демонстрируется использование лямбда-выражения для сортировки массива строк методом вставки.
Результат выполнения программы
3.3. Вычисление количества вхождений элемента в массиве. Обобщенный функциональный интерфейс
Реализовать лямбда-выражение, которое определяет количество вхождений элемента в массиве. Лямбда-выражение реализует обобщенный функциональный интерфейс.
Реализовать метод класса, который получает лямбда-выражение в качестве параметра и выводит количество вхождений элемента в массиве.
Мой массив не содержит никакой строки. Но он содержит ссылки на объекты. Каждая ссылка на объект возвращает имя, идентификатор, автора и издателя методом toString.
Теперь мне нужно отсортировать этот массив объектов по имени. Я знаю, как сортировать, но я не знаю, как извлечь имя из объектов и отсортировать их.
ОТВЕТЫ
Ответ 1
У вас есть два способа сделать это: оба используют Arrays служебный класс
- Внесите Comparator и передайте массив вместе с компаратором метод сортировки, который принимает это второй параметр.
- Внедрите Comparable интерфейс в классе, из которого ваши объекты и передают ваш массив, в метод сортировки , который принимает только один параметр.
Пример
Выход
Ответ 2
Вы можете попробовать что-то вроде этого:
Ответ 3
Java 8
Использование лямбда-выражения
Test.java
MyType.java
Вывод:
Использование ссылок методов
где compareThem должен быть добавлен в MyType.java:
Ответ 4
Обновление для конструкций Java 8
Предполагая, что класс Book Arrays.sort метод получения поля name , вы можете использовать метод Arrays.sort , передав дополнительный Comparator указанный с помощью конструкций Java 8 - метод и ссылки на метод по умолчанию для компаратора.
Кроме того, можно сравнивать несколько полей, используя методы thenComparing .
Ответ 5
с java 8 с использованием ссылочного метода
вы можете добавить метод compare в класс Book
а затем вы можете сделать это:
здесь приведен полный пример:
Ответ 6
Иногда вы хотите отсортировать массив объектов на произвольном значении. Поскольку compareTo() всегда использует ту же информацию о экземпляре, вы можете использовать другую технику. Один из способов - использовать стандартный алгоритм сортировки. Скажем, у вас есть массив книг, и вы хотите отсортировать их по высоте, который хранится как int и доступен через метод getHeight(). Здесь вы можете сортировать книги в своем массиве. (Если вы не хотите менять исходный массив, просто сделайте копию и выполните сортировку.)
Когда этот код будет выполнен, массив объекта Book будет отсортирован по высоте в порядке убывания - мечта дизайнера интерьера!
Ответ 7
Вы можете реализовать интерфейс "Comparable" для класса, чьи объекты вы хотите сравнить.
Читайте также: