Java: анонимный внутренний класс с использованием локальной переменной

Как я могу получить значение userId перешел к этому методу в моем анонимном внутреннем подклассе здесь?

public void doStuff(String userID) {
    doOtherStuff(userID, new SuccessDelegate() {
        @Override
        public void onSuccess() {
            Log.e(TAG, "Called delegate!!!! "+ userID);
        }
    });
}

Я получаю эту ошибку:

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

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

5 ответов

Решение

Конечно, вы можете назначить его как окончательный - просто укажите это ключевое слово в объявлении параметра:

public void doStuff(final String userID) {
   ...

Я не уверен, что вы имели в виду, что это переменная с неизвестным значением; все это означает, что после того, как значение присвоено переменной, оно не может быть переназначено. Поскольку вы не изменяете значение userID в своем методе, в этом случае нет проблем сделать его окончательным.

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

Вот (в основном), почему это так... если вы напишите следующий код (длинный ответ, но, внизу, вы можете получить короткую версию:-):

class Main
{
    private static interface Foo
    {
        void bar();
    }

    public static void main(String[] args)
    {
        final int x;
        Foo foo;

        x = 42;
        foo = new Foo()
        {
            public void bar()
            {
                System.out.println(x);
            }
        };

        foo.bar();
    }
}

компилятор переводит это примерно так:

class Main
{
    private static interface Foo
    {
        void bar();
    }

    public static void main(String[] args)
    {
        final int x;
        Foo foo;

        x = 42;

        class $1
            implements Foo
        {
            public void bar()
            {
                System.out.println(x);
            }
        }

        foo = new $1();
        foo.bar();
    }
}

и тогда это:

class Main
{
    private static interface Foo
    {
        void bar();
    }

    public static void main(String[] args)
    {
        final int x;
        Foo foo;

        x = 42;
        foo = new $1(x);
        foo.bar();
    }

    private static class $1
        implements Foo
    {
        private final int x;

        $1(int val)
        {
           x = val;
        }

        public void bar()
        {
            System.out.println(x);
        }
    }
}

и, наконец, к этому:

class Main
{
    public static void main(String[] args) 
    {
        final int x;
        Main$Foo foo;

        x = 42;
        foo = new Main$1(x);
        foo.bar();
    }
}

interface Main$Foo
{
    void bar();
}

class Main$1
    implements Main$Foo
{
    private final int x;

    Main$1(int val)
    {
       x = val;
    }

    public void bar()
    {
        System.out.println(x);
    }
}

Важным является то, где он добавляет конструктор к $1. Представьте, если бы вы могли сделать это:

class Main
{
    private static interface Foo
    {
        void bar();
    }

    public static void main(String[] args)
    {
        int x;
        Foo foo;

        x = 42;
        foo = new Foo()
        {
            public void bar()
            {
                System.out.println(x);
            }
        };

        x = 1;

        foo.bar();
    }
}

Можно ожидать, что foo.bar() выведет 1, но на самом деле выведет 42. Требуя, чтобы локальные переменные были окончательными, эта запутанная ситуация не может возникнуть.

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

Тем не менее, начиная с Java SE 8, локальный класс может обращаться к локальным переменным и параметрам заключающего блока, которые являются окончательными или фактически конечными. Переменная или параметр, значение которых никогда не изменяется после его инициализации, фактически является окончательным. Например, предположим, что переменная numberLength не объявлен окончательным, и вы добавляете выделенный оператор присваивания в PhoneNumber конструктор:

PhoneNumber(String phoneNumber) {
    numberLength = 7; // From Kobit: this would be the highlighted line
    String currentNumber = phoneNumber.replaceAll(
        regularExpression, "");
    if (currentNumber.length() == numberLength)
        formattedPhoneNumber = currentNumber;
    else
        formattedPhoneNumber = null;
}

Из-за этого оператора присваивания переменная numberLength больше не является окончательным. В результате компилятор Java генерирует сообщение об ошибке, похожее на "локальные переменные, на которые ссылается внутренний класс, должны быть окончательными или эффективно окончательными", где внутренний класс PhoneNumber пытается получить доступ к numberLength переменная:

if (currentNumber.length() == numberLength)

Начиная с Java SE 8, если вы объявляете локальный класс в методе, он может получить доступ к параметрам метода. Например, вы можете определить следующий метод в локальном классе PhoneNumber:

public void printOriginalNumbers() {
    System.out.println("Original numbers are " + phoneNumber1 +
        " and " + phoneNumber2);
}

Метод printOriginalNumbers доступ к параметрам phoneNumber1 а также phoneNumber2 метода validatePhoneNumber

В чем проблема с этим final как в

public void doStuff (final String userID)

Объявить метод

public void doStuff(final String userID)

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

Значение не меняется в вашем коде, так что это безопасное изменение.

Другие вопросы по тегам