栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

《OnJava》——11内部类

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

《OnJava》——11内部类

内部类

利用内部类,可以将逻辑上存在关联的类组织在一起,而且可以控制一个类在另一个类中的可见性。

内部类和组合不同,内部类是一种代码隐藏机制:将代码放在其他类的内部。

11.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; }
  }
  // 在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");
  }
}

返回一个指向内部类的引用

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:
    //要在内部类的非静态方法之外的任何地方创建内部类对象
    //必须将对象的类型指定为OutClassName.InnerClassName
    //即,将实例指定到详细的类
    Parcel2.Contents c = q.contents();
    Parcel2.Destination d = q.to("Borneo");
    
    System.out.println("c = " + c.value());
    System.out.println("d = " + d.readLabel());
  }
}


11.2 到外部类的链接

内部类看上去像是一种名称隐藏和代码组织机制。

当创建一个内部类时,这个内部类的对象中会隐含一个链接,指向用于创建该对象的外围对象。

通过这个链接,无须任何特殊条件,内部类对象就可以访问外围对象的成员。

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

//保存一个对象序列

//迭代器
interface Selector {
  boolean end();
  Object current();
  void next();
}
//Sequence是以类的形式包装起来的定长Object数组。
public class Sequence {
  private Object[] items;
  private int next = 0;
  public Sequence(int size) {
    items = new Object[size];
  }
  
  //使用add()方法向序列末尾增加一个新的Object(有空间的前提下)
  public void add(Object x) {
    if(next < items.length)
      items[next++] = x;
  }
  
  //实现“迭代器”接口,重写方法
  private class SequenceSelector implements Selector {
    //在SequenceSelector中使用到了外围items(private)
    //内部类可以访问外围对象的所有方法和字段,就好像拥有它们一样
    private int i = 0;
    @Override
    public boolean end() { return i == items.length; }
    @Override
    public Object current() { return items[i]; }
    @Override
    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();
    }
  }
}

内部类可以自动访问外围类的所有成员。

这是怎么做到的呢?

对于负责创建内部类对象的特定外围类对象而言,内部类对象偷偷地获取了一个指向它的引用。(内部得外部的属性)

该引用会被用于选择相应的成员

内部类的对象只能与其外部类的对象关联创建(当内部类为非static时)

11.3 使用 .this和.new

若要生成外部类对象的引用,可以使用外部类的名字,后面加上句点和this。

即 Inner.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"
      //如果直接写this,引用的是Inner的this,当前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();
  }
}

使用.new语法,在new表达式中提供指向其他外部类对象的引用:

public class DotNew {
  public class Inner {}
  public static void main(String[] args) {
    DotNew dn = new DotNew();
    DotNew.Inner dni = dn.new Inner();
  }
}
//除非已经有了一个外部类的对象,否则创建内部类对象是不可能的
//因为内部类的对象会暗中连接到用于创建它的外部类对象
//如果你创建的是嵌套类(static修饰的内部类),就不需要指向外部类对象的引用
11.4 内部类和向上转型

当需要向上转型为基类,特别是接口时,内部类就更有吸引力了。(从实现某个接口的对象生成一个该接口类型的引用,其效果和向上转型为某个基类在本质上是一样的)

这是因为,内部类(接口的实现)对外部而言可以时不可见的、不可用的,这便于隐藏实现。

外部获得的只是一个指向基类或接口的引用。

创建接口:接口会自动将其所有成员设置为public的。

public interface Destination {
  String readLabel();
}
public interface Contents {
  int value();
}

当得到一个指向基类或接口的引用时,无法找到其确切的类型:

class Parcel4 {
  private class PContents implements Contents {
    private int i = 11;
    @Override public int value() { return i; }
  }
  protected final class PDestination implements Destination {
    private String label;
    private PDestination(String whereTo) {
      label = whereTo;
    }
    @Override
    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:
    //非法————不能访问private类
    //- Parcel4.PContents pc = p.new PContents();
  }
}

