欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java开发笔记(五十四)内部类和嵌套类

程序员文章站 2022-11-24 17:35:31
通常情况下,一个Java代码文件只定义一个类,即使两个类是父类与子类的关系,也要把它们拆成两个代码文件分别定义。可是有些事物相互之间密切联系,又不同于父子类的继承关系,比如一棵树会开很多花朵,这些花儿作为树木的一份子,它们依附于树木,却不是树木的后代。花朵不但拥有独特的形态,包括花瓣、花蕊、花萼等, ......

通常情况下,一个java代码文件只定义一个类,即使两个类是父类与子类的关系,也要把它们拆成两个代码文件分别定义。可是有些事物相互之间密切联系,又不同于父子类的继承关系,比如一棵树会开很多花朵,这些花儿作为树木的一份子,它们依附于树木,却不是树木的后代。花朵不但拥有独特的形态,包括花瓣、花蕊、花萼等,而且拥有完整的生命周期,从含苞欲放到盛开绽放再到凋谢枯萎。这样一来,倘若把花朵抽象为花朵类,那么花朵类将囊括花瓣、花蕊、花萼等成员属性,以及含苞、盛开、凋谢等成员方法。既然花朵类如此规整,完全可以定义为一个class,但是花朵类又依附于树木类,说明它不适合从树木类独立出来。
为了解决这种依附关系的表达问题,自然就得打破常规思维,其实java支持类中有类,在一个类的内部再定义另外一个类,仿佛新类是已有类的成员一般。一个类的成员包括成员属性和成员方法,还包括刚才说的成员类,不过“成员类”的叫法不常见,大家约定俗成的叫法是“内部类”,与内部类相对应,外层的类也可称作“外部类”。仍旧以前述的树木类和花朵类为例,如今可在树木类的内部增加定义花儿类,就像下面代码那样:

//演示内部类的简单定义
public class tree {
	private string tree_name;
	
	public tree(string tree_name) {
		this.tree_name = tree_name;
	}
	
	public void sprout() {
		system.out.println(tree_name+"发芽啦");
		// 外部类访问它的内部类,就像访问其它类一样,都要先创建类的实例,再访问它的成员
		flower flower = new flower("花朵");
		flower.bloom();
	}
	
	// flower类位于tree类的内部,它是个内部类
	public class flower {
		private string flower_name;

		public flower(string flower_name) {
			this.flower_name = flower_name;
		}

		public void bloom() {
			system.out.println(flower_name+"开花啦");
		}
	}
}

从以上代码可见,外部类里面访问内部类flower,就像访问其它类一样,都要先创建类的实例,再访问它的成员。至于在外面别的地方访问这里的外部类tree,自然也跟先前的用法没什么区别。可是如果别的地方也想调用内部类flower,那就没这么容易了,因为直接通过new关键字是无法创建内部类实例的。只有先创建外部类的实例,才能基于该实例去new内部类的实例,内部实例的创建代码格式形如“外部类的实例名.new 内部类的名称(...)”。下面是外部调用内部类的具体代码例子:

		// 先创建外部类的实例,再基于该实例去创建内部类的实例
		treeinner inner = new treeinner("桃树");
		// 创建一个内部类的实例,需要在new之前添加“外层类的实例名.”
		treeinner.flower flower = inner.new flower("桃花");
		flower.bloom(); // 调用内部类实例的bloom方法

 

所谓好事多磨,引入内部类造成的麻烦不仅仅一个,还有另一个问题也挺棘手的。由于内部类是外部类的一个成员类,因此二者不可避免存在理论上的资源冲突。假设外部类与内部类同时拥有某个同名属性,比如它俩都定义了名叫tree_name的树木名称字段,那么在内部类里面,tree_name到底指的是内部类自身的同名属性,还是指外部类的同名属性呢?
从前面的类继承文章了解到,一旦遇到同名的父类属性、子类属性、输入参数,则编译器采取的是就近原则,例如在方法内部优先表示同名的输入参数,在子类内部优先表示同名的子类属性等等。同理,对于同名的内部类属性和外部类属性来说,tree_name在内部类里面优先表示内部类的同名属性。考虑到避免混淆的缘故,也可以在内部类里面使用“this.属性名”来表达内部类的自身属性。但如此一来,内部类又该怎样访问外部类的同名属性,确切地说,内部类flower的定义代码应当如何调用外部类treeinner的tree_name字段?显然这个问题足以让关键字this人格分裂,明明身在treeinner里面,却代表不了treeinner。为了拯救可怜的this,java允许在this之前补充类名,从而限定此处的this究竟代表哪个类。譬如“treeinner.this”表示的是外部类treeinner自身,而“treeinner.this.tree_name”则表示treeinner的成员属性tree_name。于是在内部类里面终于能够区分内部类和外部类的同名属性了,详细的区分代码如下所示:

		// 该方法访问内部类自身的tree_name字段
		public void bloominnertree() {
			// 内部类里面的this关键字指代内部类自身
			system.out.println(this.tree_name+"的"+flower_name+"开花啦");
		}

		// 该方法访问外部类treeinner的tree_name字段
		public void bloomoutertree() {
			// 要想在内部类里面访问外部类的成员,就必须在this之前添加“外部类的名称.”
			system.out.println(treeinner.this.tree_name+"的"+flower_name+"开花啦");
		}

