java嵌套类(Nested Classes)总结

Nested Classes定义

在java语言规范里面,嵌套类(Nested Classes)定义是:

A nested class is any class whose declaration occurs within the body of another class or interface. A top level class is a class that is not a nested class. 

说的简单一点,就是定义在类里面的类。一般把定义内部类的外围类成为包装类(enclosing class)或者外部类

 

嵌套类分类

根据nested class定义的地方,可以分为member nested class,local nested class , anonymous nested class

member nested class(成员嵌套类) :成员嵌套类 作为 enclosing class 的成员定义的,成员嵌套类有enclosing class属性

local nested class (局部嵌套类): 局部嵌套类定义在 enclosing class 的方法里面,局部嵌套类有enclosing class 属性和enclosing method 属性

anonymous nested class(匿名嵌套类):匿名嵌套类没有显示的定义一个类,直接通过new 的方法创建类的实例。一般回调模式情况下使用的比较多

 

member nested class 可以使用public,private,protected访问控制符,也可以用static,final关键字

local nested class 可以使用final关键字

anonymous nested class 不使用任何关键字和访问控制符

见下面的代码

public class EnclosingClass {

	public static final class NestedMemberClass {

	}

	public void nestedLocalClass() {

		final class NestedLocalClass {

		}
	}

	public void nestedAnonymousClass() {

		new Runnable() {

			@Override
			public void run() {
			}
		};
	}
}

 

在大多数情况下,一般把nested classes 分为两种:

Static Nested Classes(静态嵌套类): 就是用static修饰的成员嵌套类

InnerClass:静态嵌套类之外所有的嵌套类的总称,也就是没有用static定义的nested classes,Inner Classes 不能定义为static,不能有static方法和static初始化语句块。在JLS(java语言规范)里面是这么定义的:

An inner class is a nested class that is not explicitly or implicitly declared static. Inner classes may not declare static initializers (§8.7) or member inter- faces 

 

其中Inner Class又可以分为三种:

1 inner member classes :没有用static 修饰的成员内部类

2 local inner classes : 定义在方法里面的内部类,方法可以是static的也可以是非static的,也可以是构造器方法。

3 anonymous inner classes :定义在方法里面匿名类,方法可以是static的也可以是非static的

嵌套类访问规则

Static Nested Classes 以及 inner classes 有一些限制规则,下面介绍一下这些规则。

  • Static Nested Classes访问规则

用Static修饰的Nested Classes,只能访问外部类的非static变量。对于public 的 static Nested Classes 可以用 new 外部类.内部类()的方式直接创建。而默认的static Nested Classes 可以在同一包名下,用 new 外部类.内部类()的方式创建。其实和外部类的方式差不多。静态成员类可以使用访问控制符,可以使用static修饰,可以是abstract抽象类

 

public class StaticNestedClass {

	// 私有局部
	private int i = 0;

	// 静态
	public static int j = 0;

	// 不变值
	private final int k = 0;

	// static final
	private static final int m = 0;

	// 静态嵌套内,这里不是innerclass,可以直接new出来
	public static class PublicNestedClass {

		private void test1() {
			// System.out.println(i); 非innerClass不能访问enclosing类的非static属性
			System.out.println(j);
			System.out.println(m);
			// System.out.println(k); 非innerClass不能访问enclosing类的非static属性
		}

		// 可以定义static方法
		private static void test2() {

		}
	}

	// 静态嵌套内,这里不是innerclass,由于是私有的,不可以直接new出来
	private static class PrivateNestedClass {

	}
}

  

下面的例子演示了static Nested class的创建

public class TestClass {

	public static void main(String[] args) {
		
		//任何地方都可以创建
		StaticNestedClass.PublicNestedClass publicNestedClass = new StaticNestedClass.PublicNestedClass();
		
		//可以在同一package下创建
		StaticNestedClass.DefaultNestedClass defaultNestedClass = new StaticNestedClass.DefaultNestedClass();
		//编译错误,无法访问内部内
		//StaticNestedClass.PrivateNestedClass privateNestedClass = new StaticNestedClass.PrivateNestedClass();
	}
}

  

  • Inner Class访问规则

