论坛首页 Java企业应用论坛

java匿名内部类使用场景列举

浏览 9238 次
精华帖 (0) :: 良好帖 (0) :: 新手帖 (0) :: 隐藏帖 (0)
作者 正文
   发表时间:2011-11-22  
Ben.Sin 写道
kidneyball 写道
kidneyball 写道
在java8出来之前,内部类最本质的作用是可以访问外部对象的状态和外部方法的final变量。换言之,模拟闭包。
其它特性都是单纯节省代码量,只有这个作用是暂时无法替代的。


嗯,还有一种作用,private的static内部类提供了一种“类私有”的可见性,而private的非static内部类提供了“对象私有”的可见性,这也是暂时在java里没有替代方案的(scala里就有)。



弱弱的问一句,final变量的访问跟匿名内部类有什么关系?


一个例子: http://www.jmock.org/returning.html 里的 Returning Iterators over Collections 一节
final List<Employee> employees = new ArrayList<Employee>();
employees.add(alice);
employees.add(bob);

context.checking(new Expectations() {{
    oneOf (department).employees(); will(returnIterator(employees));
}});


在new出来的Exprectations类型的匿名类对象中,直接访问了它的外部方法的employees局部变量,并且持有了这个变量(即使外部方法结束了,这个final变量的值还继续存在,成为了这个匿名类对象的“自由变量”)。这已经有点闭包的意思了。类似于javascript里的:
function outer() {
   var a = 1;
   return function() {alert(a)}
}


不过在java里有个限制(据说是为了效率,java8的lambda也可能有这个限制),如果在内部类对象中访问外部方法的局部变量时,该变量必须声明为final。如果是访问外部对象的对象域(fields),则没有这个限制。
0 请登录后投票
   发表时间:2011-11-22  
由事件驱动的系统,采用内部类方式比较多,在swing\AWT频繁使用,在J2EE中其实ENUM有点变异类型的内部类,个人观点而已
0 请登录后投票
   发表时间:2011-11-22  
kidneyball 写道
Ben.Sin 写道
kidneyball 写道
kidneyball 写道
在java8出来之前,内部类最本质的作用是可以访问外部对象的状态和外部方法的final变量。换言之,模拟闭包。
其它特性都是单纯节省代码量,只有这个作用是暂时无法替代的。


嗯,还有一种作用,private的static内部类提供了一种“类私有”的可见性,而private的非static内部类提供了“对象私有”的可见性,这也是暂时在java里没有替代方案的(scala里就有)。



弱弱的问一句,final变量的访问跟匿名内部类有什么关系?


一个例子: http://www.jmock.org/returning.html 里的 Returning Iterators over Collections 一节
final List<Employee> employees = new ArrayList<Employee>();
employees.add(alice);
employees.add(bob);

context.checking(new Expectations() {{
    oneOf (department).employees(); will(returnIterator(employees));
}});


在new出来的Exprectations类型的匿名类对象中,直接访问了它的外部方法的employees局部变量,并且持有了这个变量(即使外部方法结束了,这个final变量的值还继续存在,成为了这个匿名类对象的“自由变量”)。这已经有点闭包的意思了。类似于javascript里的:
function outer() {
   var a = 1;
   return function() {alert(a)}
}


不过在java里有个限制(据说是为了效率,java8的lambda也可能有这个限制),如果在内部类对象中访问外部方法的局部变量时,该变量必须声明为final。如果是访问外部对象的对象域(fields),则没有这个限制。


这个不是那么认同,持有者的还是外部类,final只不过是定义变量指针不能被改变而已,不管外部还是内部,只是一个访问域的问题,作为参数调用非内部类也可以访问同一内存块,跟final不沾边。
与其说其一特性是访问final变量,还不如说可以直接访问private 变量更为合适。
0 请登录后投票
   发表时间:2011-11-23   最后修改:2011-11-23
Ben.Sin 写道
kidneyball 写道
Ben.Sin 写道
kidneyball 写道
kidneyball 写道
在java8出来之前,内部类最本质的作用是可以访问外部对象的状态和外部方法的final变量。换言之,模拟闭包。
其它特性都是单纯节省代码量,只有这个作用是暂时无法替代的。


