Java内部类典型用法

将一个类的定义嵌套在另一个类内部就是内部类(内部接口也可以),另一个类就是外部类。形式上很简单,关键问题是内部类有什么用,它能解决什么别人解决不了的问题?

在Java编程思想第四版中文版中有如下说法“内部类对我们非常有用,因为利用它可以对那些逻辑上相互联系的类进行分组,并可控制一个类在另一个类里的可见性”。

1、利用内部类实现分组

先看“因为利用它可以对那些逻辑上相联系的类(或者接口)进行分组这句话”。假如我们想定义一个“Tree”类,它要实现“Root”接口与“Leaf”接口。因为"Root"与"Leaf"在逻辑上是相关的,它们都是树的组成部分或者是其它植物的组成部分,因此最好把它们定义在一个文件中,也就是组织在一起,创建一个Interface.java文件,内容如下:

                                                                                            示例一

public interface Interface {
	public interface Root {
		public void show();
	}

	public interface Leaf {
		public void show();
	}
}

注意接口Root与Leaf都是public的,如果不使用内部类或者内部接口,就需要把这两个接口分别定义在各自独立的文件中,显然没有上例中使用的方式好。如果想实现以上接口的话,代码如下:

                                                                                               示例二

public class Tree implements Interface.Root, Interface.Leaf{

	@Override
	public void show() {
		System.out.println("Implement who?Root or Leaf?");
	}

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		Interface.Root r = new Tree();
		r.show();
		Interface.Leaf l = new Tree();
		l.show();
		Tree  tree = new Tree();
		tree.show();

	}

}

上例中与不使用内部接口的区别只有一个地方,就是在涉及到Root、Leaf时要在前边加上外部接口的限定符,如Interface.Root与Interface.Leaf。

2、控制内部类的可见性

上述代码中同时暴露了接口的一个问题,就是接口内方法名冲突的问题。show()这个方法同时出现在interface.Root与interface.Leaf中,在Tree中,show()方法同时实现了来自两个不同接口的show()方法,这显然是不能接受的,Root的show()方法与Leaf的show()方法在实现上不可能相同。这个问题本质上是因为继承中的“多重接口”机制引起的,因此,要解决这个问题就不能使用接口的多重实现这种机制。解决的基本思路是定义两个新类,分别实现Root与Leaf,这种方法与内部类没有关系,示例代码如下:

                                                                                              示例三

class MyRoot implements Interface.Root {
	@Override
	public void show() {
		System.out.println("Implement Root");

	}
}

class MyLeaf implements Interface.Leaf {
	@Override
	public void show() {
		System.out.println("Implement Leaf");
	}
}

public class Tree2 {
	private MyRoot r = new MyRoot();
	private MyLeaf l = new MyLeaf();

	void show(String type) {
		if ("root".equals(type)) {
			r.show();
		} else {
			l.show();
		}
	}

	public static void main(String[] args) {
		Tree2 t2 = new Tree2();
		t2.show("tree");
		t2.show("leaf");
        // 错误
        // Interface.Root r = new Tree2();
	}
}

分别定义了MyRoot与MyLeaf实现两个不同接口中的相同方法show。然后定义Tree2将MyRoot与MyLeaf组合进去。Tree2中的show()方法,注意它是独立的方法,与Root中与Leaf中的show()没有关系。它只是根据参数分别调用Root与Leaf中的不同show()。本例中的Tree2确实实现了两个不同版本的show(),但是与示例二的Tree不同,Tree2不能向上转型了。当然可以添加两个成员函数,让它模拟向上转型,示例代码如下:
                                                                                              示例四

public class Tree2 {
	private MyRoot r = new MyRoot();
	private MyLeaf l = new MyLeaf();

	void show(String type) {
		if ("root".equals(type)) {
			r.show();
		} else {
			l.show();
		}
	}
	
	Interface.Root getRoot () {
		return r;
	}
	
	Interface.Leaf getLeaf () {
		return l;
	}

	public static void main(String[] args) {
		Tree2 t2 = new Tree2();
		t2.show("tree");
		t2.show("leaf");
		
		Interface.Root r = t2.getRoot();
		r.show();
	}
}

注意Interface.Root r = t2.getRoot();这句话,它是模拟向上转型,不是真正的向上转型。以上的代码只是铺垫,与内部类没有关系,接下来就有关系了。

我们定义Tree2的目的是向外暴露一棵树的实现,而在上例中MyRoot与MyLeaf也同时暴露出去了,这不符合我们的意图,只所以分别定义MyRoot与MyLeaf是为了解决接口中方法名冲突的问题,这是我们的实现细节,需要对外隐藏。我们知道Java中普通的类只有public访问控制与默认访问控制,但内部类可以有private访问控制。因此把MyRoot与MyLeaf定义在Tree2内部,并把它设置成private型,这样就实现了隐藏,使用我们代码的人看不到MyRoot与MyLeaf了,示例如下:

                                                                                      示例五

public class Tree2 {
	private class MyRoot implements Interface.Root {
		@Override
		public void show() {
			System.out.println("[" + NAME + "]" + "Implement Root");

		}
	}
	
	private class MyLeaf implements Interface.Leaf {
		@Override
		public void show() {
			System.out.println("[" + NAME + "]" + "Implement Leaf");
		}
	}
	
	private final String NAME="Tree2";
	
	private MyRoot r = new MyRoot();
	private MyLeaf l = new MyLeaf();

