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

Переменная уже определена в методе лямбда

Рассмотрим следующий почти компилируемый код Java 8:

public static void main(String[] args) {

    LinkedList<User> users = null;
    users.add(new User(1, "User1"));
    users.add(new User(2, "User2"));
    users.add(new User(3, "User3"));

    User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();
}

static class User {

    int id;
    String username;

    public User() {
    }

    public User(int id, String username) {
        this.id = id;
        this.username = username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public int getId() {
        return id;
    }
}

Вы заметите, что User user = users.stream().filter((user) -> user.getId() == 1).findAny().get(); выдает ошибку компилятора:

переменная user уже определена в main метода (String [])

Мой вопрос: почему выражения лямбда рассматривают переменную, которая инициализируется в той же строке, что и выражение Lambda, как уже определено? Я понимаю, что Lambdas ищет вне себя (и использует) локальные переменные, поэтому вы не можете назвать переменные, которые вы используете внутри лямбда, так же, как и внешняя переменная. Но почему переменная, которая определена, считается уже определенной?

4b9b3361

Ответ 1

Перейдите в Спецификацию языка Java на имена и их области действия

Объем формального параметра метода (§8.4.1), конструктор (§8.8.1) или лямбда-выражение (§15.27) - это весь орган метод, конструктор или лямбда-выражение.

Областью объявления локальной переменной в блоке (§14.4) является остаток блока, в котором появляется объявление, начиная с его собственный инициализатор и включая любые последующие деклараторы справа оператор объявления локальной переменной.

Затем, на тему затенения и затенения

Локальная переменная (§14.4), формальный параметр (§8.4.1, §15.27.1), исключение (§14.20), а локальный класс (§14.3) может быть равен ссылается на простое имя, а не на квалифицированное имя (§6.2).

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

Это ошибка времени компиляции, если используется имя локальной переменной v объявить новую переменную в пределах области v, если только новая переменная объявляется внутри класса, чья декларация находится внутри область действия v.

Итак, в

User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();

область видимости переменной user - это все после нее в этом блоке. Теперь вы пытаетесь использовать имя этой переменной для объявления новой переменной в пределах области видимости, но не

внутри класса, чье объявление находится в пределах области v.

поэтому возникает ошибка времени компиляции. (Он объявлен в выражении лямбда, а не в классе.)

Ответ 2

посмотреть код

User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();

Имя переменной user, а переменная внутри лямбда также user

попробуйте изменить его, чтобы быть чем-то вроде этого

User user = users.stream().filter((otherUser) -> otherUser.getId() == 1).findAny().get();

Ответ 3

Это то же самое, что и с любыми другими локальными переменными: вам не разрешено скрывать их в более внутренних {} блоках.

Ответ 4

Обратите внимание: это ограничение будет удалено в будущих выпусках (я думаю, в Java 11 или Java 12). Цитата из JEP-302:

Параметры Lambda не допускаются к теневым переменным в охватывающих областях. (Другими словами, лямбда ведет себя как оператор for - см. JLS). Это часто вызывает проблемы, как в следующем (очень распространенном) случае:

Map<String, Integer> msi = ...
...
String key = computeSomeKey();
msi.computeIfAbsent(key, key -> key.length()) //error

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

Было бы желательно снять это ограничение и разрешить параметры лямбда (и локали, объявленные с помощью лямбда), теневым переменным, определенным в охватывающих областях. (Один возможный аргумент против - читаемость: если параметры лямбда разрешены для теневого копирования, то в приведенном выше примере ключ "идентификатор" означает две разные вещи в двух местах, где он используется, и, похоже, нет синтаксического барьера для разделения два обычаев.)

Ответ 5

Вопрос довольно старый, но я думал, что мой ответ может добавить лучшую ясность к уже предоставленным ответам. Особенно это касается @Sotirios Delimanolis. Назначение лямбда в

    User user = users.stream().filter((user) -> user.getId() == 1).findAny().get();

по той же причине не удается выполнить следующий код.

    Object e = null;
    try{
      throw new Exception();
    } catch(Exception e) { // compilation fails because of duplicate declaration
      //do nothing
    }

Локальная переменная (§14.4), формальный параметр (§8.4.1, §15.27.1), параметр исключения (§14.20) и локальный класс (§14.3) можно отнести только к простому имени, а не к квалифицированное имя (§6.2).

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

Поскольку lambdas имеют одинаковую область всех вещей, упомянутых выше, это терпит неудачу.