1 内部类与接口组合的意义
我们都知道,内部类可以通过多种方式实现多重继承,而在这些方法中比较方便的就是配合接口实现多重继承,在达到这个目的外,这个方法还有一个额外的好处:可以通过向上转型(转为父类或者接口)来隐藏内部类的实现细节,仅仅得到其上层的一个引用,这在许多逻辑设计中都是适用的。
比如在设计时,将一辆车作为外围类,其各组件为内部类,组件都是通过接口来实现的,我们通过一定的方式让接口的引用来持有这些组件对象,就能对组件对象的“可见度”做很好的控制和保护,从而我们就无法对组件肆意访问或修改。
2 内部类与接口组合示例
2.1 一个简单的示例
我们将创建一个接口及实现它的内部类并对其测试
// 零件接口
interface Component {
String toString();
}
class Car {
// 创建“轮子类”来实现零件接口
class Wheel implements Component {
public String toString() { return "This is a Wheel."; }
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Car c = new Car();
Car.Wheel cw = c.new Wheel();
System.out.println(cw.toString());
}
}
/* Output
This is a Wheel.
*/
注意:
1 此处 Wheel 内部类前面没有访问权限修饰词,则默认为包访问权限,即包含 Car 类的包都能够访问这个内部类。
2 接口内部的方法默认为 public 类型。
2.2 通过接口完全隐藏内部类
要想完全隐藏内部类,只需要在内部类前面添加 private 访问权限即可。
interface Component {
String toString();
}
class Car {
// 创建“轮子类”来实现零件接口
private class Wheel implements Component {
public String toString() { return "This is a Wheel."; }
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Car c = new Car();
Car.Wheel cw = c.new Wheel(); // 此行有错误
}
}
当我们运行上述程序时,编译器报错如下:
Exception in thread "main" java.lang.Error: Unresolved compilation problems:
The type Car.Wheel is not visible
The type Car.Wheel is not visible
错误消息:The type Car.Wheel is not visible(大意:Car 类的内部类 Wheel 类是不可见的)显示了两次,这说明无论是创建此内部类的引用(通过外部类类名访问内部类名称都不能!)或是创建内部类的对象,它都会出错,也就是说它对于外部(除去包含它的类)是完全隐藏的。接下来我会给出解决这一问题的最好途径。
在上一篇博客中提到创建有关内部类的常用方法,其用来向外部环境传送内部类对象引用;此外我们还需要一个可以接受并持有此内部类对象的引用,达到此目的方法只有一个,就是创建”与 private 内部类有父子关系的接口或者类”的引用,即在外围类中创建一个返回内部类对象引用的方法。通过上述方法就可以解决如何在外部环境创建 private 内部类引用的问题。
解决方案示例如下:
// 零件接口
interface Component {
String toString();
}
class Car {
// 创建“轮子类”来实现接口
private class Wheel implements Component { // 添加 private 权限
public String toString() { return "This is a Wheel."; }
}
// 返回内部类对象引用
public Wheel getWheel() {
return new Wheel();
}
}
// 测试类
public class Test {
public static void main(String[] args) {
Car c = new Car();
Component cw = c.getWheel(); // 引用声明为接口类型
System.out.println(cw.toString());
}
}
/* Output
This is a Wheel.
*/
注意:
1 返回“private 内部类对象的引用”的方法不能声明为 private 类型,否则没有外部环境可以访问这个方法,从而无法得到内部类的对象引用。
2 ”与 private 内部类有父子关系的接口或者类”必须对特定情况是可见的,否则无法创建持有 private 内部类的引用。
3 向上转型可能会失去部分数据成员或者方法,关于这点要小心。
2.3 内部类与外围类可视度的不同
1.内部类可以可以看见外围类的任何成员,包括数据成员与方法。原因是:无论外围类的成员声明为 private、protected 还是包权限,它们都是满足“此类中可见的”,而内部类在外围类中,从逻辑上看它们也是外围类的“成员”,是它的一部分,所以它们互相可见。
2.外围类无法看见其内部类的任何成员(static 内部类要例外讨论,这种情况下可以通过内部类名来访问 static 内部类的 static 成员,但 static 内部类会丧失内部类的诸多优点,我们很少会使用到这个特性),即使内部类的成员声明为 public 类型。原因是:我们知道访问成员只有两种方法,
- 1.通过 static 类名直接访问其 static 成员。
- 2 通过类对象来访问成员。
我们在此不讨论第一种情况。第二种情况提到访问成员必须有对象,但在我们通过外部类对象的方法修改内部类成员时,此时内部类对象并没有被创建,这是违反 java 逻辑的,所以外围类不能访问(非 static)内部类的成员。
3 为什么要使用内部类?
据 Java 大牛 Bruce Eckel 所说,使用内部类最吸引人的原因是:”每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。”
根据上述言论,我们可以得到这样的结论:当遇到特定问题,比如在一个类里面实现多重继承时,接口解决了部分问题,而内部类有效的实现了“多重继承”(其不仅可以继承对象,还能够继承类或抽象类)。
注意:内部类并非是”所有可用以使用它的方案”中的第一选择,因为内部类的语法较复杂,如果普通的类可以满足要求,我们就应该使用普通的类而非内部类。比如,需求是要一个类继承单个接口(类或抽象类),这时我们只需要外围类进行继承就行,没必要用内部类去实现。
但是某些情况中,我们必须使用内部类。当我们需要在一个类中实现多重继承时,其继承的不仅仅只有接口,还包括抽象类或者具体类时,此时只靠外围类去继承它们就无法做到了(因为一个类不能同时继承接口和抽象类(或其他具体类)),我们还必须使用内部类去实现其它的接口(抽象类或具体类)。
4 总结
- 内部类特性使得 Java 的多重继承方案变得完整。
- 在使用内部类前要在设计阶段给出足够的理由,它并非总是第一选择。
- 接口是内部类最好的伙伴,它们的组合可以让内部类灵活得多。
- ”内部类看外围类的可视度”与“外围类看内部类的可视度”完全不同。
5 备注
更多细节可以看 Bruce Eckel 所著的《Java 编程思想》,本博文多为该书的学习心得,欢迎大家一起探讨。