读thinking in java笔记(九):内部类

    可以将一个类的定义放在另一个类的定义内部,这就是内部类。
    内部类是一种非常有用的特性,因为它允许你把一些逻辑相关的类组织在一起,并控制位于内部的类的可见性。然而,必须要了解,内部类和组合是完全不同的概念,这一点非常重要。
    在最初,内部类看起来就像是一种代码隐藏机制,将类置于其他类的内部。但是,你将会了解到,内部类远不止如此,它了解外围类,并能与之通信,而且你用内部类写出的代码更加优雅而清晰,尽管并不总是这样。
1. 创建内部类
    创建内部类的方式就如同你想的一样,把类的定义置于外围类的里面:

public class Parcel1 {
  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; }
  } 
  // Using inner classes looks just like
  // using any other class, within Parcel1:
  public void ship(String dest) {
    Contents c = new Contents();
    Destination d = new Destination(dest);
    System.out.println(d.readLabel());
  }
  public static void main(String[] args) {
    Parcel1 p = new Parcel1();
    p.ship("Tasmania");
  }
} /* Output:
Tasmania
*/

    当我们在ship()方法里面使用内部类的时候,与使用普通类没什么不同。在这里实际的区别只是内部类的名字是嵌套在Parcel1里面的。不过你将会看到,这并不是唯一的区别。
    更典型的情况是,外部类将有一个方法,该方法返回一个指向内部类的引用,这就像在to()和contents()方法中看到的那样:

public class Parcel2 {
  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 Destination to(String s) {
    return new Destination(s);
  }
  public Contents contents() {
    return new Contents();
  }
  public void ship(String dest) {
    Contents c = contents();
    Destination d = to(dest);
    System.out.println(d.readLabel());
  }
  public static void main(String[] args) {
    Parcel2 p = new Parcel2();
    p.ship("Tasmania");
    Parcel2 q = new Parcel2();
    // Defining references to inner classes:
    Parcel2.Contents c = q.contents();
    Parcel2.Destination d = q.to("Borneo");
  }
} /* Output:
Tasmania
*/

    如果想从外部类的非静态方法之外的任意位置创建某个内部类对象,那么必须像在main()方法中那样,具体的指明这个对象的类型:OuterClassName.InnerClassName。
2. 链接到外部类
    到目前为止,内部类似乎还只是一种名字隐藏和组织代码的模式。这些是很有用的,但还不是最引人注目的,她还有其他的用途。当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系,所以它能访问其外围对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外围类的所有元素的访问权。

interface Selector {
  boolean end();
  Object current();
  void next();
}   

public class Sequence {
  private Object[] items;
  private int next = 0;
  public Sequence(int size) { items = new Object[size]; }
  public void add(Object x) {
    if(next < items.length)
      items[next++] = x;
  }
  private class SequenceSelector implements Selector {
    private int i = 0;
    public boolean end() { return i == items.length; }
    public Object current() { return items[i]; }
    public void next() { if(i < items.length) i++; }
  }
  public Selector selector() {
    return new SequenceSelector();
  } 
  public static void main(String[] args) {
    Sequence sequence = new Sequence(10);
    for(int i = 0; i < 10; i++)
      sequence.add(Integer.toString(i));
    Selector selector = sequence.selector();
    while(!selector.end()) {
      System.out.print(selector.current() + " ");
      selector.next();
    }
  }
} /* Output:
0 1 2 3 4 5 6 7 8 9
*/

    Sequence类只是一个固定大小的Object的数组,以类的形式包装了起来。可以调用add()在序列末增加新的Object(只要还有空间)。要获取Sequence中的每一个对象,可以使用Selector接口。这是“迭代器”设计模式的一个例子。Selector允许你检查序列是否到末尾了(end()),访问当前对象(current()),以及移到序列中的下一个对象(next())。因为Selector是一个接口,所以别的类可以按它们之间的方式来实现这个接口。
    这里,SequenceSelector是提供Selector功能的private类。可以看到,在main()中创建了一个Sequence,并向其中添加了一些String对象。然后通过调用selector获取一个Selector,并用它在Sequence中移动和选择每一个元素。
    最初看到SequenceSelector,可能会觉得它只不过是另一个内部类罢了。但仔细观察,注意方法end()、current()和next()都用到了items,这是一个引用,它并不是SequenceSelector的一部分,而是外围类中的一个private字段。然而内部类可以访问其外围类的方法和字段,就像自己拥有它们似的。
    所以内部类自动拥有对其外围所有成员的访问权。这是如何做到的呢?当某个外围类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然而,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有的细节,但你现在可以看到:内部类的对象只能在于其外围类的对象相关联的情况下才能被创建(就像你应该看到的,在内部类是非static类时)。构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。
