【问题标题】:Why can an anonymous class access non-final class member of the enclosing class为什么匿名类可以访问封闭类的非最终类成员
【发布时间】:2015-06-11 06:46:10
【问题描述】:

我们知道在匿名类中只能访问 final 局部变量,这里有一个很好的理由:Why are only final variables accessible in anonymous class?

但是,我发现如果变量是封闭类的成员字段,匿名类仍然可以访问非 final 变量:How can I access enclosing class instance variables from inside the anonymous class?

我很困惑。我们确保在匿名类中只能访问最终的局部变量,因为我们不希望该变量在匿名类和局部函数之间不同步。如果我们尝试访问匿名类中的非最终封闭类成员,同样的原因应该适用于这种情况。

为什么不用担心?

【问题讨论】:

  • The same reason could apply to the case if we try to access non-final instance variable in anonymous class..... 这被称为从对象的引用中访问对象的字段。为什么对象的起源或其类的声明很重要?
  • @user2499800:你看到我的回答了吗?你在哪里接触到这个?如果您回复,将不胜感激... :)

标签: java closures final anonymous-class


【解决方案1】:

对于局部变量,变量的副本是匿名类实例接收的内容。出于这个原因,局部变量必须先设为final,然后才能在匿名类中使用,这样以后它的值就不会改变了。

对于封闭类的成员字段,没有副本。相反,匿名类获得对封闭类的引用,从而访问外部类的任何/所有成员字段和方法。所以即使字段的值发生变化,变化也会反映在匿名类中,因为它是 same 引用。

我很困惑。我们确保只有最终的局部变量可以 在匿名类中访问,因为我们不希望该变量 匿名类和本地函数之间应该不同步。这 如果我们尝试访问非决赛,同样的原因应该适用于该案例 在匿名类中包含类成员。

如您所见,情况并非如此。复制只发生在局部变量上,而不是封闭类的成员字段。 原因当然是匿名类拥有对封闭类的隐式引用,并且通过该引用它可以访问外部类的任何/所有成员字段和方法。

引用下面的链接:

成员变量存在于封闭对象的生命周期中,因此它可以被内部类实例引用。然而,局部变量仅在方法调用期间存在,并且编译器以不同的方式处理,因为它的隐式副本作为内部类的成员生成。如果不将局部变量声明为 final,则可能会对其进行更改,从而导致细微的错误,因为内部类仍然引用该变量的原始值。

参考资料:

1. Why a non-final “local” variable cannot be used inside an inner class, and instead a non-final field of the enclosing class can?.

【讨论】:

  • 您可以通过解释为什么可以使用参考而不是副本来改进您的答案。虽然这可能很明显。
  • "相反,匿名类实例获得对实际成员字段的引用。"不,它们只有对封闭实例的引用。
【解决方案2】:

非静态/内部类有对封闭实例的引用。因此它们可以隐式引用实例变量和方法。

如果它是一个参数,即使是封闭类也不知道它的任何信息,因为它只能从定义了这个变量的方法中访问。

喜欢 Y.S.已经指出:

在局部变量的情况下,变量的副本是匿名类实例得到的

【讨论】:

  • “非静态内部类”是一个重言式。如果类是静态的并在另一个类中声明,它们被称为“静态成员类”。如果它们不是静态的,则称为“内部类”。
  • @ErwinBolwidt:是的,我忘记了斜线。我实际上打算编写非静态/内部类。感谢您的提示。
【解决方案3】:

更多的是相反,一个局部变量,比如匿名实例之外的x,只要方法调用就存在。它的对象引用存储在调用堆栈中; x == 包含对象引用的堆栈上的地址。匿名实例中的x 是一个副本,因为它的生命周期不同,更长甚至更短。

由于现在有两个变量,因此决定不允许分配给x(实现起来很奇怪)并要求该变量是“有效的最终变量”。

访问外部成员,是因为匿名实例属于内部类,也包含 OuterClass.this 引用。

【讨论】:

    【解决方案4】:

    考虑下面的例子

    class InnerSuper{
        void mInner(){}
    }
    class Outer{
        int aOuter=10;
        InnerSuper mOuter(){
            int aLocal=3999;
            class Inner extends InnerSuper{
                int aInner=20;
                void mInner(){
                    System.out.println("a Inner : "+aInner);
                    System.out.println("a local : "+aLocal);
                }
            }
            Inner iob=new Inner(); 
            return iob;
        }
    }
    
    class Demo{
        public static void main(String args[]){
            Outer ob=new Outer();
            InnerSuper iob=ob.mOuter(); 
            iob.mInner();
        }
    }
    

    这不会在 Java 1.8 或更高版本中产生任何错误。但是在以前的版本中,这会产生一个错误,要求您将内部类中访问的局部变量显式声明为 final。 因为编译器所做的是保留内部类访问的局部变量的副本,这样即使方法/块结束并且局部变量超出范围,副本也会存在。它要求我们声明它final,因为如果变量在局部内部类匿名类声明后在程序中动态更改其值,编译器创建的副本不会更改为其新值,并且可能由于不产生预期的输出而导致内部类出现问题。因此它建议我们明确声明它是最终的。

    但在 Java 1.8 中它不会产生错误,因为编译器将访问的局部变量隐式声明为 final。 在Java Docs中是这样描述的

    匿名类不能访问其封闭的局部变量 未声明为最终或有效最终的范围。

    让我解释一下有效最终的含义。考虑上述程序的以下更改版本

    class Outer{
        int aOuter=10;
        InnerSuper mOuter(){
            int aLocal=3999;
            class Inner extends InnerSuper{
                int aInner=20;
                void mInner(){
                    System.out.println("a Inner : "+aInner);
                    System.out.println("a local : "+aLocal);
                }
            }
            aLocal=4000;
            Inner iob=new Inner(); 
            return iob;
        }
    }
    

    即使在 Java 1.8 中,这也会产生错误。这是因为 aLocal 是在程序中动态分配的。这意味着编译器不能将变量视为实际上是最终的。据我了解,编译器将未动态更改的变量声明为最终变量。这称为变量实际上是最终的

    因此,建议您将本地内部类或匿名类访问的局部变量显式声明为 final 以避免任何错误。

    【讨论】:

    • 我是初学者,你能解释一下第一个代码吗?我无法理解什么是 Innersuper muter(),因为你没有使用过
    • mOuterOuter 类的方法,它返回InnerSuper 类的实例。该方法在main方法中执行。如果这就是您需要澄清的原因。
    • 先生,如果我没记错的话,Inner 是一个本地内部类,但我知道我们无法将本地内部类传递给其他人。但在这里你能做到吗?还是我学错了“不能传递或返回本地内部类”。我相信这是由于本地内部类属于块(方法或 if 或 for)。请先生对此作出澄清。是的先生。我想问一下什么是mutter()。你所说的关于 mouter 的内容我很清楚什么是 mOuter。
    • 无法返回或传递本地内部类的原因是因为本地范围之外的任何其他类都无法访问类的定义,使其对范围之外的其他人无用。因此,Java 编译器被编写为不允许返回本地内部/匿名类。但是在这种情况下,我返回的是 InnerSuper Class 的子类实例,其中此类定义可用于此处的其他类。希望您了解如何将超/父类与子类实例一起使用。
    • 是的,先生,谢谢您,先生,我生动地完成了每一行。先生,我得到了澄清。
    猜你喜欢
    • 1970-01-01
    • 2012-07-23
    • 1970-01-01
    • 2013-11-13
    • 2015-04-16
    • 2023-03-13
    • 2023-03-29
    • 1970-01-01
    • 2011-06-11
    相关资源
    最近更新 更多