在Parcel4中,内部类PContents时private的,所以只有Parcel4能访问它。

普通类(非内部类)无法声明为private的或protected的,它们只能被给予public或包访问权限。

PDestination是protected的,所以它只能被Parcel4、相同包中的类(因为protected也给予了包访问权限),以及Parcel4的子类访问。

这意味着客户程序员对这些成员的了解是有限的,对它们的访问权限也是有限的。

private内部类为类的设计者提供了一种方式,可以完全组织任何与类型相关的编码依赖,并且可以完全隐藏实现细节。

11.5 在方法和作用域中的内部类

内部类可以在一个方法内或者任何一个作用域内创建。

  1. 在方法中定义的类
  2. 在方法中的某个作用域内定义的类
  3. 实现某个接口的匿名类
  4. 这样的匿名类——它继承了拥有非默认构造器的类
  5. 执行字段初始化的匿名类
  6. 它通过实例初始化来执行构造(匿名内部类不可能有构造器)的匿名类。

局部内部类:在一个方法的作用域内(而不是在另一个类的作用域内)创建一个完整的类。

public class Parcel5 {
  
  public Destination destination(String s) {
    
    final class PDestination implements Destination {
      private String label;
      private PDestination(String whereTo) {
        label = whereTo;
      }
      @Override
      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的一部分。因此,PDestination在destination()外无法访问。

在return语句中的向上转型意味着destination()中只传出了一个指向Destination接口的引用。

PDestination类的名字被放在了destination()中这一事实,并不意味着一旦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();
  }
}
11.6 匿名内部类
public class Parcel7 {
  public Contents contents() {
    return new Contents() { // 插入类定义
      private int i = 11;
      @Override public int value() { return i; }
    }; // 分号是必须的
  }
  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;
    @Override 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 Parcel8 {
  public Wrapping wrapping(int x) {
    // Base constructor call:
    return new Wrapping(x) {          // [1]
      @Override public int value() {
        return super.value() * 47;
      }
    };                                // [2]
  }
  public static void main(String[] args) {
    Parcel8 p = new Parcel8();
    Wrapping w = p.wrapping(10);
  }
}

【1】将适当的参数传给基类构造器

【2】匿名内部类末尾的分号并不是用来标记类体的结束。相反,它标记表达式的结束,而该表达式恰好包含了这个匿名类。因此,它和分号在其他地方的用法没什么不同。

尽管,Wrapping是一个带有实现的普通类,但它也被用作其子类的公共“接口”:

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

也可以在定义匿名类中的字段时执行初始化:

public class Parcel9 {
  // Argument must be final or "effectively final"
  //要在匿名内部类中使用
  // to use within the anonymous inner class:
  // 参数必须是最终变量,或者"实际上的最终变量"
  public Destination destination(final String dest) {
    return new Destination() {
      private String label = dest;
      @Override
      public String readLabel() { return label; }
    };
  }
  public static void main(String[] args) {
    Parcel9 p = new Parcel9();
    Destination d = p.destination("Tasmania");
    System.out.println("d = " + d.readLabel());
  }
}

如果你正在定义一个匿名类,而且一定要用到一个在该匿名类之外定义的对象,编译器要求参数引用用final修饰,或者是“实际上的最终变量”(也就是说,在初始化之后它永远不会改变,所以它可以被视为final)

就像你在destination()的参数中看到的那样。这里不写final也没有任何问题,但把它写上当作提醒通常最好。

如果只是要给一个字段赋值,这个实例中的方法就很好。

但是如果必须执行某个蕾丝构造器的动作,该怎么办呢?因为匿名类没有名字,所以不可能有命名的构造器。

借助实例初始化,我们可以在效果上为匿名内部类创建一个构造器:

abstract class Base {
  Base(int i) {
    System.out.println("Base constructor, i = " + i);
  }
  public abstract void f();
}

public class AnonymousConstructor {
  public static Base getBase(int i) {
    return new Base(i) {
      { System.out.println("Inside instance initializer"); }
      @Override public void f() {
        System.out.println("In anonymous f()");
      }
    };
  }
  public static void main(String[] args) {
    Base base = getBase(47);
    base.f();
  }
}

这里变量i并不是必须为最终变量。尽管i被传给了匿名类的基类构造器,但是在该匿名类内部,它并没有被直接使用到。

对比一下,当参数在匿名类内部被用到:

**
 * 使用"实例初始化",来执行匿名内部类的构造
 */
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;
      @Override
      public String readLabel() { return label; }
    };
  }
  public static void main(String[] args) {
    Parcel10 p = new Parcel10();
    Destination d = p.destination("Tasmania", 101.395F);
  }
}

