`
balaschen
  • 浏览: 192183 次
  • 性别: Icon_minigender_1
社区版块
存档分类
最新评论

InnerClass引用的外层local final变量,究竟具有什么语义

阅读更多
先看这段代码:
public class ShowAnonymousClass extends JFrame {

    Button myButton;

    int count;

    public ShowAnonymousClass() {
        super("Inner Class Frame");
        myButton = new Button("click me");
        final TextArea myTextArea = new TextArea(); //就是这句
        getContentPane().add(myButton, BorderLayout.CENTER);
        getContentPane().add(myTextArea, BorderLayout.NORTH);
        myButton.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent event){
                count++;
                myTextArea.setText("button clicked: " + count + " times");
            }
        });
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        ShowAnonymousClass mainFrame = new ShowAnonymousClass();
        mainFrame.setSize(300,300);
        mainFrame.setVisible(true);
    }
}

一个很简单的例子,使用一个匿名类来处理button的事件,本来以已经习惯于匿名类只能引用上层的final变量,突然想到,当构造函数ShowAnonymousClass执行完毕之后,myTextArea这个local变量应该就失效了,当点击按钮的时候,为啥myTextArea变量对TextArea对象的应用依然有效?final 变量的具体语义究竟是什么,google一把,尽是final变量的值或引用不能改变之类的说法,查了java 语法规范也没找到,谁来说说,local变量,加了final究竟多了什么魔法?莫非类似于闭包?
分享到:
评论
13 楼 balaschen 2007-01-12  
语义,错别字:)
12 楼 dengyin2000 2007-01-12  
balaschen 写道
引用

拷贝的是引用(也就是地址)。 如果不是定义成final,然后在其他地方对这个变量进行赋值的话, 这个变量的地址就变了。 那么这个匿名类里面的地址就有可能是空的。应该是这样理解吧。


感谢两位的讨论,现在差不多搞清了,对于不定义成final,如果在其他地方对变量进行赋值,这个变量的地址是变了,但匿名类里面的变量还是指向原先的地址(因为匿名类还引用它,所以不会被垃圾回收),和外层变量指向的地址不一致了,这样就产生了语意的不一致,而不是匿名类里面的地址就有可能是空的。


你这里的“语意”是指什么?

对, 这里不是为空, 而是不同的对象了。
11 楼 balaschen 2007-01-12  
引用

拷贝的是引用(也就是地址)。 如果不是定义成final,然后在其他地方对这个变量进行赋值的话, 这个变量的地址就变了。 那么这个匿名类里面的地址就有可能是空的。应该是这样理解吧。


感谢两位的讨论,现在差不多搞清了,对于不定义成final,如果在其他地方对变量进行赋值,这个变量的地址是变了,但匿名类里面的变量还是指向原先的地址(因为匿名类还引用它,所以不会被垃圾回收),和外层变量指向的地址不一致了,这样就产生了语意的不一致,而不是匿名类里面的地址就有可能是空的。
10 楼 歆渊 2007-01-11  
dengyin2000 写道

PS: 看不懂javap之后的东西。

没有搞过汇编语言的话直接看JVM指令可能有点费劲, 翻译成Java语法就是这样:

public class ShowAnony
{
  public void test(final int n)
  {
    new ShowAnony$1(this, n).run();
  }
}

final class ShowAnony$1 implements java.lang.Runnable
{

  final int val$n;

  final ShowAnony this$0;

  ShowAnony$1(ShowAnony this$0, int n)
  {
    this.this$0 = this$0;
    this.val$n = n;
  }

  public void run()
  {
    this.val$n + 1;
  }

}

9 楼 dengyin2000 2007-01-11  
complystill 写道
balaschen 写道

运行这段代码可以发现,在其中一个innerClass内修改CountValue的值,会影响另外一个innerClass引用的CountValue,很显然,这两个innerClass的CountValue引用,指向同一个对象,这和拷贝上层的local变量引用的对象到innerClass内部,显然是矛盾的,还是搞不清楚Java的local变量的作用域问题

不矛盾, 拷贝的是到对象的引用, 而不是克隆复制对象.

这其实是编译器的特殊处理. 最简单的研究办法, 写这么一个类:
public class ShowAnony
{
  public void test(final int n)
  {
    new Runnable()
    {
      public void run()
      {
        int m = n+1;
      }
    }.run();
  }
}

编译以后再 javap:
D:\temp>javap -c ShowAnony$1
Compiled from "ShowAnony.java"
final class ShowAnony$1 extends java.lang.Object implements java.lang.Runnable{
final int val$n;

final ShowAnony this$0;

ShowAnony$1(ShowAnony, int);
  Code:
   0:   aload_0
   1:   aload_1
   2:   putfield        #1; //Field this$0:LShowAnony;
   5:   aload_0
   6:   iload_2
   7:   putfield        #2; //Field val$n:I
   10:  aload_0
   11:  invokespecial   #3; //Method java/lang/Object."<init>":()V
   14:  return

public void run();
  Code:
   0:   aload_0
   1:   getfield        #2; //Field val$n:I
   4:   iconst_1
   5:   iadd
   6:   istore_1
   7:   return

}


一切都清楚了.

要求必须加 final 应该是为了语义的一致性. 不然匿名类被构造好以后, 上层变量如果被重新赋值, 但匿名类从语义上引用外层变量, 值却还是它构造时候的, 逻辑上就错误了.


拷贝的是引用(也就是地址)。 如果不是定义成final,然后在其他地方对这个变量进行赋值的话, 这个变量的地址就变了。 那么这个匿名类里面的地址就有可能是空的。应该是这样理解吧。

PS: 看不懂javap之后的东西。
8 楼 歆渊 2007-01-11  
balaschen 写道

运行这段代码可以发现,在其中一个innerClass内修改CountValue的值,会影响另外一个innerClass引用的CountValue,很显然,这两个innerClass的CountValue引用,指向同一个对象,这和拷贝上层的local变量引用的对象到innerClass内部,显然是矛盾的,还是搞不清楚Java的local变量的作用域问题

不矛盾, 拷贝的是到对象的引用, 而不是克隆复制对象.

这其实是编译器的特殊处理. 最简单的研究办法, 写这么一个类:
public class ShowAnony
{
  public void test(final int n)
  {
    new Runnable()
    {
      public void run()
      {
        int m = n+1;
      }
    }.run();
  }
}

编译以后再 javap:
D:\temp>javap -c ShowAnony$1
Compiled from "ShowAnony.java"
final class ShowAnony$1 extends java.lang.Object implements java.lang.Runnable{
final int val$n;

final ShowAnony this$0;

ShowAnony$1(ShowAnony, int);
  Code:
   0:   aload_0
   1:   aload_1
   2:   putfield        #1; //Field this$0:LShowAnony;
   5:   aload_0
   6:   iload_2
   7:   putfield        #2; //Field val$n:I
   10:  aload_0
   11:  invokespecial   #3; //Method java/lang/Object."<init>":()V
   14:  return

public void run();
  Code:
   0:   aload_0
   1:   getfield        #2; //Field val$n:I
   4:   iconst_1
   5:   iadd
   6:   istore_1
   7:   return

}


一切都清楚了.

要求必须加 final 应该是为了语义的一致性. 不然匿名类被构造好以后, 上层变量如果被重新赋值, 但匿名类从语义上引用外层变量, 值却还是它构造时候的, 逻辑上就错误了.
7 楼 balaschen 2006-12-26  
今天又做了个实验,发现当local变量被innerClass引用的时候,在innerClass的生命周期没结束前,是不会被垃圾回收的:
public class ShowAnonymousClass extends JFrame {

    private TextArea myTextArea;

    public ShowAnonymousClass() {
        super("Inner Class Frame");
        Button button1 = new Button("button1");
        myTextArea = new TextArea();
        getContentPane().add(button1, BorderLayout.WEST);
        getContentPane().add(myTextArea, BorderLayout.NORTH);
        
        final CountValue count = new CountValue(0);
        
        button1.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent event){
            	count.incValue();
                myTextArea.setText(myTextArea.getText()+"\nbutton1 clicked: " + count.getCount() + " times");
            }
        });
        
        Button button2 = new Button("button2");
        getContentPane().add(button2, BorderLayout.EAST);
        button2.addActionListener(new ActionListener(){
            public void actionPerformed(ActionEvent event){
                count.incValue();
                myTextArea.setText(myTextArea.getText()+"\nbutton2 clicked: " + count.getCount() + " times");
            }
        });
        
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
    }

    public static void main(String[] args) {
        ShowAnonymousClass mainFrame = new ShowAnonymousClass();
        mainFrame.setSize(300,300);
        mainFrame.setVisible(true);
    }
    
    public class CountValue{
    	private int count;
    	public CountValue(int count){
    		this.count = count;
    	}
    	
    	public int getCount(){
    		return this.count;
    	}
    	
    	public void incValue(){
    		this.count ++;
    	}
    }
}

运行这段代码可以发现,在其中一个innerClass内修改CountValue的值,会影响另外一个innerClass引用的CountValue,很显然,这两个innerClass的CountValue引用,指向同一个对象,这和拷贝上层的local变量引用的对象到innerClass内部,显然是矛盾的,还是搞不清楚Java的local变量的作用域问题
6 楼 balaschen 2006-12-25  
怎么没人讨论,都跑去过生蛋节了?
5 楼 balaschen 2006-12-21  
http://forum.java.sun.com/thread.jspa?threadID=736802&messageID=4232040
这个帖子说法和我上面提到的类似,有没有权威的文档来证明这种说法呢?
4 楼 balaschen 2006-12-21  
在一个台湾人的论坛上看到,解释的还比较合理:
http://www.javaworld.com.tw/jute/post/view?bid=29&id=66043&sty=3&age=0&tpg=1&ppg=1
大致的意思是说,innerClass对于访问的上层local变量,因为local变量的生命周期的原因,是无法直接访问的,因此,会把local变量拷贝一个副本到innerClass内作为一个field保存起来,这样一来,innerClass内部使用的那个local变量,和外层的并不是同个变量,在innerClass内部修改innerClass引用local变量的值(假设允许修改的话),并不会影响外层的local变量的值,这样就很容易引起混淆,因此,对innerClass引用的local变量加上final修饰符,限制这些变量的值不能改变,这样一来,语义就统一了,这也可以理解为什么innerClass可以直接引用外层类的变量,因为innerClass可以透过其持有的上层类实例的引用来访问其成员变量。
public class ShowAnonymousClass extends JFrame {   
  
    Button myButton;   
    private TextArea myTextArea; //把myTextArea定义为成员变量
    int count;   
  
    public ShowAnonymousClass() {   
        super("Inner Class Frame");   
        myButton = new Button("click me");   
        myTextArea = new TextArea(); //去掉final
        getContentPane().add(myButton, BorderLayout.CENTER);   
        getContentPane().add(myTextArea, BorderLayout.NORTH);   
        myButton.addActionListener(new ActionListener(){   
            public void actionPerformed(ActionEvent event){   
                count++;   
                myTextArea.setText("button clicked: " + count + " times");   
            }   
        });   
        setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);   
    }   
  
    public static void main(String[] args) {   
        ShowAnonymousClass mainFrame = new ShowAnonymousClass();   
        mainFrame.setSize(300,300);   
        mainFrame.setVisible(true);   
    }   
}  


如果这种说法正确的话,final 变量并没有额外的语义,而是innerClass有点类似于动态语言的闭包,从这点上看,似乎没有必要把innerClass引用的上层local变量规定为必须final,不final不是更灵活么?
3 楼 balaschen 2006-12-21  
如果final的local变量没有特殊的语义,innerClass似乎也没必要要求上层的local变量必须final吧,继续google
2 楼 balaschen 2006-12-21  
myTextArea指向的对象当然是存在的,但myTextArea作为local变量,构造函数执行完毕,本身应该实效,被垃圾回收。变量失效和变量指向的对象失效,是两回事吧。
1 楼 sorphi 2006-12-21  
>>当构造函数ShowAnonymousClass执行完毕之后,myTextArea这个local变量应该就失效了


getContentPane().add(myTextArea, BorderLayout.NORTH);

myTextArea指向的对象被其他的对象(contentPane)钩住了

相关推荐

Global site tag (gtag.js) - Google Analytics