java面向对象浅析系列3——初识奇特的内部类

1 概述

内部类是Java语言中比较特别的对象。一方面他在具备与普通类很多相似的特征,另一方面又具有一些独特的特点。下面我们来分析一下内部类。

2 简述Java文件的组织

为了避免混淆,在说明内部类之前,先来看看Java文件的组织。

Java程序允许一个Java源文件中定义多个类,但是必须且仅有一个public类,且类名要与文件同名,其他类只能保持默认范围,即不能再用public、protected、private定义类。如以下代码所示:

// Java源文件TestMultiClass.java
/*
 * 唯一的与Java文件名同名的类,
 * 该类可以定义为public或默认,即不使用public、protected、private。
 */
public class TestMultiClass {

}
/*
 * 这样也可以。
 */
class TestMultiClass {

}

/*
 * 同一个Java文件中可以定义其他不同命的类,但不能添加public、protected、private。
 */
class AnotherDefaultClass {
	
}

/*
 * 如果定义了另一个protected类,会引起编译错误
 */
protected class AnotherProtectedClass {
	
}

/*
 * 如果定义了另一个private类,会引起编译错误
 */
private class AnotherPrivateClass {
	
}

/*
 * 如果定义了另一个public类,会引起编译错误
 */
public class AnotherPublicClass {
	
}


/*
 * 如果在Java源文件TestMultiClass.java中,
 * )与Java文件名同名的类定义了protected或private,也会引起编译错误。
 */
protected class TestMultiClass {

}
private class TestMultiClass {

}
(需要说明的是,尽管Java规范允许一个源文件中定义多个类,但是通常情况我们在进行系统开时并不赞成、甚至限制这种用法,除非有必要)

像以上这样定义在同一个Java源文件中不与文件同名的类可不是内部类,我们先讨论这种情况也是为了以示区别,避免初学者混淆。

3 了解内部类

现在,我们要进入正题了。内部类是指在一个类内部定义的类,就像在类中定义的属性和方法那样,但是却与属性和方法有所不同。

对于初学者,往往在静态内部类和非静态内部类的实例化和使用上存在很多困扰。实际上如果我们对静态和非静态有了深刻理解的话,这样的困扰也就迎刃可解了。

我们要时刻记住静态的,不管是属性、方法还是内部类,都可以直接通过其所在外部类的类名访问;而非静态的,不管是属性、方法还是内部类,都需要先实例化其所在外部类的对象,然后通过实例化对象访问。我们还是来看看具体代码。

3.1 静态内部类

先看静态情形:

/*
 * 包含静态属性、方法、内部类的类
 */
public class OuterClassForStatic {

	public static String staticPropStr = "静态的字符串属性";

	public static void staticMethod() {
		System.out.println("静态的方法");
	}

	public static class StaticInnerClass {

		public static String innerStaticPropStr = "静态内部类的静态的字符串属性";

		public static void innerStaticMethod() {
			System.out.println("静态内部类的静态的方法");
		}

		public String innerPropStr = "静态内部类的非静态的字符串属性";

		public void innerMethod() {
			System.out.println("静态内部类的非静态的方法");
		}

	}

	public static final void main(String[] srgs) {
		// 静态属性和方法可以直接用类名访问
		System.out.println(OuterClassForStatic.staticPropStr);
		OuterClassForStatic.staticMethod();

		// 访问静态内部类中的静态属性和方法可以直接用内部类的类名OuterClasssForStatic.StaticInnerClass访问
		System.out.println(OuterClassForStatic.StaticInnerClass.innerStaticPropStr);
		OuterClassForStatic.StaticInnerClass.innerStaticMethod();

		// 访问静态内部类中的非静态属性和方法需要先实例化内部类,再用内部类的实例化对象访问
		// 实例化静态内部类时,内部类的构造方法可以看成是外部类的静态方法,直接用类名访问
		OuterClassForStatic.StaticInnerClass inner = new OuterClassForStatic.StaticInnerClass();
		// 访问静态内部类中的非静态属性和方法,用内部类的实例化对象访问
		System.out.println(inner.innerPropStr);
		inner.innerMethod();

	}

}

3.2 非静态内部类

再看非静态情形:

/*
 * 包含非静态属性、方法、内部类的类
 */
public class OuterClasssForInstance {

	public String notStaticPropStr = "非静态的字符串属性";

	public void notStaticMethod() {
		System.out.println("非静态的方法");
	}

	public class NotStaticInnerClass {

		// 在非静态内部类中定义的静态属性必须定义为final
		public static final String innerNotStaticPropStr = "非静态内部类的静态的字符串属性(必须为常量)";

		/*
		 *  在非静态内部类中不允许定位静态方法(即使适用了final也不行)。
		 *  以下的语句会引起编译失败
		 */
		public static final void innerNotStaticMethod() {
			System.out.println("非静态内部类的静态的方法");
		}

		public String innerPropStr = "非静态内部类的非静态的字符串属性";

		public void innerMethod() {
			System.out.println("非静态内部类的非静态的方法");
		}

	}