3. 使用.this与.new
    如果你需要生成对外部类对象的引用,可以使用外部类的名字后面加.this。这样产生的引用自动的具有正确的类型,这一点在编译器就被知晓并接受检查,因此没有任何运行时开销。

public class DotThis {
  void f() { System.out.println("DotThis.f()"); }
  public class Inner {
    public DotThis outer() {
      return DotThis.this;
      // A plain "this" would be Inner's "this"
    }
  }
  public Inner inner() { return new Inner(); }
  public static void main(String[] args) {
    DotThis dt = new DotThis();
    DotThis.Inner dti = dt.inner();
    dti.outer().f();
  }
} /* Output:
DotThis.f()
*/

    使用new表达式创建内部类对象时,需要在new表达式中提供对其他外部类对象的引用。这就是所谓的.new语法。

public class DotNew {
  public class Inner {}
  public static void main(String[] args) {
    DotNew dn = new DotNew();
    DotNew.Inner dni = dn.new Inner();
  }
}

    要想直接创建内部类对象,你不能按照你想象的方式,去引用外部类的名字,而是必须使用外部类的对象来创建该内部类对象,就像上面看到的那样。
    在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗的连接到创建它的外部类的对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要外部类的引用。

.new引用与Parcel的示例:
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");
  }
} 

4. 内部类与向上转型
    当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了用武之地。(从实现了某个接口的对象,得到对此接口的引用,与向上转型为这个对象的基类,实质上效果是一样的。)这是因为此内部类(某个接口的实现)能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便的隐藏实现细节。

示例接口:
interface Destination {
  String readLabel();
}

interface Contents {
  int value();
} 

class Parcel4 {
  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();
  }
}

public class TestParcel {
  public static void main(String[] args) {
    Parcel4 p = new Parcel4();
    Contents c = p.contents();
    Destination d = p.destination("Tasmania");
    // Illegal -- can't access private class:
    //! Parcel4.PContents pc = p.new PContents();
  }
}

    Parcel4中增加了一些新东西:内部类PContents是private,所以除了Parcel4,没有人能访问它。PDestination是protected,所以只有Parcel4及其子类、还有与Parcel4同一个包中的类能访问PDestination,其他类都不能访问PDestination。这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。实际上,甚至不能向下转型成private内部类(或protected内部类,除非是继承自它的子类),因为不能访问其名字,就像在Testparcel类中看到的那样。于是,private内部类给类的设计者提供了一种途径,通过这种方式完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。此外,从客户端程序员的角度,由于不能访问任何新增的,原本不属于公共接口的方法,所以扩展接口是没有价值的。
5. 在方法和作用域内的内部类
    可以在一个方法里面或者在任意的作用域内定义内部类。这么做有两个理由:
    1. 你实现了某类型的接口,于是可以创建并返回对其的引用。
    2. 你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但是又不希望这个类是公共可用的。

在方法的作用域内创建一个完整的类。这被称为内部类:
public class Parcel5 {
  public Destination destination(String s) {
    class PDestination implements Destination {
      private String label;
      private PDestination(String whereTo) {
        label = whereTo;
      }
      public String readLabel() { return label; }
    }
    return new PDestination(s);
  }
  public static void main(String[] args) {
    Parcel5 p = new Parcel5();
    Destination d = p.destination("Tasmania");
  }
}

    PDestination类是destination()方法的一部分,而不是Parcel5的一部分。所以,在destination()方法之外不能访问PDestination。注意,出现在return语句中是向上转型,返回的类型是Destination的引用,它是PDestination的基类。
    你可以在同一个子目录下的任意类中对某个内部类使用类标识符PDestination,这并不会有命名冲突。

如何在任意的作用域内嵌入一个内部类:
public class Parcel6 {
  private void internalTracking(boolean b) {
    if(b) {
      class TrackingSlip {
        private String id;
        TrackingSlip(String s) {
          id = s;
        }
        String getSlip() { return id; }
      }
      TrackingSlip ts = new TrackingSlip("slip");
      String s = ts.getSlip();
    }
    // Can't use it here! Out of scope:
    //! TrackingSlip ts = new TrackingSlip("x");
  } 
  public void track() { internalTracking(true); }
  public static void main(String[] args) {
    Parcel6 p = new Parcel6();
    p.track();
  }
}

    TrackingSlip 被嵌入在if语句的作用域内,这并不是说该类的创建是有条件的,它其实与别的类一起编译过了。然而,在定义它的作用域之外,它是不可用的;除此之外,它与普通的类一样。
