9

How can I get the value of userId passed to this method in my anonymous inner subclass here?

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

I get this error:

Cannot refer to a non-final variable userID inside an inner class defined in a different method

I'm pretty sure I can't assign it as final since it's a variable with an unknown value. I had heard that this syntax does preserve scope in some way, so I think there must be a syntax trick I don't quite know yet.

Alex Wayne
  • 178,991
  • 47
  • 309
  • 337
  • too bad java requires final here. it is obvious to compiler that the local variable is not changed, it shouldn't ask coder to confirm that. in case the anon class writes to the local variable, a compile warning is enough. bad bad design choice. – irreputable Jul 14 '10 at 23:24
  • possible duplicate of [Why are only final variables accessible in anonymous class?](http://stackoverflow.com/questions/4732544/why-are-only-final-variables-accessible-in-anonymous-class) –  Jul 01 '15 at 14:23

5 Answers5

10

As everyone else here has said, local variables have to be final to be accessed by an inner class.

Here is (basically) why that is... if you write the following code (long answer, but, at the bottom, you can get the short version :-):

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();
    }
}

the compiler translates it roughly like this:

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();
    }
}

and then this:

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);
        }
    }
}

and finally to this:

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);
    }
}

The important one is where it adds the constructor to $1. Imagine if you could do this:

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();
    }
}

You would expect that foo.bar() would print out 1 but it would actually print out 42. By requiring local variables to be final this confusing situation cannot arise.

Community
  • 1
  • 1
TofuBeer
  • 60,850
  • 18
  • 118
  • 163
  • Question: The annon inner class only makes automatic copies of `final` local variables that it uses correct? Not copies of other local (final or not) variables that it doesn't directly use? I ask because I'm concerned about memory leaks. – Tony Chan Oct 11 '12 at 19:45
7

Sure you can assign it as final - just put that keyword in the declaration of the parameter:

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

I'm not sure what you meant about it being a variable with an unknown value; all that final means is that once a value is assigned to the variable, it cannot be re-assigned. Since you're not changing the value of the userID within your method, there's no problem making it final in this case.

Andrzej Doyle
  • 102,507
  • 33
  • 189
  • 228
2

In Java 8, this has changed a little bit. You can now access variables that are effectively final. Relevant snippet and example from the Oracle documentation (emphasis mine):

However, starting in Java SE 8, a local class can access local variables and parameters of the enclosing block that are final or effectively final.

Effectively final: A non-final variable or parameter whose value is never changed after it is initialized is effectively final.

For example, suppose that the variable numberLength is not declared final, and you add the highlighted assignment statement in the PhoneNumber constructor:

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;
}

Because of this assignment statement, the variable numberLength is not effectively final anymore. As a result, the Java compiler generates an error message similar to "local variables referenced from an inner class must be final or effectively final" where the inner class PhoneNumber tries to access the numberLength variable:

if (currentNumber.length() == numberLength)

Starting in Java SE 8, if you declare the local class in a method, it can access the method's parameters. For example, you can define the following method in the PhoneNumber local class:

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

The method printOriginalNumbers accesses the parameters phoneNumber1 and phoneNumber2 of the method validatePhoneNumber

srk
  • 4,857
  • 12
  • 65
  • 109
mkobit
  • 43,979
  • 12
  • 156
  • 150
1

What's the problem with making it final as in

public void doStuff (final String userID)
1

declare the method

public void doStuff(final String userID)

The value needs to be final so that the compiler can be sure it doesn't change. This means the compiler can bind the value to the inner class at any time, without worrying about updates.

The value isn't changing in your code so this is a safe change.

mdma
  • 56,943
  • 12
  • 94
  • 128