为了避免混淆,在说明内部类之前,先来看看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源文件中不与文件同名的类可不是内部类,我们先讨论这种情况也是为了以示区别,避免初学者混淆。
现在,我们要进入正题了。内部类是指在一个类内部定义的类,就像在类中定义的属性和方法那样,但是却与属性和方法有所不同。
对于初学者,往往在静态内部类和非静态内部类的实例化和使用上存在很多困扰。实际上如果我们对静态和非静态有了深刻理解的话,这样的困扰也就迎刃可解了。
我们要时刻记住静态的,不管是属性、方法还是内部类,都可以直接通过其所在外部类的类名访问;而非静态的,不管是属性、方法还是内部类,都需要先实例化其所在外部类的对象,然后通过实例化对象访问。我们还是来看看具体代码。
先看静态情形:
/*
* 包含静态属性、方法、内部类的类
*/
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();
}
}
再看非静态情形:
/*
* 包含非静态属性、方法、内部类的类
*/
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();
}
}
从以上可以看出,内部类的要点和难点在于对于内部类的构造方法的理解,我们只需要把内部类的构造方法理解为“静态内部类的构造方法看成是其所在外部类的静态方法,而非静态内部类的构造方法看成是其所在外部类的非静态方法”就可以了。
另外,在静态内部类中可以访问外部类的静态属性和方法、不能访问非静态属性和方法;而非静态内部类可以访问外部类的静态和非静态的属性、方法。这一点与方法对属性和其他区方法的访问规则相似。
我们知道单例模式中一个对象只会创建唯一的一个实例,在这里,让一个对象提供服务时,怎样仅仅创建一个实例是关键;而要想这唯一的实例只在第一次使用时才创建,同时还要保证在并发环境下的线程安全和高效,则并不是容易的。
先看一个简单的做法:
/**
* 简单的单例对象
*/
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;
}
}