从一次意外开始说java匿名内部类

java的内部类、匿名类本来以为自己用的已经很溜了, 结果, 就在昨天晚上12点来钟发生了重大事故。要说事故的严重性呢,那就是导致我一晚上没有睡着觉。

那下面先用一段模拟代码来描述下我出现的问题的:

public class Test {
	public static void main(String[] args) throws InterruptedException {
		View v = new View();
		v.show(1);
		Thread.sleep(500);
		v.mTextView.execute();
		Thread.sleep(1000);
		v.show(2);
		Thread.sleep(500);
		v.mTextView.execute();
	}
}

class View {
	public TextView mTextView;
	
	public void show(int position) {
		if(mTextView == null) {
			mTextView = new TextView();
			mTextView.setListener(new Listener() {
				@Override
				public void click() {
					System.out.println("position = " + position);
				}
			});
		}
		
		mTextView.show();
	}
}

class TextView {
	private Listener mListener;
	
	public void setListener(Listener l) {
		mListener = l;
	}
	
	public void execute() {
		mListener.click();
	}
	
	public void show() {
		System.out.println("textview show...");
	}
}

interface Listener {
	public void click();
}

不出意外的话, console肯定是打印的1  2, 但是偏偏就在这困扰到我了,打印的结果是1  1, 仔细顺一下代码,我们就应该去思考这个匿名内部类到底是怎么使用的外部类那个方法的参数的。

从打印的结果上看, 我猜想肯定是在这个内部类的实例中保存了position参数,那带着这个猜想,我们来debug一下程序。

技术分享

这是第一次执行到的时候, 发现什么问题了吗。 在mListener中竟然有一个和position相关的变量。到这里,我们感觉那个猜测可能是正确的。再往下思考,既然在mListener对象中保存了这个变量,那么下次执行到,同一个对象,所以变量肯定是相同的,这样也就解开我们的疑惑了。

技术分享


总结一下:

在我们new一个匿名内部类的时候,如果使用了方法中的东西,那么jvm会给我们的匿名类加一个构造方法,并且将这个参数传递进来,例如上面的例子中:

class View$Listener {
	public View$Listener(int position) {
		this.Listener$position = position;
	}
	
	public void click() {
		...
	}
}

既然知道了这些,那么我们的疑惑也就解开了, 那上面的问题怎么解决呢? 其实我们很早之前就已经知道解决方法了,想想android中自定义Adapter的getView()方法,你会恍然大悟。上面的问题怎么解决呢?就是把setListener放到if后面,而不是里面。

public class Test {
	public static void main(String[] args) throws InterruptedException {
		View v = new View();
		v.show(1);
		Thread.sleep(500);
		v.mTextView.execute();
		Thread.sleep(1000);
		v.show(2);
		Thread.sleep(500);
		v.mTextView.execute();
	}
}

class View {
	public TextView mTextView;
	
	public void show(int position) {
		if(mTextView == null) {
			mTextView = new TextView();
//			mTextView.setListener(new Listener() {
//				@Override
//				public void click() {
//					System.out.println("position = " + position);
//				}
//			});
		}
		
		mTextView.setListener(new Listener() {
			@Override
			public void click() {
				System.out.println("position = " + position);
			}
		});
		
		mTextView.show();
	}
}

class TextView {
	private Listener mListener;
	
	public void setListener(Listener l) {
		mListener = l;
	}
	
	public void execute() {
		mListener.click();
	}
	
	public void show() {
		System.out.println("textview show...");
	}
}

interface Listener {
	public void click();
}

这样在每次调用show方法时,都会重新new一个Listener内部类。


郑重声明:本站内容如果来自互联网及其他传播媒体,其版权均属原媒体及文章作者所有。转载目的在于传递更多信息及用于网络分享,并不代表本站赞同其观点和对其真实性负责,也不构成任何其他建议。