嗯,还有一种作用,private的static内部类提供了一种“类私有”的可见性,而private的非static内部类提供了“对象私有”的可见性,这也是暂时在java里没有替代方案的(scala里就有)。



弱弱的问一句,final变量的访问跟匿名内部类有什么关系?


一个例子: http://www.jmock.org/returning.html 里的 Returning Iterators over Collections 一节
final List<Employee> employees = new ArrayList<Employee>();
employees.add(alice);
employees.add(bob);

context.checking(new Expectations() {{
    oneOf (department).employees(); will(returnIterator(employees));
}});


在new出来的Exprectations类型的匿名类对象中,直接访问了它的外部方法的employees局部变量,并且持有了这个变量(即使外部方法结束了,这个final变量的值还继续存在,成为了这个匿名类对象的“自由变量”)。这已经有点闭包的意思了。类似于javascript里的:
function outer() {
   var a = 1;
   return function() {alert(a)}
}


不过在java里有个限制(据说是为了效率,java8的lambda也可能有这个限制),如果在内部类对象中访问外部方法的局部变量时,该变量必须声明为final。如果是访问外部对象的对象域(fields),则没有这个限制。


这个不是那么认同,持有者的还是外部类,final只不过是定义变量指针不能被改变而已,不管外部还是内部,只是一个访问域的问题,作为参数调用非内部类也可以访问同一内存块,跟final不沾边。
与其说其一特性是访问final变量,还不如说可以直接访问private 变量更为合适。


“final的局部变量”和“private的对象域”。局部变量是指在语句块(statement block)内部定义,通常情况下作用域为当前语句块的那些变量。。。请看下面例子:
public class Test {

	public static interface MyInterface {
		void action();
	}
	
	private static MyInterface newAction(final int b /*必须是final*/) {
		MyInterface action;
		{
			final int a = 10; // 必须是final

			action = new MyInterface() {
				public void action() {
					System.out.println(a+b); //内部类对象直接引用外部方法的局部变量
				}
			};
		}
		
		//System.out.println(a);   //编译错误,已经超出a的作用域了
		return action;
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MyInterface actor = newAction(5);
		
		actor.action();  //结果为15, 现在局部变量a和b被actor对象持有了(作为其自由变量)
	}
}

你试试把newAction方法里的final去掉,会出现编译错误。

我不是说“一定要用内部类来访问final局部变量”(非内部类的代码中也可以访问局部变量),而是说“java规定了内部类如果要访问外部方法的局部变量,这个局部变量必须是final”。
0 请登录后投票
   发表时间:2011-11-23  
kidneyball 写道
Ben.Sin 写道
kidneyball 写道
Ben.Sin 写道
kidneyball 写道
kidneyball 写道
在java8出来之前,内部类最本质的作用是可以访问外部对象的状态和外部方法的final变量。换言之,模拟闭包。
其它特性都是单纯节省代码量,只有这个作用是暂时无法替代的。


嗯,还有一种作用,private的static内部类提供了一种“类私有”的可见性,而private的非static内部类提供了“对象私有”的可见性,这也是暂时在java里没有替代方案的(scala里就有)。



弱弱的问一句,final变量的访问跟匿名内部类有什么关系?


一个例子: http://www.jmock.org/returning.html 里的 Returning Iterators over Collections 一节
final List<Employee> employees = new ArrayList<Employee>();
employees.add(alice);
employees.add(bob);

context.checking(new Expectations() {{
    oneOf (department).employees(); will(returnIterator(employees));
}});


在new出来的Exprectations类型的匿名类对象中,直接访问了它的外部方法的employees局部变量,并且持有了这个变量(即使外部方法结束了,这个final变量的值还继续存在,成为了这个匿名类对象的“自由变量”)。这已经有点闭包的意思了。类似于javascript里的:
function outer() {
   var a = 1;
   return function() {alert(a)}
}


不过在java里有个限制(据说是为了效率,java8的lambda也可能有这个限制),如果在内部类对象中访问外部方法的局部变量时,该变量必须声明为final。如果是访问外部对象的对象域(fields),则没有这个限制。


