Java Note 6

Java 内部类

Posted by JH on September 7, 2020

1.内部类的创建

一个定义在另一个类中的类,叫作内部类

如果想从外部类的非静态方法之外的任意位置创建某个内部类的对象,那么必须具体地指明这个对象的类型:OuterClassName.InnerClassName

注:在外部类的静态方法中也可以直接指明类型 InnerClassName,在其他类中需要指明 OuterClassName.InnerClassName

2.链接外部类

当生成一个内部类的对象时,此对象与制造它的外部对象(enclosing object)之间就有了一种联系, 所以它能访问其外部对象的所有成员,而不需要任何特殊条件。此外,内部类还拥有其外部类的所有元素的访问权。

内部类的对象只能在与其外部类的对象相关联的情况下才能被创建(就像你应该看到的,内部类是非 static 类时)。 构建内部类对象时,需要一个指向其外部类对象的引用。

.this.new的使用

如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点this 的方式。这样产生的引用自动地具有正确的类型, 这一点在编译期就被知晓并受到检查,因此没有任何运行时开销。下面的示例展示了如何使用 .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();
    }
}

有时需要创建某个内部类的对象,则必须在 new 表达式中提供对其他外部类对象的引用, 这时候需要使用 .new 语法:

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

注意不能这样声明: dn.new DotNew.Inner

3.内部类与向上转型

public interface Destination {
    String readLabel();
}

public interface Contents {
    int value();
}

现在 Contents 和 Destination 表示客户端程序员可用的接口,接口的所有成员自动被设置为 public。

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:
        //- Parcel4.PContents pc = p.new PContents();
    }
}

观察以上代码,可以发现在 Parcel4 中:

  1. 内部类 PContents 是 private,所以除了 Parcel4,没有人能访问它。 普通(非内部)类的访问权限不能被设为 private 或者 protected;他们只能设置为 public 或 package 访问权限。

  2. PDestination 是 protected,所以只有 Parcel4 及其子类、还有与 Parcel4 同一个包中的类(因为 protected 也给予了包访问权) 能访问 PDestination,其他类都不能访问 PDestination,这意味着,如果客户端程序员想了解或访问这些成员,那是要受到限制的。 实际上,甚至不能向下转型成 private 内部类(或 protected 内部类,除非是继承自它的子类),因为不能访问其名字(可以在 TestParcel 类中看到)。

private 内部类给类的设计者提供了一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现的细节。 此外,从客户端程序员的角度来看,由于不能访问任何新增加的、原本不属于公共接口的方法,所以扩展接口是没有价值的。 这也给 Java 编译器提供了生成高效代码的机会。

4.内部类方法和作用域

可以在一个方法里面或者在任意的作用域内定义内部类。

  • 一个定义在方法中的类。
  • 一个定义在作用域内的类,此作用域在方法的内部。
  • 一个实现了接口的匿名类。
  • 一个匿名类,它扩展了没有默认构造器的类。
  • 一个匿名类,它执行字段初始化。
  • 一个匿名类,它通过实例初始化实现构造(匿名内部类不可能有构造器)。

这样使用的理由是:

  1. 如前所示,实现了某类型的接口,就可以创建并返回对其的引用。

  2. 要解决一个复杂的问题,想创建一个类来辅助解决方案,但是又不希望这个类是公共可用的。

5.嵌套类

如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为 static,这通常称为嵌套类。嵌套类意味着:

  1. 要创建嵌套类的对象,并不需要其外部类的对象。

  2. 不能从嵌套类的对象中访问非静态的外部类对象。

普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有 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();
        Destination d = destination("Tasmania");
    }
}

注意此时:在 main() 中,没有任何 Parcel11 的对象是必需的;而是使用选取 static 成员的普通语法来调用方法-这些方法返回对 Contents 和 Destination 的引用。

内部类的总结

一般说来,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外部类的对象。所以可以认为内部类提供了某种进入其外部类的窗口。

  • 内部类必须要回答的一个问题是:如果只是需要一个对接口的引用,为什么不通过外部类实现那个接口呢?

  • 答案是:“如果这能满足需求,那么就应该这样做。”

  • 那么内部类实现一个接口与外部类实现这个接口有什么区别呢?

  • 答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。

所以,使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。