	public static final void main(String[] srgs) {
		// 要访问非静态的属性、方法及内部类,需要先实例化
		OuterClasssForInstance outerInstance = new OuterClasssForInstance();

		// 非静态属性和方法需要通过实例化对象访问
		System.out.println(outerInstance.notStaticPropStr);
		outerInstance.notStaticMethod();

		// 访问非静态内部类中的静态属性(常量)、需要通过外部类的实例化对象结合内部类的类名进行访问
		System.out.println(OuterClasssForInstance.NotStaticInnerClass.innerNotStaticPropStr);
		// 由于无法在非静态内部类中定义静态方法,故以下语句也是错误的。
		OuterClasssForInstance.NotStaticInnerClass.innerNotStaticMethod();

		// 访问非静态内部类中的非静态属性和方法,需要先实例化内部类,再用内部类的实例化对象访问
		// 实例化非静态内部类时,内部类的类型及构造方法可以看成是外部类的非静态方法,需要通过外部类的实例化对象访问。
		// 这里要注意的是,与一般调用构造方法不同,这里的new关键字要放在实例化对象的"."之后。
		OuterClasssForInstance.NotStaticInnerClass inner = outerInstance.new NotStaticInnerClass();

		// 访问非静态内部类中的非静态属性和方法,用内部类的实例化对象访问
		System.out.println(inner.innerPropStr);
		inner.innerMethod();

		// 非静态类的实例化是非常特别的,初学者可能不太好理解,所以尤其要注意。
		// 另外作为一个更显特殊的情况,看以下一条语句
		OuterClasssForInstance.NotStaticInnerClass inner2 = new OuterClasssForInstance().new NotStaticInnerClass();
		// 如果将上一条语句分解为以下语句,就好理解了。
		OuterClasssForInstance oter2 = new OuterClasssForInstance();
		OuterClasssForInstance.NotStaticInnerClass inner2 = oter2.new NotStaticInnerClass();

	}

}

3.3 小结

从以上可以看出,内部类的要点和难点在于对于内部类的构造方法的理解,我们只需要把内部类的构造方法理解为“静态内部类的构造方法看成是其所在外部类的静态方法,而非静态内部类的构造方法看成是其所在外部类的非静态方法”就可以了。

另外,在静态内部类中可以访问外部类的静态属性和方法、不能访问非静态属性和方法;而非静态内部类可以访问外部类的静态和非静态的属性、方法。这一点与方法对属性和其他区方法的访问规则相似。

4 内部类的一个典型应用——线程安全、延迟实例化的单例对象

我们知道单例模式中一个对象只会创建唯一的一个实例,在这里,让一个对象提供服务时,怎样仅仅创建一个实例是关键;而要想这唯一的实例只在第一次使用时才创建,同时还要保证在并发环境下的线程安全和高效,则并不是容易的。

先看一个简单的做法:

/**
 * 简单的单例对象
 */
public class SimpleSingletonObject {

	/**
	 * 将构造方法定义为private,防止从外部实例化该对象。
	 */
	private SimpleSingletonObject() {
		super();
	}

	public void doTask() {
		System.out.println("执行单例对象的任务");
	}

	/** 保持唯一的实例 */
	private static SimpleSingletonObject instance;

	/**
	 * 获取唯一的对象实例。
	 * 使用延迟实例化,只有在第一次使用使才创建实例。
	 * @return 唯一的对象实例
	 */
	public static SimpleSingletonObject getInstance() {
		if (instance == null) {
			// 只在尚未实例化时才创建新实例
			instance = new SimpleSingletonObject();
		}

		// 返回唯一的实例
		return instance;
	}

}
以上代码实现了简单的单例模式。但是不能保证线程安全,在并发环境下,可能会创建多个实例,虽然最后只会保留一个实例,对内存要求不高时还是可以忍受的。

要做到线程安全的话,一般可以使用以下几种方法:

 (1) 使用synchronized关键字定义getInstance方法;

 (2) 将if (instance == null)的语句块包裹在synchronized块中;

 (3) 使用互斥锁(ReentrantLock)。

这里不想过多讨论多线程编程的相关知识,只能简单的说以上3种方法不算太高效。

下面将使用内部类巧妙的实现线程安全、延迟实例化的单例对象。

/**
 * 使用内部类实现的单例对象
 */
public class SingletonObjectUsingInnerClass {

	/**
	 * 将构造方法定义为private,防止从外部实例化该对象。
	 */
	private SingletonObjectUsingInnerClass() {
		super();
	}

	public void doTask() {
		System.out.println("执行单例对象的任务");
	}

	/**
	 * 用内部类保持单例对象的唯一实例。
	 */
	private static class InstanceFactory {
		/**
		 * 保持唯一的实例。
		 * 静态属性的初始化在类加载时才进行。
		 */
		private static SingletonObjectUsingInnerClass instance = new SingletonObjectUsingInnerClass();
	}

	/**
	 * 获取唯一的对象实例。
	 * 使用延迟实例化,只有在第一次使用使才创建实例。
	 * @return 唯一的对象实例
	 */
	public static SingletonObjectUsingInnerClass getInstance() {
		/*
		 * 返回唯一的实例。
		 * 这里利用Java虚拟机的类加载机制,
		 * 类只有在第一次使用时才被Java虚拟机记载,
		 * 这时才会因为初始化静态属性而引起创建唯一的实例,
		 * 而类初次使用和加载的过程会由Java虚拟机保证线程安全,
		 * 这样就实现了线程安全、延迟实例化的单例对象,并且相对来说还算高效。
		 */
		return InstanceFactory.instance;
	}

}
以上代码还是比较简洁的,其中使用内部类封装了单例对象的唯一实例的实例化逻辑,同时利用类加载机制巧妙的实现了线程安全和延迟实例化。

5 总结

内部类是一种特别的对象,他既具有普通类的各种特点,又具有类的属性、方法的一部分特点。虽然内部类不那么容易,但是如果我们了解了他的本质,使用起来也并不那么难。总而言之,Java中内部类的使用是为了更好的进行代码逻辑的封装,隐藏实现细节。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值