这个不是那么认同,持有者的还是外部类,final只不过是定义变量指针不能被改变而已,不管外部还是内部,只是一个访问域的问题,作为参数调用非内部类也可以访问同一内存块,跟final不沾边。
与其说其一特性是访问final变量,还不如说可以直接访问private 变量更为合适。


“final的局部变量”和“private的对象域”。局部变量是指在语句块(statement block)内部定义,通常情况下作用域为当前语句块的那些变量。。。请看下面例子:
public class Test {

	public static interface MyInterface {
		void action();
	}
	
	private static MyInterface newAction(final int b /*必须是final*/) {
		MyInterface action;
		{
			final int a = 10; // 必须是final

			action = new MyInterface() {
				public void action() {
					System.out.println(a+b); //内部类对象直接引用外部方法的局部变量
				}
			};
		}
		
		//System.out.println(a);   //编译错误,已经超出a的作用域了
		return action;
	}
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		MyInterface actor = newAction(5);
		
		actor.action();  //结果为15, 现在局部变量a和b被actor对象持有了(作为其自由变量)
	}
}

你试试把newAction方法里的final去掉,会出现编译错误。

我不是说“一定要用内部类来访问final局部变量”(非内部类的代码中也可以访问局部变量),而是说“java规定了内部类如果要访问外部方法的局部变量,这个局部变量必须是final”。


这个例子刚好说明一个问题,内部类访问外部类的变量必须强制定义外部类变量为final,而非内部类的某一特性。
这种说法是一种本末倒置。

为什么需要定义final,其中一个原因我想是避免在多线程环境下造成外部变量被置空而导致空指针异样。

http://java.sun.com/docs/books/jls/third_edition/html/classes.html中有这么一段

Inner classes include local (§14.3), anonymous (§15.9.5) and non-static member classes (§8.5). Here are some examples:

    class Outer {
    int i = 100;
    static void classMethod() {
    final int l = 200;
    class LocalInStaticContext{
    int k = i; // compile-time error
    int m = l; // ok
    }
    }
   
    void foo() {
    class Local { // a local class
    int j = i;
    }
    }
    }

The declaration of class LocalInStaticContext occurs in a static context-within the static method classMethod. Instance variables of class Outer are not available within the body of a static method. In particular, instance variables of Outer are not available inside the body of LocalInStaticContext. However, local variables from the surrounding method may be referred to without error (provided they are marked final).
0 请登录后投票
   发表时间:2011-11-24   最后修改:2011-11-24
"内部类访问外部类的变量必须强制定义外部类变量为final,而非内部类的某一特性。"。这个是各人理解问题了,反正就是这么简单一个事情,我把“可以访问外部的final局部变量”理解为特性,你把“被访问的外部局部变量必须为fianl”理解为限制。我觉得都无可厚非,而且不是重点。

重点是,这种写法所带来的效果(延长局部变量的生命周期),在java里暂时没有其他语言特性可以代替。例如说,jmock (www.jmock.org)库以及其他大部分mock库的官方推荐使用方式就是基于这种写法。

另外,按照2010年10月openjdk正在开发的lambda特性的说法,不允许访问非final局部变量确实与多线程有关,主要是怕这个lambda(或者内部类对象)被传递到外部线程,而lambda本身难以加锁,造成同步问题。

http://cr.openjdk.java.net/~briangoetz/lambda/lambda-state-3.html

引用

6. Local variable capture
The current rules for capturing local variables of enclosing contexts in inner classes are quite restrictive; only final variables may be captured. For lambda expressions (and for consistency, probably inner class instances as well), we relax these rules to also allow for capture of effectively final local variables. (Informally, a local variable is effectively final if making it final would not cause a compilation failure; this can be considered a form of type inference.)

It is our intent to not permit capture of mutable local variables. The reason is that idioms like this:

int sum = 0;
list.forEach(#{ e -> sum += e.size(); });


are fundamentally serial; it is quite difficult to write lambda bodies like this that do not have race conditions. Unless we are willing to enforce (preferably statically) that such lambdas not escape their capturing thread, such a feature may well cause more trouble than it solves.

0 请登录后投票
论坛首页 Java企业应用版

跳转论坛:
Global site tag (gtag.js) - Google Analytics