Подтвердить что ты не робот

Что такое разбивка на синтаксис лямбда Java?

Пожалуйста, объясните синтаксис методов лямбда Java 8.

Есть много объяснений там, для каких функций лямбда есть, но я не могу найти подробное объяснение синтаксиса, и мне очень трудно научиться правильно воспроизводить синтаксис, потому что я не понимаю почему они написаны так, как они есть.

Вот общий случай, с которым я сталкиваюсь, любезно предоставил NetBeans:

public static void main(String[] args) {
    SwingUtilities.invokeLater(() -> {
        new MainAppJFrame();
    });
}

Итак, каким-то образом следующее лямбда-выражение разрешается в анонимный метод Runnable object run():

() -> {
    // do stuff
}

-> - собственно синтаксис лямбда, правильно? А фигурные скобки просто содержат код анонимного метода. Являются ли круглые скобки пустым аргументом, потому что в этом случае мы создаем метод Runnable.run()?

Это все довольно неясно для меня. Я предполагаю, что компилятор знает, как создать анонимный Runnable на основе типа, ожидаемого методом SwingUtilities.invokeLater(Runnable)? Что произойдет, если будут два метода SwingUtilities.invokeLater, которые отличаются только списком параметров? Очевидно, что в этом конкретном случае нет, но это возможно в другом месте:

interface ExampleLambdaConsumer {
    public void doSomething(Runnable r);
    public void doSomething(java.lang.reflect.Method m);
}

class C implements ExampleLambdaConsumer {
    // implementations for doSomething methods here

    public static void main(String[] args) {
        doSomething(() -> {
            // lambda method body here
        }
    }
}
4b9b3361

Ответ 1

Синтаксис:

arguments -> body

где arguments может быть как

  • ()

  • одна переменная, если тип этой переменной может быть выведен из контекста

  • последовательность переменных, с типами или без (или начиная с Java 11, с var), в скобках.
    Примеры: (x), (x, y), (int x, int y), (var x, var y) (Java 11+).
    Недопустимо следующее: (int x, y), (x, var y), (var x, int y)

и body может быть выражением или блоком {...} с операторами. Выражение (кроме вызова метода или конструктора) просто возвращается, т.е. () -> 2 эквивалентно () -> {return 2;}


В случае лямбда-выражений, таких как () -> f() (тело является выражением вызова метода или конструктора):

  • если f() возвращает void, они эквивалентны () -> { f(); }

  • в противном случае они эквивалентны либо () -> { f(); }, либо () -> { return f(); }). Компилятор выводит его из вызывающего контекста, но обычно он предпочитает последний.

Поэтому, если у вас есть два метода: void handle(Supplier<T>) и void handle(Runnable), то:

  • handle(() -> { return f(); }) и handle(() -> x) вызовут первый,

  • handle(() -> { f(); } вызовет второй, и

  • handle(() -> f()):

    • если f() возвращает void или тип, который не может быть преобразован в T, то он вызовет второй

    • если f() возвращает тип, который можно преобразовать в T, то он вызовет первый


.Компилятор пытается сопоставить тип лямбды с контекстом. Я не знаю точных правил, но отвечаю:

Что бы произошло, если бы было два метода SwingUtilities.invokeLater, которые отличаются только списком параметров?

это: это зависит от того, что будет эти списки параметров. Если бы у другого invokeLater был также ровно один параметр, и этот параметр имел бы тип, который также является интерфейсом с одним методом типа void*(), то он бы пожаловался, что не может выяснить, какой метод вы имеете в виду.

Почему они написаны как они есть? Ну, я думаю, потому что синтаксис в С# и Scala почти одинаков (они используют =>, а не ->).

Ответ 2

Синтаксис

(parameter_list_here) -> { stuff_to_do; }

Кожные фигурные скобки могут быть опущены, если это одно выражение. Обычные круглые скобки вокруг списка параметров могут быть опущены, если это один параметр.

Синтаксис работает только для всех функциональных интерфейсов. Аннотирование @FunctionalInterface сообщает компилятору, что вы намереваетесь написать такой интерфейс и выдает ошибку компиляции, если вы не отвечаете требованиям (например), например, он должен иметь только один переопределяемый метод.

@FunctionalInterface
interface TestInterface {
    void dostuff();
}

Runnable также объявляется так. Других интерфейсов нет, и они не могут использоваться с лямбда-функциями.

Теперь, когда мы создали новый функциональный интерфейс с методом, который не принимает никаких параметров, как насчет того, что мы проверяем вопрос о "столкновении" в подписях?

public class Main {
    private void test(Runnable r) {

    }
    private void test(TestInterface ti) {

    }
    public static void main(String[] args) { 
        test(() -> { System.out.println("test");})
    }

    @FunctionalInterface
    interface TestInterface {
        void dostuff();
    }
}

Результат: ошибка компиляции: неоднозначный вызов метода тестирования.

Вы видите, что компилятор/виртуальная машина (если она выполнена) находит соответствующие методы и их список параметров и видит, является ли параметр функциональным интерфейсом, и если он создает анонимную реализацию этого интерфейса. Технически (в байтовом коде) он отличается от анонимного класса, но в остальном идентичен (вы не увидите файлы Main $1.class).

Ваш примерный код (любезно предоставлен Netbeans) также можно заменить на

SwingUtilities.invokeLater(MainAppJFrame::new);

Btw.:)

Ответ 3

Лямбда-выражения в основном применяются в Java 8 до упрощают переопределение функции процесса как анонимные функции.

Это простой ярлык для переопределить старые анонимные функции java.

Обратитесь к следующему примеру:

Предположим, что у вас есть интерфейс A, который имеет только один метод, описанный ниже:

interface A{        
    void print();           
}

теперь с старым стилем java мы будем переопределять это анонимным способом, как показано ниже:

new A() {           
        @Override
        public void print() {
            System.out.println("in a print method");                
        }           
};

дополнительно с выражением java 8 lambda мы будем использовать его, как показано ниже:

() -> System.out.println("in a print method");

Здесь мы можем передать параметры, необходимые для метода перед оператором -> а затем переопределенное тело после оператора ->.

нам нужно только установить дополнительные параметры, чтобы нам было объявить интерфейс с @FunctionalInterface, как показано ниже:

 @FunctionalInterface
 interface A{        
    void print();           
 }

Примечание: - выражение лямбда может использоваться только для "функционального" интерфейса, который имеет только один метод, отличный от метода по умолчанию.

Ответ 4

Синтаксис сбивает с толку. Это сводит меня с толку. Если бы не Интеллидж, поправляя меня, я бы ошибся.

Компилируется нормально.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> {return (integer%2 == 0);};
}

}

Компилируется нормально.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> {return integer%2 == 0;};
}

}

НЕ компилируется, так как это больше не утверждение.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> {return integer%2 == 0};
}

}

НЕ компилируется, так как это больше не оператор, а оператор return был удален.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> {integer%2 == 0};
}

}

Компилируется нормально.

class Scratch {
public static void main(String[] args) {
    Predicate<Integer> even = integer -> integer%2 == 0;
}

}

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

На самом деле я написал это больше для себя, чем для чего-либо еще, потому что это меня беспокоило