我们在实例初始化操作内可用看到一段代码,也就是if语句,它们不能作为字段初始化的一部分来执行。

所以在效果上,实例初始化就是匿名内部类的构造器。不过它也有局限性——我们无法重载实例初始化部分,所以只能有一个这样的构造器。

与普通的继承相比,匿名内部类有些局限性,因为它们要么是扩展一个类,要么是实现一个接口,但是两者不可兼得。

而且就算要实现接口,也只能实现一个。

11.7 嵌套类

如果不需要内部类对象和外部类对象之间的连接,可以将内部类设置为static的。这就是嵌套类。

普通内部类对象中隐式地保留了一个引用,指向创建该对象的外部类对象。

对于static的内部类(嵌套类)来说:

  1. 不需要一个外部类对象来创建嵌套类对象
  2. 无法从嵌套类对象内部访问非static的外部类对象

嵌套类和普通内部类之间的不同:

普通内部类的字段和方法,只能放在类的外部层次中,所以普通内部类中不能有static数据、static字段,也不能包含嵌套类

但是嵌套类中可以包含:其他的静态元素

public class Parcel11 {
  private static class ParcelContents implements Contents {
    private int i = 11;
    @Override public int value() { return i; }
  }
  protected static final class ParcelDestination implements Destination {
    private String label;
    private ParcelDestination(String whereTo) {
      label = whereTo;
    }
    @Override
    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();
    System.out.println("c.value() = " + c.value());
    Destination d = destination("Tasmania");
    System.out.println("d.readLabel() = " + d.readLabel());
  }
}

这样在main()中并不需要Paecel11对象;

静态方法可以直接调用,类名调用和对象调用

非静态方法只能通过对象调用

相反,我们使用选择static成员的普通语法来调用方法,这些方法指向Contents和Destination类型的引用。

普通内部类(非static的)可以使用特殊的this(内部类.this)引用来创建指向外部类对象的连接。而嵌套类没有特殊的this引用,这使它和static方法类似。

11.7.1 接口中的类

嵌套类可以是接口的一部分。

放到接口中的任何类都会自动成为public和static的。

因为类是static的,所以被嵌套的类知识放在了这个接口的命名空间内。

甚至可以在内部类内实现包围它的这个接口:

public interface ClassInInterface {
  void howdy();

  
  class Test implements ClassInInterface {
    @Override public void howdy() {
      System.out.println("Howdy!");
    }
    public static void main(String[] args) {
      new Test().howdy();
    }
  }
}


当你要创建某个接口的所有不同实现使用的公用代码时,将一个类嵌入这个接口中会非常方便。

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

一个内部类被嵌套多少册并不重要。

它可以透明地访问它的所有类的所有成员,如下面的代码所示:

class MNA {
  private void f() {}
  class A {
    private void g() {}
    public class B {
      public void h() {
        System.out.println("B::h()");
        g();
        {
          System.out.println("B::CodeBlock");
        }
        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();
  }
}


通过使用内部类来提供单独的实现,满足更多的需求。

当创建内部类时,并没有增加或修改外围类的接口。

参考《OnJava》Visit http://OnJava8.com for more book information.

转载请注明:文章转载自 www.mshxw.com
本文地址:https://www.mshxw.com/it/990760.html
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号