Java:使用局部变量的匿名内部类

时间:2010-07-14 22:21:34

标签: java scope anonymous-class

如何在我的匿名内部子类中获取传递给此方法的userId的值?

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

我收到此错误:

  

不能引用在不同方法中定义的内部类中的非最终变量userID

我很确定我不能将它指定为final,因为它是一个具有未知值的变量。我听说这种语法确实以某种方式保留了范围,所以我认为必须有一个我还不太了解的语法技巧。

5 个答案:

答案 0 :(得分:11)

正如其他所有人所说的那样,局部变量必须是最终才能被内部类访问。

这是(基本上)为什么...如果你写下面的代码(长答案,但是,在底部,你可以得到短版本: - ):

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,但它实际上会打印出来。通过要求局部变量是最终的,这种混乱的情况不会出现。

答案 1 :(得分:7)

当然你可以将它指定为final - 只需将该关键字放在参数的声明中:

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

我不确定你认为它是一个具有未知值的变量是什么意思;最后一个意思是,一旦为变量赋值,就不能重新分配。由于您没有在方法中更改userID的值,因此在这种情况下将其设置为final是没有问题的。

答案 2 :(得分:1)

中制作final有什么问题
public void doStuff (final String userID)

答案 3 :(得分:1)

声明方法

public void doStuff(final String userID)

值必须是最终的,以便编译器可以确定它不会更改。这意味着编译器可以随时将值绑定到内部类,而无需担心更新。

您的代码中的值不会发生变化,因此这是一个安全的更改。

答案 4 :(得分:1)

在Java 8中,这已经发生了一些变化。您现在可以访问有效最终的变量。来自Oracle documentation(强调我的)的相关摘录和示例:

  

但是,从Java SE 8开始,本地类可以访问本地   封闭块的变量和参数是final或   有效的最终值永远不变的变量或参数   初始化后的更改实际上是最终的。例如,   假设变量numberLength未被声明为final,而您   在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编译器生成了一个   类似于"从内部引用的局部变量的错误消息   课程必须是最终的或有效的最终成绩#34;内在的地方   PhoneNumber尝试访问numberLength变量:

if (currentNumber.length() == numberLength)
     

从Java SE 8开始,如果在方法中声明本地类,则为   可以访问方法的参数。例如,您可以定义   PhoneNumber本地类中的以下方法:

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

方法printOriginalNumbers访问参数   方法phoneNumber1

phoneNumber2validatePhoneNumber