JAVA基础 补充 内部类

本文详细解析了闭包的概念及其在JavaScript和Java中的应用,同时深入探讨了Java内部类的各种形式,如普通内部类、局部内部类、匿名内部类、嵌套内部类等,以及它们的特点和应用场景。

可爱的我只是链接的搬运工~

闭包(closure):

一个可调用的对象,纪录了来自于创建它的作用域的信息。a外部有一个变量引用指向a内部的函数b,因为b需要使用a的成员变量(外部只能根据b中所定义的方式来操作a的成员变量),所以GC不会在a执行完毕后马上回收i。

JAVASCRIPT 例子:

function a() {

    var i = 0;

    function b() {

        alert(++i);

    }

    return b;

}

var c = a();

c();


闭包的意义:

- 保护函数的内部变量

- 在内存中维持一个变量

- 防止方法重名


JAVA通常使用内部类和回调来实现闭包。JAVA内部类不仅可以包含外围类对象的信息,还自动拥有一个指向此外围类对象的引用。在此作用域内,内部类有权操作所有的成员,包括private成员。JAVA 8中引入了新特性lambda表达式。


回调(callback):

- 同步调用

- 回调,被调用方在接口被调用时也调用对方的接口(幽默风趣的回调文章:http://www.importnew.com/19301.html)

- 异步调用,类似消息或事件的机制

package InnerClass;

interface Incrementable {
	void increment();
}

class Callee1 implements Incrementable {
	private int i = 0;

	@Override
	public void increment() {
		i++;
		System.out.println(i);
	}
	
}

class MyIncrement {
	public void increment() {
		System.out.println("Other operation");
	}
	
	static void f(MyIncrement mi) {
		mi.increment();
	}
}

class Callee2 extends MyIncrement {
	private int i = 0;
	public void increment() {
		super.increment();
		i++;
		System.out.println(i);
	}
	
	private class Closure implements Incrementable {

		@Override
		public void increment() {
			Callee2.this.increment();
		}
	}
	
	Incrementable getCallbackRef() {
		return new Closure();
	}
}

class Caller {
	private Incrementable ref;
	
	Caller(Incrementable ref) {
		this.ref = ref;
	}
	
	void go() {
		ref.increment();
	}
}

public class Test5 {
	public static void main(String[] args) {
		Callee1 c1 = new Callee1();
		Callee2 c2 = new Callee2();
		MyIncrement.f(c2);
		
		Caller caller1 = new Caller(c1);
		Caller caller2 = new Caller(c2.getCallbackRef());
		caller1.go();
		caller1.go();
		caller2.go();
		caller2.go();
	}
}


内部类:可以将一个类定义在另一个类内部,允许你把一些逻辑相关的类组织起来,像是一种代码隐藏机制,内部类了解外部类并能与之通信。一个内部类被嵌套多少层并不重要,依然能够透明的访问它所有外围类的所有成员。

为什么需要内部类?

内部类提供了某种进入其外围类的窗口。如果只需要一个对接口的引用,应该直接通过外围类实现那个接口。使用内部类时,每个内部类都能独立地继承一个实现,所以无论外围类是否已经继承了接口,对内部类没有任何影响。

package InnerClass;

interface AInterface {
	void printSth ();
}

public class Test6 implements AInterface{

	@Override
	public void printSth() {
		System.out.println("out");
	}
	
	public class Inner implements AInterface{

		@Override
		public void printSth() {
			System.out.println("in");
		}
		
	}
	
	public static void main(String[] args) {
		Test6 t6 = new Test6();
		Test6.Inner ti = t6.new Inner();
		ti.printSth();
		t6.printSth();
	}

}


内部类的继承:

定义一个内部类时,这个内部类会有一个隐式引用(implicit reference)指向外部类的实例。子类继承内部类需要保证外部类实例先于内部类实例存在,详细见:http://www.tuicool.com/articles/7jM7Fb。


内部类的覆盖:

package InnerClass;

public class Test7 {
	private int p;
	public class Inner {
		private int p;
	}
}

package InnerClass;

import static java.lang.System.out;

class Egg2 {
	protected class Yolk {
		public Yolk() {
			out.println("Egg2.Yolk()");
		}
		public void f() {
			out.println("Egg2.Yolk.f()");
		}
	}
	
	private Yolk y = new Yolk();
	public Egg2() {
		out.println("New Egg2()");
	}
	public void insertYolk(Yolk yy) {
		y = yy;
	}
	public void g() {
		y.f();
	}
}

class Yolk2 extends Egg2.Yolk {
	public Yolk2 (Egg2 eg) {
		eg.super();
	}
}

public class BigEgg2 extends Egg2 {
	public class Yolk extends Egg2.Yolk {
		public Yolk() {
			out.println("BigEgg2.Yolk()");
		}
		public void f() {
			out.println("BigEgg2.Yolk().f()");
		}
	}
	
	public BigEgg2() {
		insertYolk(new Yolk());
	}
	public static void main(String[] args) {
		Egg2 e2 = new BigEgg2();
		e2.g();
	}
}


public class BigEgg2 extends Egg2 {
//	public class Yolk extends Egg2.Yolk {
//		public Yolk() {
//			out.println("BigEgg2.Yolk()");
//		}
//		public void f() {
//			out.println("BigEgg2.Yolk().f()");
//		}
//	}
	
	public BigEgg2() {
		insertYolk(new Yolk());
	}
	public static void main(String[] args) {
		Egg2 e2 = new BigEgg2();
		e2.g();
	}
}

内部类的标识:

内部类也会生成一个.class文件,命名规则为“外围类名字$内部类名字”,如果内部类时匿名的,编译器会简单的产生一个数字作为标识符。如果内部类嵌套在别的内部类之中,只需要直接将它们的名字加在其外围类名字与“$”后面。

package InnerClass;

public class Test7 {
	public class Inner {
		// Test7$Inner$InnerInner.class
		public class InnerInner {
			
		}
	}
}

- 普通内部类

package InnerClass;

//普通内部类
interface Printer {
	void PrintVal();
} 

public class Test1 {
	
	private String outVal;
	
	class Inner implements Printer {
		private int i;
		
		Inner(String val) {
			/*
			 * 内部类可以访问外部类所有成员,当外部类对象创建一个内部类对象时,内部类对象会捕获一个指向外围类对象的引用,
			 * 只有内部类对象与外部类对象相关联时,内部类实例下才会被创建,所以内部类不能有static数据
			 */
			outVal = val;
			i = 5;
		}
		
		public void PrintVal() {
			System.out.println(outVal);
		}
		
		//获取外部类引用
		public Test1 getOuter() {
			//如果只有this的话,其指向内部类实例,而Test1.this引用指向外部类实例
			return Test1.this;
		}
	}
	
	public Inner inner(String val) {
		return new Inner(val);
	}
	
	public static void main(String[] args) {
		Test1 test1 = new Test1();
		//下面一行代码是invalid,在拥有外部类对象之前是不可能创建内部类对象
		//Test1.Inner ti = new Test1.Inner("...");
		Test1.Inner ti1 = test1.inner("...1...");
		ti1.PrintVal();
		Test1.Inner ti2 = test1.new Inner("...2...");
		ti2.PrintVal();
		//外部类可以访问内部类的私有变量
		System.out.println(ti2.i); 
		Printer p = test1.inner("...3...");
		p.PrintVal();
	}
}


- 局部内部类

局部内部类不能有访问说明符,因为它们不是外围类的一部分,但可以访问外围类的成员。

package InnerClass;

//局部内部类
interface Sample {
	public void printStr();
}

public class Test2 {
	private String strVal = "...";
	//局部内部类只能在作用域内被使用
	public Sample getSample() {
		//不能给Inner设置public,private等修饰符,可以在同一个子目录下的任意类中采用Inner同名,不会影响
		class Inner implements Sample {
			//private int i = 1;

			@Override
			public void printStr() {
				System.out.println(strVal);
			}
		}
		return new Inner();
	}
	
	public static void main(String[] args) {
		Test2 t2 = new Test2();
		Sample s = t2.getSample();
		s.printStr();
	}
}

- 匿名内部类

匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备,而且也只能实现一个接口。

package InnerClass;

//匿名内部类
interface Sample2 {
	public void printStr();
}

class Wrapper {
	private int i;
	
	public Wrapper(int i) {
		this.i = i;
	}
	
	public int getI() {
		return i;
	}
}

public class Test3 {
	private String strVal = "...";
	
	//返回实现Sample2的类,向上转型
	public Sample2 getSample() {
		return new Sample2() {
			public void printStr() {
				System.out.println(strVal);
			}
		}; //分号不可少,return表达式
	}
	
	//返回实现Wrapper的类,向上转型
	public Wrapper getWrapper(int x, Sample2 y) {
		return new Wrapper(x) {
			private Sample2 s = y;
			private int p;
			{
			//实例初始化
				p = 7;78
				System.out.println(p);
			}
			public int getI() {
				y.printStr();
				s.printStr();
				return super.getI();<span style="white-space:pre">	</span>
			}
		};
	}
	
	public static void main(String[] args) {
		Test3 t3 = new Test3();
		Sample2 s = t3.getSample();
		Wrapper w = t3.getWrapper(5, s);
		w.getI();
	}
}


- 嵌套内部类

package InnerClass;

//如果不需要内部类与外围类有联系,可以将内部类声明为static,称为嵌套类
//不需要外围类对象来创建内部类的对象,不能从嵌套类的对象中访问非静态的外围类对象(外围类.this不可用)
//可以包含static数据

//在接口中所有类都是自动的public和static的
//Main Class: InnerClass.ClassInInterface$Nested
//会生成一个独立的类文件InnerClass.ClassInInterface$Nested.class
public interface ClassInInterface {
	void howdy();
	class Nested implements ClassInInterface {
		public void howdy() {
			System.out.println("howdy");
		}
		public static void main(String[] args) {
			new Nested().howdy();
		}
	}
}


- 工厂方法

package InnerClass;

//工厂方法
interface Service {
	void m1();
	void m2();
}

interface ServiceFactory {
	Service getService();
}

class Impl1 implements Service {

	@Override
	public void m1() {
		System.out.println("m1");
	}

	@Override
	public void m2() {
		System.out.println("m2");
	}
	
	public static ServiceFactory factory = new ServiceFactory() {
		public Service getService() {
			return new Impl1();
		}
	};
	
}
class Impl2 implements Service {

	@Override
	public void m1() {
		System.out.println("m11");
	}

	@Override
	public void m2() {
		System.out.println("m22");
	}
	public static ServiceFactory factory = new ServiceFactory() {
		public Service getService() {
			return new Impl2();
		}
	};
}

public class Test4 {
	public static void main(String[] args) {
		//不同的工厂对应不同的服务,易于扩展
		ServiceFactory sf;
		Service s;
		sf = Impl1.factory;
		//sf= Impl2.factory;
		s = sf.getService();
		s.m1();
		s.m2();
	}
}

- 变量捕获

C#变量捕获:

一个函数运行完,对应的栈帧将退出,局部变量所占用的空间将被释放。如果外部函数已执行结束,那内部函数还怎么访问这些“死了的”变量?

C#会在后台构造一个类型,将所有需要被捕获的变量(包括值类型变量和引用类型变量)作为它的公有字段。

http://www.cnblogs.com/instance/archive/2011/05/22/2053541.html


而JAVA只实现了不变量final的捕获(因为就算函数GO DIE了,不变量并不会随着stack frame马上消失,所以局部和匿名内部类只能捕获final值)。

http://www.cnblogs.com/chenjunbiao/archive/2011/01/26/1944417.html

http://www.zhihu.com/question/28190927

http://www.zhihu.com/question/27416568/answer/36565794

http://www.zhihu.com/question/21395848

package InnerClass;

interface InnerInf {
	
}

public class Test8 {
	public InnerInf m(int a) {
		class Inner implements InnerInf {
			private int b = a;
			public void mm() {
				// 会出错,因为a是不允许被改变的
				// a++;
			}
		}
		// 会出错,因为a是不允许被改变的
		// a = 6;
		return new Inner();
	}
}

package InnerClass;

interface InnerInf {
	
}

public class Test8 {
	public InnerInf m(int a) {
		final int b = a + 1;
		class Inner implements InnerInf {
			public void mm() {
				System.out.println(b);
			}
		}
		a = 8;
		return new Inner();
	}
}

当a是引用类型时,final只是限定对象的引用不变,它的成员变量值可以改变。


- 内部类与控制框架
应用程序框架用以解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是继承一个或多个类,并覆盖某些方法。模板是保持不变的事物,而可覆盖的方法就是变化的事物。控制框架是一类特殊的应用程序框架,用来解决响应事件的需求。


Reference:

1. http://blog.youkuaiyun.com/yaerfeng/article/details/7326956

2. http://www.zhihu.com/question/21395848

3. Thinking in JAVA 4th

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值