6. 匿名内部类

例子:
public class Parcel7 {
  public Contents contents() {
    return new Contents() { // Insert a class definition
      private int i = 11;
      public int value() { return i; }
    }; // Semicolon required in this case
  }
  public static void main(String[] args) {
    Parcel7 p = new Parcel7();
    Contents c = p.contents();
  }
}

    contents方法将返回值的生成与表示这个返回值的类的定义结合在一起!另外这个类是匿名的,它没有名字。更糟的是,看起来似乎是你正要创建一个Contents对象。但是然后(在到达语句结束的分号之前)你却说:“等一等,我想在这里插入一个类的定义”。
    这种奇怪的语法指的是:“创建一个继承自Contents的匿名类的对象。”通过new表达式返回的引用被自动向上转型为Contents的引用。上述匿名内部类的语法是下述形式的简化形式:

public class Parcel7b {
  class MyContents implements Contents {
    private int i = 11;
    public int value() { return i; }
  }
  public Contents contents() { return new MyContents(); }
  public static void main(String[] args) {
    Parcel7b p = new Parcel7b();
    Contents c = p.contents();
  }
}

    在这个匿名内部类中,使用了默认的构造器来生成Contents。下面的代码展示的是,如果你的基类需要一个有参数的构造器,应该怎么办:

public class Wrapping {
  private int i;
  public Wrapping(int x) { i = x; }
  public int value() { return i; }
} 

public class Parcel8 {
  public Wrapping wrapping(int x) {
    // Base constructor call:
    return new Wrapping(x) { // Pass constructor argument.
      public int value() {
        return super.value() * 47;
      }
    }; // Semicolon required
  }
  public static void main(String[] args) {
    Parcel8 p = new Parcel8();
    Wrapping w = p.wrapping(10);
  }
}

    只需要简单地传递合适的参数给基类的构造器即可,这里是将x传入new Wrapping(x)。尽管Wrapping只是一个具有具体实现的普通类,但它还是被其导出类当作公共“接口”来使用,我们注意到,Wapping拥有一个要求传递一个参数的构造器,这使得事情变得更加有趣了。
    在匿名内部类末尾的分号,并不是用来标记内部类结束的。实际上它标记的是表达式的结束,只是这个表达式正巧包含了匿名内部类罢了。因此,这与别的地方使用的分号是一致的。
    在匿名类中定义字段时,还能够对其执行初始化操作:

public class Parcel9 {
  // Argument must be final to use inside
  // anonymous inner class:
  public Destination destination(final String dest) {
    return new Destination() {
      private String label = dest;
      public String readLabel() { return label; }
    };
  }
  public static void main(String[] args) {
    Parcel9 p = new Parcel9();
    Destination d = p.destination("Tasmania");
  }
}

    如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的,就像你在上面方法中看到的那样。
    如果只是简单的给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(因为它根本没名字),但通过实例初始化,就能达到为匿名内部类创建一个构造器的效果,就像这样:

abstract class Base {
  public Base(int i) {
    print("Base constructor, i = " + i);
  }
  public abstract void f();
}   

public class AnonymousConstructor {
  public static Base getBase(int i) {
    return new Base(i) {
      { print("Inside instance initializer"); }
      public void f() {
        print("In anonymous f()");
      }
    };
  }
  public static void main(String[] args) {
    Base base = getBase(47);
    base.f();
  }
} /* Output:
Base constructor, i = 47
Inside instance initializer
In anonymous f()
*/

    在此例中,不要求变量i一定是final的。因为i被传递给匿名类的基类的构造器,它并不会在匿名类内部使用。
    下例是带实例初始化的“parcel”形式。注意destination()的参数必须是final的,因为它们是在匿名内部类中使用的。

