【问题标题】:Declaring class inside a method - Final keyword [duplicate]在方法中声明类-Final关键字[重复]
【发布时间】:2013-04-15 13:45:38
【问题描述】:

给定方法中的以下内部类 (IsSomething):

public class InnerMethod {

private int x;


public class Something {
    private int y;

    public void printMyNumber(double x)
    {
        class IsSomething extends Something {

            public void print() {
                System.out.println(x);
            }

        }
    }
}

}

为什么 X 变量必须是 FINAL 才能使其工作..? (我说的是“printMyNumber”函数的X参数。)

【问题讨论】:

    标签: java


    【解决方案1】:

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

    Final local variables

    我知道创建一个局部变量或一个 参数最终。第一个原因是你不想要你的代码 改变局部变量或参数。许多人认为 更改方法内的参数是不好的风格,因为它使 代码不清楚。作为一种习惯,一些程序员使他们的所有参数 “final”以防止自己改变它们。我不这样做, 因为我发现它让我的方法签名有点难看。

    第二个原因是当我们想要访问一个局部变量或 内部类中的参数。这是真正的原因,因为 据我所知,最终的局部变量和参数是 在 JDK 1.1 中引入 Java 语言。

    public class Access1 {
      public void f() {
        final int i = 3;
        Runnable runnable = new Runnable() {
        public void run() {
          System.out.println(i);
        }
        };
      }
    }
    

    在 run() 方法中,只有在外部类中将 i 设为 final 时,我们才能访问它。为了理解推理,我们必须

    看看编译器做了什么。它产生两个文件,Access1.class 和 Access1$1.class。当我们用 JAD 反编译它们时,我们得到:

    public class Access1 {
      public Access1() {}
      public void f() {
        Access1$1 access1$1 = new Access1$1(this);
      }
    }
    

    class Access1$1 implements Runnable {
      Access1$1(Access1 access1) {
        this$0 = access1;
      }
      public void run() {
        System.out.println(3);
      }
      private final Access1 this$0;
    }
    

    由于 i 的值是最终的,编译器可以将其“内联”到内部

    类。局部变量必须是最终的,这让我感到不安 由内部类访问,直到我看到上面的内容。

    当局部变量的值可以根据不同的情况改变时 内部类的实例,编译器将其添加为 内部类并让它在构造函数中初始化。这 这背后的根本原因是Java没有指针, C 的方式。

    考虑以下类:

    public class Access2 {
      public void f() {
        for (int i=0; i<10; i++) {
        final int value = i;
        Runnable runnable = new Runnable() {
          public void run() {
            System.out.println(value);
          }
        };
        }
      }
    } 
    

    这里的问题是我们每次通过for循环都必须创建一个新的本地数据成员,所以我今天有一个想法

    在编码的时候,就是把上面的代码改成下面这样:

    public class Access3 {
      public void f() {
        Runnable[] runners = new Runnable[10];
        for (final int[] i={0}; i[0]<runners.length; i[0]++) {
        runners[i[0]] = new Runnable() {
          private int counter = i[0];
          public void run() {
            System.out.println(counter);
          }
        };
        }
        for (int i=0; i<runners.length; i++)
        runners[i].run();
      }
      public static void main(String[] args) {
        new Access3().f();
      }
    }
    

    我们现在不必声明额外的最终局部变量。事实上,这也许不是真的

    int[] i 就像一个指向 int 的通用 C 指针?我花了4年时间 看到这个,但如果你听说过这个想法,我想听听你的意见 其他地方。

    【讨论】:

    • 所以通过声明它是最终的。编译器是做什么的?保持它..?它不会在函数范围的末尾被销毁吗?
    • 全部阅读 :) 很好。关于这一行: System.out.println(3);它真的只是用 3 替换了 i 最终变量吗?内联它?
    • @Rouki 我不认为它将 i 替换为 3.final 允许 Java 编译器在运行时“捕获”变量的值并将副本作为字段存储在内部类中。
    【解决方案2】:

    匿名类中的方法实际上无法访问局部变量和方法参数。相反,当实例化匿名类的对象时,对象方法引用的最终局部变量和方法参数的副本作为实例变量存储在对象中。匿名类对象中的方法真正访问那些隐藏的实例变量。

    来自 JLS:

    Any local variable, formal method parameter or exception handler parameter used but not declared in an inner class must be declared final. Any local variable, used but not declared in an inner class must be definitely assigned (§16) before the body of the inner class.

    【讨论】:

    • 好的,所以编译器确实将最终变量保留在当前对象中。对吗?
    • 该值被复制到匿名内部类的实例中,因此一旦将代码复制到方法内部但类外部不应修改它,否则值将不同步。
    • 题外话。内部类会自动扩展其中的类吗?
    • 不,它们是完全不同的概念。
    • @noob:我想问一下外部类的扩展是如何工作的,就像这个问题一样?我的意思是内部类如何在外部类中定义自身。?
    【解决方案3】:

    这是因为本地类实例的生命周期可能比定义该类的方法的执行时间长得多。出于这个原因,本地类必须拥有它使用的所有局部变量的私有内部副本(这些副本由编译器自动生成)。确保局部变量和私有副本始终相同的唯一方法是坚持局部变量是最终的。

    【讨论】:

      猜你喜欢
      • 2013-02-25
      • 2016-12-20
      • 1970-01-01
      • 2021-02-06
      • 1970-01-01
      • 1970-01-01
      • 2011-03-06
      • 2020-03-24
      • 1970-01-01
      相关资源
      最近更新 更多