3

给定方法中的以下内部类(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 参数。)

4

3 回答 3

1

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

从 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.

于 2013-04-15T05:38:34.043 回答
1

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

最终局部变量

我知道将局部变量或参数设为 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() 方法中,只有在外部类中将其设为 final 时,我们才能访问 i。要理解其中的道理,我们必须

看看编译器做了什么。它生成两个文件,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 年时间才看到这一点,但如果您在其他地方听到过这个想法,我想听听您的意见。

于 2013-04-15T05:39:06.177 回答
0

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

于 2013-04-15T05:44:34.063 回答