	void show(String type) {
		if ("root".equals(type)) {
			r.show();
		} else {
			l.show();
		}
	}
	
	Interface.Root getRoot () {
		return r;
	}
	
	Interface.Leaf getLeaf () {
		return l;
	}

	public static void main(String[] args) {
		Tree2 t2 = new Tree2();
		t2.show("tree");
		t2.show("leaf");
		
		Interface.Root r = t2.getRoot();
		r.show();
	}

可以看到,MyRoot与MyLeaf成为Tree2的私有内部类,使用Tree2的人看不到MyRoot、MyLeaf,甚至不知道它们的存在,这也就是通过内部类控制MyRoot与MyLeaf的可见性,从而实现了隐藏实现细节的目的。

另一个非常重要的问题就是,注意Tree2中的NAME属性,它是Tree2中的私有final成员,但是对于内部类MyRoot与MyLeaf而言却是可见的,也就是说内部类可以共享外部类的一切,这一点也很重要。

3、通过方法内部类、语句块内部类、匿名内部类实现更深层次的隐藏

内部类不仅可以嵌套在类内部,还可以嵌套在方法内部、语句块内部,还可以匿名。示例代码如下:

                                                                                            示例六

public class Tree2 {
	private final String NAME = "Tree2";

	private Interface.Root r;
	private Interface.Leaf l;
	
	private void createRoot(int len) {
		class MyLeaf implements Interface.Root {
			@Override
			public void show() {
				System.out.println("[" + NAME + "]" + "[" + len + "]" + "Implement Leaf");
			}
		}
		r = new MyLeaf();
	}

	private void createLeaf(String color) {
		class MyRoot implements Interface.Root {
			@Override
			public void show() {
				System.out.println("[" + NAME + "]" + "[" + color + "]" + "Implement Root");

			}
		}
		r = new MyRoot();
	}
	
	Tree2 (int len, String color) {
		createRoot(len);
		createLeaf(color);
	}

	void show(String type) {
		if ("root".equals(type)) {
			r.show();
		} else {
			l.show();
		}
	}
	
	Interface.Root getRoot () {
		return r;
	}
	
	Interface.Leaf getLeaf () {
		return l;
	}

	public static void main(String[] args) {
		Tree2 t2 = new Tree2(10, "blue");
		t2.show("tree");
		t2.show("leaf");

		Interface.Root r = t2.getRoot();
		r.show();
	}
}

将MyRoot与MyLeaf分别定义在私有方法createRoot与createLeaf中,现在MyRoot与MyLeaf不再是类级别的内部类了,变成方法级别的内部类了。它们分别只在createRoot中createLeaf中可见,对于类中的其它方法它们都不可见,隐藏的更深。另外代码看起来结构更合理,因为相关的部分都被定义在了一起。

利用匿名内部类更进一步隐藏:

                                                                                           示例七 

public class Tree2 {
	private final String NAME = "Tree2";

	private Interface.Root r;
	private Interface.Leaf l;

	Tree2(int len, String color) {
		r = new Interface.Root() {
			@Override
			public void show() {
				System.out.println("[" + NAME + "]" + "[" + len + "]" + "Implement Leaf");
			}
		};
		l = new Interface.Leaf() {
			@Override
			public void show() {
				System.out.println("[" + NAME + "]" + "[" + color + "]" + "Implement Root");

			}
		};
	}

	void show(String type) {
		if ("root".equals(type)) {
			r.show();
		} else {
			l.show();
		}
	}

	Interface.Root getRoot() {
		return r;
	}

	Interface.Leaf getLeaf() {
		return l;
	}

	public static void main(String[] args) {
		Tree2 t2 = new Tree2(10, "blue");
		t2.show("tree");
		t2.show("leaf");

		Interface.Root r = t2.getRoot();
		r.show();
	}
}

createRoot与createLeaf方法都省了,直接在构建函数中使用内部匿名类,无需指定名称。

注意一个问题,匿名类没有名字,那么自然它就不可能有构建器。我们知道,构建器的一个重要作用是接收参数并用不同的逻辑初始化实例。上边的例子中,我们给Tree2类的构造方法传递了参数,而在内部匿名类中可以直接使用这个参数,这样,内部匿名类也就不需要通过构造器传递参数了。如果实在需要用一段逻辑初始化内部类,那么也可以用构造函数的替换方法initializer,就是把构造逻辑用{}扩起来就可以了,因此内部匿名类没有构造函数完全没有问题。 

4、总结

总结一下内部类的典型用法:

  1. 将逻辑上相关的类与接口组织在一起而不是各个定义单独的文件,如示例一。
  2. 更细粒度的控制类的可见性,如示例五。

从内部类的典型用法来看,内部类并非一定要用。它的一个作用是组织代码,使代码看起来更紧凑,组织的更合理。这个的话没有内部类也可以,只不过是代码的组织看起来不那么好而已。

关于细粒度控制类的可见性,没有这个也可以。我们可以把类定义成默认的访问控制,这样包以外的用户,也就是外部的用户就看不见这个类。而对于包内的开发人员,一般都是一个团队的人,自然知道怎么用,当然也就不用刻意隐藏来防止误用了。

只有在解决不同接口的方法名冲突时,内部类是必需的。实际上内部类的真正威力不是在这里,以上的代码里你可能忽略了一个事实,内部类可以访问外部类的一切,包括外部类私有的成员与方法,这个特性才是它的真正威力所在,我打算在下一篇文章中介绍这个特性。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值