【问题标题】:Java: Anonymous inner class using a local variableJava:使用局部变量的匿名内部类
【发布时间】:2010-07-14 22:21:33
【问题描述】:

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

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

我收到此错误:

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

我很确定我不能将它分配为 final,因为它是一个具有未知值的变量。我听说这种语法确实以某种方式保留了作用域,所以我认为一定有一个我还不知道的语法技巧。

【问题讨论】:

  • 太糟糕的 java 需要 final 在这里。编译器很明显局部变量没有改变,它不应该要求编码器确认这一点。如果 anon 类写入局部变量,编译警告就足够了。糟糕的设计选择。

标签: java scope anonymous-class


【解决方案1】:

正如这里的其他人所说,局部变量必须是 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。通过要求局部变量是最终的,这种令人困惑的情况不会出现。

【讨论】:

  • 问题:annon 内部类只自动复制它使用的final 局部变量,正确吗?不是它不直接使用的其他本​​地(最终或非最终)变量的副本?我问是因为我担心内存泄漏。
【解决方案2】:

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

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

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

【讨论】:

  • 嗯,好的。我想我真的不明白final 声明是如何工作的。谢谢。
【解决方案3】:

在 Java 8 中,这发生了一些变化。您现在可以访问实际上是最终变量的变量。来自Oracle documentation 的相关 sn-p 和示例(强调我的):

但是,从 Java SE 8 开始,本地类可以访问本地 封闭块的变量和参数是最终的或 实际上是最终的

Effectively final: 一个非final变量或参数,其值在初始化后永远不会改变是有效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 编译器生成一个 类似于“从内部引用的局部变量”的错误消息 类必须是最终的或有效的最终”,其中内部类 PhoneNumber 尝试访问 numberLength 变量:

if (currentNumber.length() == numberLength)

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

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

方法printOriginalNumbers访问参数 方法validatePhoneNumberphoneNumber1phoneNumber2

【讨论】:

    【解决方案4】:

    将其设为final 有什么问题

    public void doStuff (final String userID)
    

    【讨论】:

      【解决方案5】:

      声明方法

      public void doStuff(final String userID)
      

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

      代码中的值没有改变,所以这是一个安全的改变。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2018-10-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多