public class Parcel10 {
  public Destination
  destination(final String dest, final float price) {
    return new Destination() {
      private int cost;
      // Instance initialization for each object:
      {
        cost = Math.round(price);
        if(cost > 100)
          System.out.println("Over budget!");
      }
      private String label = dest;
      public String readLabel() { return label; }
    };
  } 
  public static void main(String[] args) {
    Parcel10 p = new Parcel10();
    Destination d = p.destination("Tasmania", 101.395F);
  }
} /* Output:
Over budget!
*/

  6.1 再访工厂方法
    使用匿名内部类时Factories示例:

interface Service {
  void method1();
  void method2();
}

interface ServiceFactory {
  Service getService();
}   

class Implementation1 implements Service {
  private Implementation1() {}
  public void method1() {print("Implementation1 method1");}
  public void method2() {print("Implementation1 method2");}
  public static ServiceFactory factory =
    new ServiceFactory() {
      public Service getService() {
        return new Implementation1();
      }
    };
}   

class Implementation2 implements Service {
  private Implementation2() {}
  public void method1() {print("Implementation2 method1");}
  public void method2() {print("Implementation2 method2");}
  public static ServiceFactory factory =
    new ServiceFactory() {
      public Service getService() {
        return new Implementation2();
      }
    };
}   

public class Factories {
  public static void serviceConsumer(ServiceFactory fact) {
    Service s = fact.getService();
    s.method1();
    s.method2();
  }
  public static void main(String[] args) {
    serviceConsumer(Implementation1.factory);
    // Implementations are completely interchangeable:
    serviceConsumer(Implementation2.factory);
  }
} /* Output:
Implementation1 method1
Implementation1 method2
Implementation2 method1
Implementation2 method2
*/

    现在用于Implementation1 和Implementation2 的构造器都可以是private的,并且没有任何必要去创建工厂的具名类。另外你经常只需要单一的工厂,因此,在本例中它被创建为Service实现中的一个static域。
7. 嵌套类
    如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static时,就不是这样了。嵌套类意味着:
    1. 要创建嵌套类的对象,并不需要其外围类的对象。
    2. 不能从嵌套类的对象中访问非静态的外围类对象。
    嵌套类与普通的内部类还有一个区别。普通内部类的字段和方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西:

public class Parcel11 {
  private static class ParcelContents implements Contents {
    private int i = 11;
    public int value() { return i; }
  }
  protected static class ParcelDestination
  implements Destination {
    private String label;
    private ParcelDestination(String whereTo) {
      label = whereTo;
    }
    public String readLabel() { return label; } 
    // Nested classes can contain other static elements:
    public static void f() {}
    static int x = 10;
    static class AnotherLevel {
      public static void f() {}
      static int x = 10;
    }
  }
  public static Destination destination(String s) {
    return new ParcelDestination(s);
  }
  public static Contents contents() {
    return new ParcelContents();
  }
  public static void main(String[] args) {
    Contents c = contents();
    Destination d = destination("Tasmania");
  }
}

    在main()中,没有任何Parcel11的对象时必需的;而是使用选取static成员的普通方法来调用方法,这些方法返回对Contents和Destination的引用。
    就像你在本章前面看到的那样,在一个普通的内部类中,通过一个特殊的this引用可以链接到其他外围的类对象。嵌套类就没有这个特殊的this引用,这使得它类似一个static方法。
  7.1 接口内部的类
    正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分。你放到接口中的任何类都自动是public和static的。因为类是static的,只是将嵌套类置于接口的命名空间内,这并不违反接口的规则。你甚至可以在内部类中实现其外围接口,就像下面这样:

public interface ClassInInterface {
  void howdy();
  class Test implements ClassInInterface {
    public void howdy() {
      System.out.println("Howdy!");
    }
    public static void main(String[] args) {
      new Test().howdy();
    }
  }
} /* Output:
Howdy!
*/

    如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便。
  7.2 从多层嵌套类中访问外部类的成员
    一个内部类被嵌套多少层并不重要,它能透明地访问所有它所嵌入的外围类的所有成员。

class MNA {
  private void f() {}
  class A {
    private void g() {}
    public class B {
      void h() {
        g();
        f();
      }
    }
  }
}   

public class MultiNestingAccess {
  public static void main(String[] args) {
    MNA mna = new MNA();
    MNA.A mnaa = mna.new A();
    MNA.A.B mnaab = mnaa.new B();
    mnaab.h();
  }
}

    可以看到在MNA.A.B中,调用方法g()和f()不需要任何条件(即使它们被定为为private)。这个例子同时展示了如何从不同的类里创建多层嵌套的内部类对象的基本语法。“.new”语法能产生正确的作用域。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值