Java SE 是什么,包括哪些内容(二十)?
本文内容参考自Java8标准
再次感谢Java编程思想对本文的启发!
可以将一个类的定义放在另一个类的内部,这就是内部类。
内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。
但是,你必须要知道的是:内部类与组合是完全不同的概念。
在最初,内部类看起来就像是一种"代码隐藏机制:将类置于其它类的内部"。但是,你将会了解到,内部类其实远不止如此,它了解外部类(能访问外部类的任何内容),并能与之通信,而且你用内部类写出的代码会更加的优雅(尽管并不总是这样)。
最初,内部类可能看起来有些奇怪,而且要花些时间才能在设计中轻松地使用它们。对内部类的需求并非总是很明显的,但是在描述完内部类的基本语法之后,后面我会通过一些例子来明确显现内部类的益处。
从本文开始及之后的与内部类有关的博文会说明内部类的这些特性是为了语言的完备性而设计的,但是你也许不需要使用它们,至少一开始可能不需要,因此,我现在打算将你可能需要的部分写出来。
1、创建内部类
创建内部类的方式和类其实一样,只不过它是一个置于外围类里面的类。
代码示例:
// 创建内部类
//外部类Parcel1
public class Parcel1{
//内部类Contents
class Contents{
//内部类int类型的类变量i,初始化值为11
private int i = 11;
//内部类方法value()
public int value(){
//返回i的值。
return i;
}
}
//内部类Destination
class Destination{
//内部类String类型的类变量label
private String label;
//内部类构造方法
Destinaton(String whereTo){
//为类变量label赋值初始化。
label = whereTo;
}
//内部类方法readLabel()
String readLabel(){
//返回变量label的值。
return label;
}
}
//外部类方法ship(String dest),带一个String类型的形式参数dest
public void ship(String dest){
//创建内部类Contents的对象实例
Contents c = new Contents();
//创建内部类Contents的对象实例
Destination d = new Destination(dest);
//打印内部类d调用方法readLabel()的结果
System.out.println(d.readLabel());
}
//程序执行入口main方法
public static void main(String[] args){
//创建外部类Parcel1的对象实例
Parcel1 p = new Parcel1();
//调用方法ship(String dest)
p.ship("Tasmania");
}
}
当我们在方法ship()里面使用内部类的时候,与使用普通类没什么区别,在这里实际的区别只是内部类的名字是嵌套在类Parcel1里面的。不过,越往后面你会发现,这并不是唯一的区别。
更典型的应用是,外部类将有一个方法,该方法返回一个指向内部类的引用,就现在下面的代码示例中方法to()和contents()中看到的一样。
代码示例:
// 外部类的方法返回一个指向内部类的引用
//外部类Parcel2
public class Parcel2{
//内部类Contents
class Contents{
//int类型的内部类变量i,初始化值为11
private int i = 11;
//方法value()
public int value(){
//返回类变量i的值
return i;
}
}
//内部类Destination
class Destination{
//String类型的内部类变量label
private String label;
//构造方法,带一个String类型的形式参数whereTo
Destination(String whereTo){
//初始化类变量
label = whereTo;
}
//方法readLabel(),返回类型为String
String readLabel(){
//返回类变量label的值
return label;
}
}
//方法to(String s),带一个String类型的形式参数s,返回类型为
//内部类Destination
public Destination to(String s){
//返回一个类Destination的对象实例
return new Destination(s);
}
//方法contents(),返回类型为内部类Contents
public Contents contents(){
return new Contents();
}
//方法ship(String dest),带一个String类型的形式参数dest
public void ship(String dest){
//创建内部类Contents的对象实例
Contents c = contents();
//创建内部类Destination的对象实例
Destination d = to(dest);
//打印调用方法readLabel()的结果.
System.out.println(d.readLabel());
}
//程序执行入口main方法
public static void main(String[] args){
//创建外部类Parcel2的对象实例
Parcel2 p = new Parcel2();
//调用方法ship(String dest)
p.ship("Tasmania");
//创建外部类Parcel2的另一个对象实例
Parcel2 q = new Parcel2();
//调用方法contents()返回内部类Contents的对象实例
//同时赋值时需明确使用OuterClassName.InnerClassName形式标识
Paecel2.Contents c = q.contents();
//同上
Paecel2.Destiantion d = q.to("Borneo");
}
}
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在main()方法中那样,具体指明对象的类型:OuterClassName.InnerClassName(Paecel2.Contents),如果是在非静态方法之内,就直接创建就行了:
2、链接到外部类
到目前为止,内部类似乎还只是一种名字隐藏和代码组织的模式。这些是很有用,但是还不是最瞩目的,它还有一些其他的用途。
当你生成一个内部类对象时,此对象与它的外围类对象之间就有了一种联系(这种联系是自动生成的,时机就在创建了一个内部类对象的当时),所以它能访问其外围类对象的所有成员,而不需要任何的特殊条件。此外,内部类还拥有其外围类的所有元素的访问权,下面通过代码示例说明。
代码示例:
// 内部类能访问外围类的所有成员
//接口Selector
interface Selector{
//方法end()
boolean end();
//方法current(),返回类型为Object
Object current();
//方法next()
void next();
}
//类Sequence
public class Sequence{
//Object数组类型的类变量items
private Object[] items;
//int类型的类变量next,初始化值为0
private int next = 0;
//构造方法,带一个int类型的形式参数size
public Sequence(int size){
//创建一个Object数组对象,赋值给引用items
//[size]表示数组空间长度
items = new Object[size];
}
//方法add(Object x),带一个Object类型的形式参数x
public void add(Object x){
//if语句,如果next的值小于数组items的长度
if(next < items.length){
//则将x的值赋值给数组items的下标为next的引用,
//同时next的值自增1
items[next++] = x;
}
}
//内部类SequenceSelector实现接口Selector
private class SequenceSelector implements Selector{
//int类型的类变量i,初始化值为0
private int i = 0;
//实现方法end(),用于判断是否是数组末尾
public boolean end(){
//返回i值是否等于items.length的boolean类型结果
//如果相等返回true,如果不等返回false
return i == items.length;
}
//方法current(),返回类型为Object
public Object current(){
//返回当前数组下标为i的数组对象
return items[i];
}
//方法next(),下移
public void next(){
//if语句,如果i的值小于数组items的长度
if(i<items,length){
//i值自增1
i++;
}
}
}
//方法selector()
public Selector selector(){
//返回内部类SequenceSelector的对象实例
return new SequenceSelector();
}
//程序执行入口main方法
public static void main(String[] args){
//创建外部类Sequence的对象实例,传入的实际参数为10
//说明同时创建了一个数组长度空间为10的数组对象
Sequence sequence = new Sequence(10);
//for循环语句
for(int i = 0;i<10;i++){
//往这个创建的数组对象中添加实际的对象
//让数组中每一个引用都连接上真正的对象
//这个对象就是类Integer.toString(i)的结果
sequence.add(Integer.toString(i));
}
//调用方法selector()返回一个内部类的对象实例,赋值给
//父接口Selector的引用selector
Selector selector = sequence.selector();
//while循环,!selector.end()表示只要未达到数组的末尾
//循环一直继续
while(!selector.end()){
//每次循环都输出数组对象内容
System.out.println(selector.current + " ");
//数组下标下移
selector.next();
}
}
}
Sequence类知识一个固定大小的数组(长度为10),以类的形式包装了起来,可以调用add()在序列末增加行的Object(只要还有空间),要获取Sequence中的每一个对象,可以使用Selector接口,这是"迭代器"设计模式的一个例子,你将在后面会更多地接触到(一个方法判断是否到了末尾,一个方法返回当前对象,一个方法进行下移操作),Selector允许你检查序列是否到末尾了(end()),访问当前对象(current()),以及移到序列中的下一个对象(next()),因为Selector是一个接口,所以别的类可以按它们自己的方式来实现自己的接口,并且让某一个方法以此接口为参数,来生成更加通用的代码。
这里,SequenceSelector是提供接口Selector功能的private类,可以看到,在方法main()中创建了一个Sequence,并想其中添加了一些String对象(类Interger.toString(i)),然后通过调用selector()获取一个Selector,并用它在Sequence中移动和选择每一个元素。
最初看到SequenceSelector,可能会觉得它只不过是另一个内部类而已,但是当你仔细观察它的时候,注意方法end()、current()、和next()都用到了Object(它其实是外部类的类变量),这是一个引用,它并不是SequenceSelector的一部分,而是外围类中的一个private类变量,然而内部类可以方位其外部类的方法和类变量,就像自己拥有它们一样,这带来了很大的方便。
所以内部类自动拥有对其外部类所有成员的访问权,这是如何做到的呢?
当某个外部类的对象创建了一个内部类对象时,此内部类对象必定会秘密捕获一个指向那个外部类的引用:
然后,在你访问此外部类对象的时候,就是用那个引用来选择外部类的成员,幸运的是,编译器会帮你处理所有的细节。
但你现在可以看到:内部类的对象只能在与其外部类的对象相关联的情况下才能被创建(在内部类是非static内部类时)。
构建内部类对象时,需要一个指向其外部类对象的引用,如果编译器访问不到这个引用就会报错。不过绝大多数时候这都无需程序员担心。
以上的功能你可以尝试使用组合来实现,你就会发现内部类的意义。
PS:时间有限,有关Java SE的内容会持续更新!今天就先写这么多,如果有疑问或者有兴趣,可以加QQ:2649160693,并注明CSDN,我会就博文中有疑义的问题做出解答。同时希望博文中不正确的地方各位加以指正。