《Java编程思想》第10章 内部类

书中源代码:https://github.com/yangxian1229/ThinkingInJava_SourceCode
可以将一个类的定义放在另一个类的定义内部,这就是内部类。
内部类与组合是完全不同的概念。

10.1 创建内部类

把类的定义置于外围类的里面。更典型的情况是,外部类有一个方法,该方法返回一个指向内部类的引用,就像在to()和contents()方法中看到的那样。

//: innerclasses/Parcel2.java
// Returning a reference to an inner class.

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。

10.2 链接到外部类

内部类拥有其外围类的所有元素的访问权。

//: innerclasses/Sequence.java
// Holds a sequence of Objects.

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
*///:~

10.3 使用.this与.new

如果需要生成对外部类对象的引用,可以使用外部类后面紧跟圆点和this

//: innerclasses/DotThis.java
// Qualifying access to the outer-class object.

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语法。

//: innerclasses/DotNew.java
// Creating an inner class directly using the .new syntax.

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

要想直接创建内部类的对象,不能引用外部类的名字DotNew,而是必须使用外部类的对象来创建内部类对象。在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地链接到创建它的外部类对象上。但是,如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。

10.4 内部类与向上转型

当将内部类向上转型为其基类,尤其是转型为一个接口的时候,内部类就有了勇武之地。这个是因为此内部类——某个接口的实现——能够完全不可见,并且不可用。所得到的只是指向基类或接口的引用,所以能够很方便地实现隐藏。
//两个接口

//: innerclasses/Destination.java
public interface Destination {
  String readLabel();
} ///:~
//: innerclasses/Contents.java
public interface Contents {
  int value();
} ///:~
//: innerclasses/TestParcel.java

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();
  }
} ///:~

10.5 在方法和作用域内的内部类

在方法的作用域内创建一个完整的类。这被称为局部内部类

//: innerclasses/Parcel5.java
// Nesting a class within a method.

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
你可以在同一子目录下的人以类中对某个内部类使用类标识符PDestination,这并不会有命名冲突。
你可以在任意的作用域内嵌入一个内部类。在作用域之外,它是不可用的,除此之外,它与普通类一样。

10.6 匿名内部类

//: innerclasses/Parcel7.java
// Returning an instance of an anonymous inner class.

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的匿名类的对象。通过new表达式返回的引用被自动向上转型为对Contents的引用。上述匿名内部类的语法是下述形式的简化形式:

扫描二维码关注公众号,回复: 4226670 查看本文章
//: innerclasses/Parcel7b.java
// Expanded version of Parcel7.java

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。下面代码展示的是,如果你的基类需要一个有参数的构造器,应该怎么办:

//: innerclasses/Parcel8.java
// Calling the base-class constructor.

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);
  }
} ///:~

//: innerclasses/Wrapping.java
public class Wrapping {
  private int i;
  public Wrapping(int x) { i = x; }
  public int value() { return i; }
} ///:~

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

//: innerclasses/Parcel9.java
// An anonymous inner class that performs
// initialization. A briefer version of Parcel5.java.

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的,就像你在destination()的参数中看到的那样。
如果只是简单地给一个字段赋值,那么此例中的方法是很好的。但是,如果想做一些类似构造器的行为,该怎么办呢?在匿名类中不可能有命名构造器(一位它根本就没有名字),但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果,就像这样:

//: innerclasses/AnonymousConstructor.java
// Creating a constructor for an anonymous inner class.
import static net.mindview.util.Print.*;

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被传递给匿名类的基类构造器,它并不会在匿名类内部被直接使用。

10.6.1 再访工厂方法

//: innerclasses/Factories.java
import static net.mindview.util.Print.*;

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
*///:~

现在用于Implementation1Implementation2的构造器都可以是private的,并且没有任何必要去创建作为工厂的具名类。另外,你经常只需要单一的工厂对象,因此在本例中他被创建为Serviec实现中的一个static域。

10.7 嵌套类

如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。者通常称为嵌套类。想要理解static应用于内部类时的含义,就必须记住,普通的内部类对象隐式地保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时候,就不是这样了。嵌套类意味着:
1)要创建嵌套类的对象,并不需要其外围类的对象。
2)不能从嵌套类的对象中访问非静态的外围类对象。
嵌套类与普通的内部类还有一个区别。普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西。嵌套类没有**.this这个特殊引用,这使得它类似于一个static**方法。

10.7.1 接口内部的类

你放到接口中的任何类都自动的是publicstatic的。

10.7.2 从多层嵌套类中访问外部类的成员

一个内部类被嵌套多少层并不重要——它能透明的访问所有它所嵌入的外围类的所有成员。

10.8 为什么需要内部类

每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
必须在一个类中以某种方式实现两个接口。由于接口的灵活性,可以有两种选择:使用单一类,或者使用内部类。//: innerclasses/MultiInterfaces.java
如果拥有的是抽象的类或者具体的类,而不是接口,那就只能使用内部类才能实现多重继承。//: innerclasses/MultiImplementation.java

10.8.1 闭包与回调

闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。

10.8.2 内部类与控制框架

(看完22章,再回来看)

10.9 内部类的继承

//: innerclasses/InheritInner.java
// Inheriting an inner class.

class WithInner {
  class Inner {}
}

public class InheritInner extends WithInner.Inner {//。。。。
  //! InheritInner() {} // Won't compile
  InheritInner(WithInner wi) {
    wi.super();
  }
  public static void main(String[] args) {
    WithInner wi = new WithInner();
    InheritInner ii = new InheritInner(wi);
  }
} ///:~

猜你喜欢

转载自blog.csdn.net/lanzijingshizi/article/details/84446982
今日推荐