当然多数场合没有这种外部与内部属性命名冲突的情况,故而在this前面添加类名纯属多此一举,只有定义了内部类,并且内部类又要访问外部类成员的时候,才需要显式指定this的归属类名。

苦口婆心地啰嗦了许久,内部类的小脾气总算搞定了。不料一波三折,之前说到其它地方调用内部类的时候,必须先创建外部类的实例,然后才能创建并访问内部类的实例。这个流程实在繁琐,好比我想泡一杯茉莉花茶,难道非得到田里种一株茉莉才行?很明显这么搞费时又费力,理想的做法是:只要属于对茉莉花的人为加工,而非紧密依赖于茉莉植株的自然生长,那么这个茉莉花类理应削弱与茉莉类的耦合关系。为了把新的类与类关系同外部类与内部类区分开来,java允许在内部类的定义代码前面添加关键字static,表示这是一种静态的内部类,它无需强制绑定外部类的实例即可正常使用。
静态内部类的正式称呼叫“嵌套类”,外层类于它而言仿佛一层外套,有套没套不会对嵌套类的功能运用产生实质性影响,套一套的目的仅仅表示二者比较熟悉而已。下面是把flower类改写为嵌套类的代码定义例子,表面上只加了一个static:

//演示嵌套类的定义
public class treenest {
	private string tree_name;

	public treenest(string tree_name) {
		this.tree_name = tree_name;
	}

	public void sprout() {
		system.out.println(tree_name+"发芽啦");
	}
	
	// flower类虽然位于treenest类的里面,但是它被static修饰,故而与treenest类的关系比起一般的内部类要弱。
	// 为了与一般的内部类区别开来,这里的flower类被叫做嵌套类。
	public static class flower {
		private string flower_name;
		
		public flower(string flower_name) {
			this.flower_name = flower_name;
		}

		public void bloom() {
			system.out.println(flower_name+"开花啦");
		}

		public void bloomoutertree() {
			// 注意下面的写法是错误的,嵌套类不能直接访问外层类的成员
			//system.out.println(treenest.this.tree_name+"的"+flower_name+"开花啦");
		}
	}
}

现在flower类变成了嵌套类,别的地方访问它就会省点事,按照格式“new 外层类的名称.嵌套类的名称(...)”即可直接创建嵌套类的实例,不必画蛇添足先创建外层类的实例。完整的调用代码如下所示:

	// 演示嵌套类的调用方法
	private static void testnest() {
		// 创建一个嵌套类的实例,格式为“new 外层类的名称.嵌套类的名称(...)”
		treenest.flower flower = new treenest.flower("桃花");
		flower.bloom();
	}

 

正所谓有利必有弊,外部调用嵌套类倒是省事,嵌套类自身若要访问外层类就不能随心所欲了。原先花朵类作为内部类之时,通过前缀“外部类的名称.this”便可访问外部类的各项成员;现今花朵类摇身一变嵌套类,要访问外层的树木类不再容易了,对嵌套类而言,外层类犹如一个熟悉的陌生人,想跟它打招呼就像跟路人打招呼一样无甚区别,都得先创建对方的实例,然后才能通过实例访问它的每个成员。
迄今为止,这里已经介绍了好几种的类,它们相互之间的关系各异,通俗地说,子类与父类之间是继承关系,内部类与外部类之间是共存关系,嵌套类与外层类之间是同居关系。



更多java技术文章参见《java开发笔记(序)章节目录