inner member classes(内部成员类) 可以访问外部类的所有实例属性,静态属性。因为内部成员类持有一个外部对象的引用,内部类的实例可以对外部类的实例属性进行修改。如果是public的 inner  member classes,可以通过 外部类实例.new 内部类()的方式进行创建,当调用内部类的构造器的时候,会把当前创建的内部类对象实例中持有的外部对象引用赋值为当前创建内部类的外部类实例。内部成员类可以是使用访问控制符,可以定义为final,也可以是抽象类。

 

  

public class MemberInnerClass {

	// 私有局部
	public int i = 0;

	// 静态
	private static int j = 0;

	// 不变值
	private final int k = 0;

	// static final
	private static final int m = 0;

	public class PublicMemberInnerClass {
		// enclosing Class的属性都可以访问
		public void test() {
			System.out.println(i);
			System.out.println(j);
			System.out.println(m);
			System.out.println(k);
		}

		public MemberInnerClass getOutterClass() {
			return MemberInnerClass.this;
		}
		// 这里会报错,不允许定义static方法
		// private static final void test();
	}

	// 私有的innerclass 外部不能访问
	private class PrivateMemberInnerClass {
	}

	// 公开局部类,外部可以访问和创建,但是只能通过OutterClass实例创建

	class DefaultMemberInnerClass {
		public MemberInnerClass getOutterClass() {
			return MemberInnerClass.this;
		}
	}

}

  

下面例子演示了内部成员类的创建

public class TestClass {

	public static void main(String[] args) {

		// 任何地方都可以创建
		MemberInnerClass t = new MemberInnerClass();

		// 可以创建,pmic里面保存对t的引用
		MemberInnerClass.PublicMemberInnerClass pmic = t.new PublicMemberInnerClass();

		// 可以在同一package下创建,dmic保存对t的引用
		MemberInnerClass.DefaultMemberInnerClass dmic = t.new DefaultMemberInnerClass();

		// 编译错误,无法访问内部内
		// MemberInnerClass.PrivateMemberInnerClass pmic = t.new
		// PrivateMemberInnerClass();

		// 下面验证一下outterClass是同一个对象
		System.out.println(pmic.getOutterClass() == t);
		System.out.println(dmic.getOutterClass() == t);

	}
}

 运行程序,打印结果:

true
true

  

2 local inner classes(局部类)

局部类 定义在类方法里面。这个方法既可以是静态方法,也可以是实例方法,也可以是构造器方法或者静态初始化语句块。

局部类可以定义在一个static上下文里面 和 非static上下文里面。局部类不能有访问控制符(private,public,protected修饰),可以是抽象的,也可以定义为final

定义在static上下文(static 字段初始化,static初始化块,static方法)里面的local inner classes 可以访问类的静态属性,如果定义在静态方法里面的局部类,还可以方法里面定义的final变量。在static上下文定义的局部类,没有指向父类实例变量的引用,因为static方法不属于类的实例,属于类本身。而且局部类不能在外部进行创建,只能在方法调用的时候进行创建

 

public class LocalInnerClass {

	// 私有局部
	private int i = 0;

	// 静态
	public static int j = 0;

	// 不变值
	private final int k = 0;

	// static final
	private static final int m = 0;

	public static void test() {
		final int a = 0;
		int b = 0;
		// local inner class不能够有访问控制符 比如public private
		abstract class LocalStaticInnerClass {
			// local inner class不能定义静态属性
			// private static int c;
			private int d = 0;
			public LocalStaticInnerClass() {
				// 可以访问方法里面定义的final 变量
				System.out.println(a);
				// 不能访问b 因为b不是final
				// System.out.println(b);		
				// 定义在static上下文里面的local inner class 不能访问外部类的非static字段
				// System.out.println(i);
				// System.out.println(k);
				System.out.println(j);
				System.out.println(m);
			}
			// local inner class不能定义静态方法
			// public static void test(){}
		}
	}

