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

9

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

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

我遇到了这个错误:
不能在定义在另一个方法中的内部类中引用非终态变量userID
我很确定我不能把它赋值为final,因为它是一个未知值的变量。我听说这种语法在某种程度上保留了作用域,所以我认为必须有一种我还不太了解的语法技巧。

太糟糕了,Java在这里需要使用final。编译器很明显可以知道局部变量没有被更改,它不应该要求程序员确认。如果匿名类写入局部变量,则编译警告就足够了。非常糟糕的设计选择。 - irreputable
可能是为什么只有final变量可以在匿名类中访问?的重复问题。 - user719662
5个回答

10

正如其他人所说,内部类访问局部变量必须是 final。

以下是原因(基本上)... 如果您编写以下代码(长答案,但在底部您可以获得简短版本 :-):

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。通过要求局部变量为final,就可以避免这种混淆的情况。

问题:匿名内部类只会自动复制它使用的final局部变量,而不是其他它没有直接使用的局部变量,这是正确的吗?我问这个问题是因为我担心会出现内存泄漏。 - Tony Chan

7

当然可以将其分配为final - 只需在参数声明中放置该关键字:

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

我不确定你所说的未知值变量是什么意思;final只是表示一旦将值分配给变量,它就不能被重新分配。由于您在方法中没有更改userID的值,在这种情况下将其设置为final是没有问题的。


啊,好的。我想我并没有真正理解final声明的工作原理。谢谢。 - Alex Wayne

2
在Java 8中,这方面有了一些变化。您现在可以访问“有效final”的变量。以下是来自Oracle文档的相关代码片段和示例(重点是我的):

然而,从Java SE 8开始,局部类可以访问封闭块的本地变量和参数,这些变量和参数是final或有效final的。

有效final:一个非final变量或参数,在初始化后其值从未更改过,因此是有效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


1

将其设置为final有什么问题呢?

public void doStuff (final String userID)

1

声明方法

public void doStuff(final String userID)

值需要是final的,这样编译器才能确保它不会改变。这意味着编译器可以随时将该值绑定到内部类,而不必担心更新。

在您的代码中,该值不会更改,因此这是一个安全的更改。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接