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

Почему мы не можем использовать методы по умолчанию в лямбда-выражениях?

Я читал этот учебник по Java 8, где автор показал код:

interface Formula {
    double calculate(int a);

    default double sqrt(int a) {
        return Math.sqrt(a);
    }
}

И затем сказал

Невозможно получить доступ к методам по умолчанию из лямбда-выражений. следующий код не компилируется:

Formula formula = (a) -> sqrt( a * 100);

Но он не объяснил, почему это невозможно. Я запустил код, и он дал ошибку,

несовместимые типы: Формула не является функциональным интерфейсом

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

4b9b3361

Ответ 1

Это более или менее вопрос о сфере видимости. Из JLS

В отличие от кода, появляющегося в объявлениях анонимного класса, значение имена и ключевые слова this и super, появляющиеся в лямбда-теле, наряду с доступностью ссылочных объявлений, являются одинаковыми как в окружающем контексте (за исключением того, что параметры лямбда вводят новые имена).

В вашем примере

Formula formula = (a) -> sqrt( a * 100);

область не содержит декларации для имени sqrt.

Это также намекает на JLS

Практически говоря, для выражения лямбда необычно говорить о себе (либо называть себя рекурсивно, либо вызывать его другие методы), в то время как более распространено желание использовать имена для ссылки к вещам в окружающем классе, которые иначе были бы затенены (this, toString()). Если для лямбда-выражения необходимо ссылаться на себя (как будто через this), ссылку на метод или анонимную Вместо этого следует использовать внутренний класс.

Я думаю, что это могло быть реализовано. Они решили не допускать этого.

Ответ 2

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

Например, этот компилятор

class Main {

    public static void main(String[] args) {
        new Main().foo();
    }

    void foo() {
        System.out.println(this);
        Runnable r = () -> {
            System.out.println(this);
        };
        r.run();
    }
}

и он печатает что-то вроде

[email protected]
[email protected]

Другими словами this является Main, а не объектом, созданным выражением лямбда.

Таким образом, вы не можете использовать sqrt в своем лямбда-выражении, потому что тип ссылки this не Formula, или подтип, и у него нет метода sqrt.

Formula является функциональным интерфейсом, хотя код

Formula f = a -> a;

компилируется и запускается для меня без каких-либо проблем.

Хотя вы не можете использовать выражение лямбда для этого, вы можете сделать это с помощью анонимного класса, например:

Formula f = new Formula() { 
    @Override 
    public double calculate(int a) { 
        return sqrt(a * 100); 
    }
};

Ответ 3

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

interface Value {
    int get();

    default int getDouble() {
        return get() * 2;
    }
}

public static void main(String[] args) {
    List<Value> list = Arrays.asList(
            () -> 1,
            () -> 2
        );
    int maxDoubled = list.stream()
        .mapToInt(val -> val.getDouble())
        .max()
        .orElse(0);
    System.out.println(maxDoubled);
}

выводит 4, как ожидалось, и использует метод по умолчанию внутри выражения лямбда (.mapToInt(val -> val.getDouble()))

Что автор вашей статьи пытается здесь сделать

Formula formula = (a) -> sqrt( a * 100);

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

Это работает отлично, в приведенном выше примере кода Value value = () -> 5 или с Formula в качестве интерфейса, например

Formula formula = (a) -> 2 * a * a + 1;

Но

Formula formula = (a) -> sqrt( a * 100);

не удается, поскольку пытается получить доступ к методу (this.) sqrt, но не может. Lambdas в соответствии со спецификацией наследует их сферу действия из своего окружения, что означает, что this внутри лямбды относится к той же самой вещи, что и непосредственно за ее пределами. И снаружи нет метода sqrt.

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

interface NotRunnable {
    void notRun();
}

private final Runnable r = () -> {
    System.out.println("Hello");
};

private final NotRunnable r2 = r::run;

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

Ответ 4

Это немного влияет на обсуждение, но я все равно нашел его интересным.

Еще один способ увидеть проблему - подумать об этом с точки зрения саморегуляции лямбда.

Например:

Formula formula = (a) -> formula.sqrt(a * 100);

Казалось бы, это должно иметь смысл, так как к тому времени, когда лямбда будет выполнена, ссылка formula должна быть уже инициализирована (т.е. нет способа сделать formula.apply() до тех пор, пока formula не будет правильно, в чьем случае, из тела лямбда, тела apply, должно быть возможно ссылаться на одну и ту же переменную).

Однако это тоже не работает. Интересно, что это было возможно в начале. Вы можете видеть, что Морис Нафталин зарегистрировал его на своем Lambda часто задаваемом веб-сайте. Но по какой-то причине поддержка этой функции была в конечном итоге удалена.

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

Ответ 5

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

Вы получаете сообщение об ошибке incompatible types: Formula is not a functional interface, потому что вы не указали аннотацию @FunctionalInterface, если вы предоставили вам возможность получить ошибку "метод undefined", компилятор заставит вас создать метод в классе.

@FunctionalInterface должен иметь только один абстрактный метод, который имеет ваш интерфейс, но отсутствует аннотация.

Но статические методы не имеют такого ограничения, так как мы можем получить к нему доступ без ссылки на объект, как показано ниже.

@FunctionalInterface
public interface Formula {

    double calculate(int a);

    static double sqrt(int a) {
        return Math.sqrt(a);
    }
}

public class Lambda {

    public static void main(String[] args) {
    Formula formula = (a) -> Formula.sqrt(a);
        System.out.println(formula.calculate(100));
    }

}