内部类
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。
最初,内部类看起来像一种代码隐藏机制:将类置于其他类的内部。但是内部类远不止如此,它了解外围类,并能与之通信;而且你用内部类写出的代码更加优雅而清晰,尽管不总是如此。
创建内部类:
将类的定义置于外围类的里面
在外围类的普通方法适用内部类的时候,与适用普通类没有不同
如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须像在main()方法中那样,具体的指明这个对象的类型:OuterClassName.InnerClassName.
链接到外部类
到目前为止,内部类似乎只还有一种名字隐藏和组织代码的模块。内部类还有其他用途。当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象所有的成员,而不需要任何特殊条件,此外,内部类还拥有其外围类的所有元素的访问权。
内部类自动拥有对其外围类所有成员的访问权。这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,次内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,你在访问次外围类的成员时,就是用那个引用来选择外围类的成员。
使用.this与.new
public class DoThis {
void f() {
System.out.println("DoThis.f()");
}
public class Inner {
public DoThis outer(){
return DoThis.this;
}
}
public Inner inner() {
return new Inner();
}
public static void main(String[] args) {
DoThis dt = new DoThis();
DoThis.Inner dti = dt.inner();
dti.outer().f();
}
}
如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this。这样产生的引用自动地具有正确的类型
public static void main(String[] args) {
DoThis dt = new DoThis();
DoThis.Inner dti = dt.inner();
dti.outer().f();
DoThis.Inner dni = dt.new Inner();
}
有时你可能想要告诉某些其他对象,去创建其某个内部类的对象。要实现此目的,你必须在new表达式中提供对其他外部类对象的引用,这时需要使用.new语法
在拥有外部类对象之前是不可能创建内部类对象的。这时因为内部类对象会暗暗地连接到创建它的外部类对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。
public class Parcel3 {
class Contents {
private int i = 11;
public int value() {
return i;
}
}
class Destination {
private String label;
Destination(String whereTo) {
label = whereTo;
}
String readLabel(){
return label;
}
}
public static void main(String[] args) {
Parcel3 p = new Parcel3();
//Must use instance of outer class
//to create an instance of the inner class:
Parcel3.Contents c = p.new Contents();
Parcel3.Destination d = p.new Destination("Tasmania");
}
}
内部类与向上转型
内部类向上转型为其基类,尤其是转型为一个接口的时候,很有用。
这是因为此内部类——某个接口的实现——能够完全不可见,并且不可用。所得到的知识指向基类或者接口的而引用,所以能够很方便的隐藏实现细节。
public class Parcel4 {
//现在Contents和Destination表示客户端程序员可用的接口。记住,接口中的所有成员自动被设置为public的
//当取得了一个指向基类或接口的引用时,甚至可能无法找出它确切的类型,看下面的例子
private class PContents implements Contents {
private int i = 11;
public int value() { return i; }
}
protected class PDestination implements Destination {
private String label;
private PDestination(String whereTo) {
label = whereTo;
}
public String readLabel() { return label; }
}
public Destination destination(String s) {
return new PDestination(s);
}
public Contents contents() {
return new PContents();
}
}
class TestParcel {
@Test
public void test1() {
Parcel4 p = new Parcel4();
Contents c = p.contents();
Destination d = p.destination("Tasmania");
}
}
interface Destination{
String readLabel();
}
interface Contents {
int value();
}
Parcel4中增加了一些新东西:内部类PContents是private,所以除了Parcel4,没有人能访问它。PDestination是protected,所以只有Parcel4及其自雷、还有与P[arcel4同一个包中的类能够访问PDestination,其他类都不能访问PDestination。这意味着,如果客户端程序员想要了解或者访问这些成员,那是要受到限制的。甚至不能向下转型成private内部类。于是private内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度来看,由于不能访问任何新增加的、完全不属于任何公共接口的方法,所以扩展接口是没有价值的。
内部匿名类
public class Parcel7 {
public Contents contents() {
return new Contents(){
private int i = 11;
public int value() {
return i;
}
};
}
public static void main(String[] args) {
Parcel7 p = new Parcel7();
Contents c = p.contents();
}
}
contents()方法将返回值的生成和表示这个返回值的类定义结合在一起!但是这个类是匿名的,它没有名字。看起来似乎是你正要创建一个Contents对象,但是,你却说:“等一等,我想在这里插入一个类的定义”。
这种奇怪的语法指的是:创建一个继承自Contents的匿名类的对象。通过new表达式返回的引用被自动向上转型为对Contents的引用。
如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用时final的。
如果想要做一些类似构造器的行为,怎么办呢?在匿名类中不可能有命名构造器(因为它根本没有名字!),但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果。
public Destination destination(final String dest, final float price){
return new Destination() {
private String label;
{
this.label = dest;
}
public String readLabel() {
return label;
}
};
}
使用内部匿名类达到工厂:
public class Factories {
}
interface Service {
void method1();
void method2();
}
interface ServiceFactory {
Service getService();
}
class Implementation1 implements Service {
private Implementation1() {}
public void method1() {System.out.println("Implementation1 method1");}
public void method2() {System.out.println("Implementation1 method2");}
public static ServiceFactory factory(){
return new ServiceFactory() {
public Service getService() {
return new Implementation1();
}
};
}
}
class Implementation2 implements Service {
private Implementation2() {}
public void method1() {System.out.println("Implementation2 method1");}
public void method2() {System.out.println("Implementation2 method2");}
public static ServiceFactory factory = new ServiceFactory() {
public Service getService() {
return new Implementation2();
}
};
}
嵌套类
如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为static。这通常被称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。
嵌套类意味着:
1、要创建嵌套类的对象,并不需要其外围类的对象
2、不能从嵌套类的对象中访问非静态的外围类对象
嵌套类内部可以有静态字段和方法而普通内部类是不允许存在的
接口内部的类
正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的人都自动是public和static的。因为类是static的,只是将嵌套类置接口的命名空间内,这并不违反接口的规则。甚至可以在内部类中实现其外围接口。
public interface ClassInterface {
void howdy();
class Test implements ClassInterface {
public void howdy() {
System.out.println("Howdy!");
}
public static void main(String[] args) {
new Test().howdy();
}
}
}
一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员。
为什么需要内部类?
一般来说,内部类继承自某个类或者实现某个接口,内部类的代码操作创建它的外围类的对象,所以可以认为内部类提供了某种进入其外围类的窗口
内部类必须要会回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外围类实现那个接口呢?答案是:“如果这能满足需求,那么就应该这样做”。那么内部类实现一个接口与外围类实现这个接口有什么区别呢?答案是:后者不能总享用到接口带来的方便,有时候需要用到接口的实现。所以使用内部类最吸引人的原因是:
每一个内部类都能独立地继承自一个实现,所以无论外围类是否已经继承了某个实现,对于内部类都没有影响。
如果没有内部类提供的、可以继承多个具体的或抽象的类的能力,一些设计与编程问题就很难解决,从这个角度上看,内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效的实现了“多重继承”。也就是说,内部类允许继承多个非接口类型
闭包与回调
闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象的信息,还拥有一个指向次外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。
public class Callbacks {
public static void main(String[] args) {
Callee1 c1 = new Callee1();
Callee2 c2 = new Callee2();
MyIncrement.f(c2);
Caller caller1 = new Caller(c1);
Caller caller2 = new Caller(c2.getCallbackReference());
caller1.go();
caller1.go();
caller2.go();
caller2.go();
}
}
interface Incrementable {
void increment();
}
class Callee1 implements Incrementable {
private int i = 0;
public void increment() {
i++;
System.out.println(i);
}
}
class MyIncrement {
public void increment() {
System.out.println("Other operation");
}
static void f(MyIncrement mi){ mi.increment();}
}
//加入你的类必须实现incrementable接口,你必须使用一个内部类
class Callee2 extends MyIncrement {
private int i = 0;
public void increment() {
super.increment();
i++;
System.out.println(i);
}
private class Closure implements Incrementable {
public void increment(){
//区别你的外部类方法,否则你将得到一个无线循环
Callee2.this.increment();
}
}
Incrementable getCallbackReference() {
return new Closure();
}
}
class Caller {
private Incrementable callbackReference;
Caller(Incrementable cbh) { callbackReference = cbh; }
void go() { callbackReference.increment();}
}
这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。就代码而言,Calle1是简单的解决方式。Callee2继承自MyIncrement,后者已经有了一个不同的increment()方法,并且与Incrementable接口期望的increment()方法完全不想管。所以如果Callee2继承了MyIncrement,就不能为了Incrementable的用途而覆盖increment()方法,于是只能使用内部类独立的实现Incrementable。
内部类与控制框架
在将要介绍的控制框架(control framework)中,可以看到更多使用内部类的具体例子。应用程序框架(application framework)就是被设计泳衣解决某类特定问题的一个类或一组类。要运用某个应用程序框架,通常是集成一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用框架提供的解决方案,已解决你的特定问题。模板方法包含算法的基本结构,并且会调用一个或多个可覆盖的方法,以完成算法的动作,设计模式总是将变化的事物与不变的事物分离开,在这个模式中,模板方法是保持不变的事物,而可控制的方法就是变化的事物。
要理解内部类是如何允许简单的创建过程以及如何使用控制框架的,请考虑这样一个控制框架,它的工作就是在事件“就绪”的时候执行事件。虽然“就绪”可以指任何事,但在本例中是指基于时间触发的事件。接下来的问题就是,对于要控制什么,控制框架并不包含任何具体的信息。那些信息是在实现算法的action部分的时候,通过继承来提供的。
public abstract class Event {
private long eventTime;
protected final long delayTime;
public Event(long delayTime) {
this.delayTime = delayTime;
start();
}
public void start(){
eventTime = System.nanoTime() + delayTime;
}
public boolean ready() {
return System.nanoTime() >= eventTime;
}
public abstract void action();
}
当希望运行Event并随后调用start()时,那么构造器就会获取事件,此时时间是这样得来的:start()获取当前事件,然后加上一个延迟时间没这样生成触发事件的时间。start()是一个独立的方法,而没有包含在构造器内,因为这样就可以在时间运行以后重新启动计时器,也就是能重复使用Event对象。例如,如果想要重复一个事件,只需简单地在action()中调用start()方法。
public class Controller {
private List<Event> eventList = new ArrayList<Event>();
public void addEvent(Event c) { eventList.add(c);}
public void run() {
while(eventList.size() > 0) {
for(Event e : new ArrayList<Event>(eventList))
if (e.ready()) {
System.out.println(e);
e.action();
eventList.remove(e);
}
}
}
}
run方法循环遍历eventList,寻找就绪的、要运行的Event对象。对找到的每一个就绪的时间,使用对象的toString()打印其信息,调用其action()方法,然后从队列中移除此Event。注意,在目前的设计中你并不知道Event到底做了什么。使用抽象将变化与不变进行了分离。
内部类允许:
控制框架的完整实现是由单个的类创建的,从而使得实现的细节被封装了起来。内部类用来表示解决问题所必需的各种不同的action()。
内部类能够很容易地访问外围类的任何成员,所以可以避免这种实现变得笨拙。
内部类的继承
因为内部类的构造器必须连接到指向其外围类对象的引用,所以在继承内部类的时候,事情会变得有些复杂。问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,从而在导出类中不再存在可连接的默认对象。
public class InheritInner extends WithInner.Inner{
InheritInner(WithInner wi){
wi.super();
}
public static void main(String[] args) {
WithInner wi = new WithInner();
InheritInner ii = new InheritInner(wi);
}
}
class WithInner {
class Inner {}
}
可以看到,InheritInner只继承自内部类,而不是外围类。但是当要生成一个构造器时,默认的构造器并不算好,而且不能只是传递一个指向外围你类对象的引用。此外,必须在构造器内使用以下语法:
enclosingClassReference.super();
这样才提供了必要的引用,然后程序才能编译通过。
局部内部类,在代码块中定义的内部类
内部类标识符
由于每个类都会产生一个.class文件,其中包含了如何创建该类型的对象的全部信息。内部类也必须生成一个.class文件已包含它们的Class对象信息。这些类文件的命名有严格的规则:外围类的额名字,加上"$",再加上内部类的名字
如果内部类是匿名的,编译器会简单的产生一个数字作为其标识符,如果内部类是嵌套在别的内部类之中,只需直接将它们的名字加在其外围类标识符与"$"的后面。