JavaRush /Курси /Java Syntax Zero /Функціональний метод

Функціональний метод

Java Syntax Zero
Рівень 19 , Лекція 2
Відкрита

1. Функціональні методи

Якщо інтерфейс має тільки один метод, змінній цього типу-інтерфейсу можна присвоїти значення, задане лямбда-виразом (лямбда-функцією). Такі інтерфейси стали називати функціональними інтерфейсами (після додавання в Java підтримки лямбда-функцій).

Наприклад, у Java є інтерфейс Consumer<Тип> (Consumer == Споживач), який містить метод accept(Тип obj). Навіщо ж потрібен цей інтерфейс?

У Java 8 у колекцій з'явився метод forEach(), який дає змогу для кожного елемента колекції виконати якусь дію. І от саме для передавання дії в метод forEach() і використовується функціональний інтерфейс Consumer<T>.

Отак можна вивести всі елементи колекції на екран:

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "Привіт", "як", "справи?");

list.forEach( (s) -> System.out.println(s) );
Виведення всіх елементів колекції (з використанням лямбда-виразу)

Компілятор перетворить цей код на такий:

ArrayList<String> list = new ArrayList<>();
Collections.addAll(list, "Привіт", "як", "справи?");

list.forEach(new Consumer<String>()
{
   public void accept(String s)
   {
      System.out.println(s);
   }
});
Виведення всіх елементів колекції (з використанням анонімного класу)

Перший запис однозначно коротший за другий. І хоча читати код із лямбда-виразами непросто, читати код з анонімними внутрішніми класами часом іще складніше.



2. Посилання на метод

Натомість наш код із лямбда-виразом можна скоротити ще більше.

По-перше, можна випустити дужки навколо параметра s:

list.forEach( (s) -> System.out.println(s) );
Було
list.forEach( s -> System.out.println(s) );
Стало

Так можна робити, тільки якщо параметр один. Якщо параметрів декілька, потрібно використовувати дужки.

А по-друге, можна записати так:

list.forEach( System.out::println );
Найкомпактніший запис

Це все той самий запис. Зверніть увагу, що після println немає дужок.

Тут записано той самий код — виклик методу:

об'єкт::метод
x -> об'єкт.метод(x)

Подумайте самі: ми хотіли для кожного елемента колекції list виконувати якусь дію. Якщо ця дія — виклик однієї функції (як-от println()), було б розумно просто передати функцію в метод як параметр.

А як пояснити компілятору, що функцію потрібно саме передати, а не викликати? Для цього перед іменем методу ставимо не крапку, а дві двокрапки: одна двокрапка вже зайнята в тернарному операторі.

Це й є найпростішим і найкомпактнішим записом.



3. Конструктор

Посилання на методи за допомогою подвійної двокрапки дуже зручно використовувати, коли ми працюємо з потоками вводу-виведення. Ви переконаєтеся в цьому трохи згодом.

А наразі поговорімо про три популярні способи передачі посилання на метод:

Посилання на метод об'єкта

Для того щоб передати посилання на метод об'єкта, потрібно записати код такого формату: об'єкт::метод.
Цей код є еквівалентним коду x -> об'єкт.метод(x).

Об'єктом можуть бути такі спеціальні змінні, як this і super.

Посилання на метод класу

Для того щоб передати посилання на статичний метод, потрібно записати код такого формату: клас::метод. Цей код буде перетворено на код x -> клас.метод(x);

Посилання на конструктор

Конструктор за своєю поведінкою чимось схожий на статичний метод класу, тому на нього теж можна передати посилання. Відповідний код має такий вигляд: клас::new.

Наприклад, можна обійти стирання типів у колекцій і передати в метод toArray() посилання на конструктор, який створить потрібний масив: toArray( int[]::new );


Коментарі (7)
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ
Олександр Рівень 79
1 березня 2025
Є інший варіант задання посилання на метод об'єкта.

Назва класу::назва методу экземпляра
У такому разі перший параметр функціонального інтерфейсу виконує роль об'єкта, а інші параметри будуть параметрами методу, викликаного для цього об'єкта. Приклад:

interface ForString {
    boolean check(String s1, String s2);
}
public class SingleLambda {
    public static void main(String[] args) {
        System.out.print("Equals: ");
        //"Hello" виступає як об'єкт, на якому викликається метод equals("Hi"). "Hello". equals ("Hi");
        //Метод функціонального інтерфейсу має два параметри, метод equals має один параметр
        isTrue(String::equals, "Hello","Hi");
        System.out.print("Contains: ");
        // Те, що і вище, тільки для contains("el"). "Hello".contains("el");
        isTrue(String::contains, "Hello", "el");
        Test test = new Test();
        System.out.print("firstBiggerLength: ");
        // Звичайний визов через об'єкт
        // два параметри у методі функціонального інтерфейсу, два параметри у методі firstHaveBiggerLength
        isTrue(test::firstHaveBiggerLength, "Hello","Hi");
    }

    static void isTrue(ForString obj, String s1, String s2) {
        if(obj.check(s1, s2)) System.out.println("True");
        else System.out.println("False");
    }
}
class Test{
    boolean firstHaveBiggerLength(String string1, String string2){
        return string1.length() > string2.length();
    }
}
17 січня 2024
мда, є один мем який на всі 100% підходить до цієї лекції)
Elder_HD Рівень 42 Expert
20 червня 2023
Дякую, що так поглиблено пояснили за абстрактні класи, лямбди функції, посилання на методи! Видно, що на цьому рівні дуже пропрацьований матеріал, особисто для тих хто вчить Java як першу мову програмування. Впевнений, що цієї інфи на стільки достатньо, що тепер навіть не потрібно йти вивчати матеріал на інших ресурсах (ютуб, книги). P.S. до цього утримувався від критики лекцій у квестах. Але тут не то, що важко для сприйняття, а просто інформація подана як аксіома - "ось тримайте, просто запам'ятайте і використовуйте"
Микита Рівень 89 Expert
15 червня 2023
чому при запуску кода останнього завдання в IDE не спрацьовує команда? return strings.toArray(String[]::new); IDE дає помилку "String[] is not a functional interface" команда return strings.toArray(new String [] {}); спрацьовує
FAUST_ua Рівень 29
1 жовтня 2022
На мій погляд дещо поверхнево пояснено.
RomanH Рівень 29
17 січня 2022
Знайомство з foreach обов'язково через println!
Vasia Рівень 26
5 вересня 2022
Ні, пішло з print