	public void test2() {
		final int a = 0;
		int b = 0;
		final class LocalNonStaticInnerClass{	
			public LocalNonStaticInnerClass() {
				//定义在非static上下文的local inner class 可以访问外部类的所有属性
				System.out.println(i);
				System.out.println(k);
				System.out.println(j);
				System.out.println(m);
			}
		}
	}

}

 

 3 anonymous inner classes (匿名类)也是定义在方法里面,匿名类和局部类访问规则一样,只不过内部类显式的定义了一个类,然后通过new的方式创建这个局部类实例,而匿名类直接new一个类实例,没有定义这个类。匿名类最常见的方式就是回调模式的使用,通过默认实现一个接口创建一个匿名类然后,然后new这个匿名类的实例。

public class AnonymousInnerClass {
	//访问规则和局部类一样
	public void test() {
		
		//匿名类实现
		new Thread(new Runnable() {

			@Override
			public void run() {

			}
		}).start();
		
		//非匿名类实现
		class NoneAnonymousClass implements Runnable{
			public void run() {

			}
		}	
		NoneAnonymousClass t = new NoneAnonymousClass();
		new Thread(t).start();
	}
}

嵌套类的层次

嵌套类是可以有层次的,也就是说嵌套类里面还是定义类,成为嵌套类中的嵌套类。虚拟机如何保证嵌套类正确的嵌套层层次?

对于merber class,内部嵌套类的可以表示为 A$B 其中A为外部类,B为内部成员类 ,如果B里面又有成员为C的嵌套类,那么C就可以表示为A$B$C,如果A定义了两个同名member class,那么编译器就会报错。如果B里面又包含了为名B的nested class,则编译器会报错.

对于local inner Class,局部类可以表示为A$1B的方式,其中A为外部类,B为第一个局部类 如果在不同的方法里面定义了同名的局部类B,编译器是可以编译通过的,那么定义的第二个局部类B可以表示为A$2B,如果在同一个方法里面同定义两个相同的局部类B,那么编译错是要报错的。如果B里面又定义了同名的成员类,则可以表示为A$1B$B。

对于anonymous inner classes,匿名类可以表示为A$1的方式,代表程序里面有一个匿名类。如果有N个,可以表示为A$N的方式(N为自然数).

看看下面的例子

public class NestedClassLevel {

	class A {
		// 编译器会报错,A里面不能在定义名为A的nested classes
		// class A{}
		public void test() {
			class B {
			}
		}
	}

	//可以在继续定义B
	class B {
		public void test(){
			//可以无限定义匿名类
			new Runnable() {
				public void run() {
					//可以无限定义匿名类
					new Runnable() {			
						public void run() {			
						}
					};
				}
			};
		}
	}

	// 只能定义一个B
	// class B{}

	public void test() {
		// 可以定义A
		class A {
			public void test() {
				//可以有同名的局部类B和成员类B
				class B {
					public void test() {
						
					}
				}
				//局部类A里面不能在定义A
				//class A{}
			}
		}
		//可以有同名的局部类B和成员类B
		class B {

		}
	}

}

 

对于定义在非static上下文里面的nested类层次,比如A$B$1C ,则最内层的嵌套类C有一个指向B实例的引用,B有一个指向A实例的引用,最终最内层的嵌套类可以访问A中的属性可以方法,一般把B成为A的直接嵌套类。但是A不可以访问B或者C中属性或者方法。

nested interface

由于interface默认是定义为一个 public static的特殊类,所以interface可以直接作为 static member class。可以通过A.B的方式进行访问。

 

nested class的应用

在java提供的基本类库里面,大量使用nested classes。比如我们知道的map类。其中 Map类里面有一个定义了Entry类abstract inner class。所以我们在遍历map的时候,一般使用

for (Map.Entry entry:map.entrySet()){

}

 

总结:nested类是java里面比较复杂的一个概念,必须详细了解jvm中对于嵌套类的实现以及java编译器对嵌套类的处理才可以深入了解嵌套类细节。