Java编程思想之内部类

可以将一个类的定义放在另一个类的定义内部,这就是内部类
把一些逻辑相关的类组织在一起,并控制位于内部的类的可视性。


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

  • 内部类自动拥有对其外围类所有成员的访问权。这是由于当某个外部类的对象创建了一个内部类对象时,此内部类对象必定会秘密地捕获一个指向那个外围类对象的引用。然后,在你访问此外围类的成员时,就是用那个引用来选择外围类的成员。幸运的是,编译器会帮你处理所有细节。

  • 内部类的对象只能在与其外围类对象相关联的情况下才能被创建(在内部类是非static类时),构建内部类对象时,需要一个指向其外围类对象的引用,如果编译器访问不到这个引用就会报错。不过,绝大多数时候,这都无须程序员操心。

  • 如果你创建的是嵌套类(静态内部类),那么它就不需要对外部类对象的引用。

1 使用.this与.new

如果你需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟圆点和this。
如果想直接创建内部类对象,必须使用外部类的对象来创建该内部类对象。这也解决了内部类名字作用域的问题,因此你不必声明(实际上你也不能声明)outter.new Outter.Inner()。

public class Outter{
    void f(){
        System.out.println("Outter.f() ");
    }
    public class Inner{
        void f(){
            System.out.println("Inner.f() ");
        }
        public Outter outer(){
            this.f();   //Inner's "this"
            return Outter.this;
        }
    }
    public static class StaticInner{
        public StaticInner(){
            System.out.println("StaticInner()");
        };
    }
    public Inner inner(){
            return new Inner();
    }
    public static void main(String[] args){
        //外部类将有一个方法返回一个指向内部类的引用
        Outter outter = new Outter();
        Outter.Inner inner = outter.inner();
        inner.outer().f();

        //使用.new语法,需要提供对其外部类对象的引用
        Outter.Inner in = outter.new Inner();
        in.outer().f();

        //访问静态内部类
        Outter.StaticInner si = new Outter.StaticInner();
    }
}
/*
运行结果:
Inner.f()
Outter.f()
Inner.f()
Outter.f()
StaticInner()

编译后产生的文件结构:
Outter$Inner.class
Outter$StaticInner.class
Outter.class
*/

2 匿名内部类

如果定义一个匿名内部类,并且希望它使用在一个在其外部定义的对象,那么编译器会要求其参数引用是final的,如果忘记了,会得到一个编译时错误消息。
如果想做一些类似构造器的行为,因为匿名内部类没有名字,所以其不可能有命名构造器,但通过实例初始化,就能够达到为匿名内部类创建一个构造器的效果。

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

public class AnonymousClass{

    //不要求变量i一定是final,因为它被传递给匿名类的基类的构造器,它并不会在匿名类内部使用
    public static Base getBase(int i,final int c){
        return new Base(i){
            private int co;
            {
                co = c;
                if(co>0)
                    System.out.println("co = "+co);
                System.out.println("Inside instance initialization");
            }
            public void f(){
                System.out.println("In anonymous f()");
            }
        };
    }
    public static void main(String[] args){
        Base base = getBase(47,5);
        base.f();
    }
}

/*
运行结果为:
Base constructor,i = 47
co = 5
Inside instance initialization
In anonymous f()

先执行基类构造器,再实例初始化。
*/

匿名内部类与正规的继承相比有些受限,因为匿名内部类既可以扩展类,也可以实现接口,但是不能两者兼备。而且如果是实现接口,也只能实现一个接口。

3 嵌套类

  • 如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。这通常称为嵌套类。
  • 普通的内部类保存了一个引用,指向创建它的外围类对象。然而,当内部类是static的时,就不是这样了。
  • 嵌套类意味着:
    • 1)要创建嵌套类的对象,并不需要其外围类的对象。
    • 2)不能从嵌套类的对象中访问非静态的外围类对象。
      普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static数据和static字段,也不能包含嵌套类。但是嵌套类可以包含所有这些东西。

3.1 接口内部的类

  • 放到接口中的任何类都自动是public 和 static 。嵌套类可以作为接口的一部分,甚至可以在其内部类中实现其外围接口。
  • 如果你想要创建某些公共代码,使得 它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便。

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

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

4 为什么需要内部类

内部类的特性:
- 解决“多重继承”的问题
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
- 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
- 创建内部类对象的时刻并不依赖于外围类对象的创建。
- 内部类并没有令人迷惑的“is-a”关系;它就是一个独立的实体。

4.1 闭包与回调

闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。可以看出内部类是面向对象的闭包,因为它不仅包含外围类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外围类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。
通过内部类提供闭包的功能是优良的解决方案,它比指针更灵活、更安全。可以在运行时动态地决定需要调用什么方法。

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();
    }
}
//如果你的类需要用某种不同的方法实现increment()方法,你可以使用一个内部类
/*因为Callee2继承自MyIncrement,后者已经有了一个不同的increment()方法,并且与
Incrementable接口期望的increment()方法完全不相关。所以如果Callee2继承了MyIncrement
就不能为了Incrementable的用途而覆盖increment()方法,于是只能使用内部类独立地实现
Incrementable。
*/
class Callee2 extends MyIncrement{
    private int i = 0;
    public void increment(){
        super.increment();
        i++;
        System.out.println(i);
    }
    /*
    内部类实现了Incrementable,以提供一个返回Callee2的”钩子“(hook)——而且是
    一个安全的钩子。无论谁获得此Incrementable的引用,都只能调用increment(),
    除此之外没有其他功能。
    */
    private class Closure implements Incrementable{
        public void increment(){
            //指定外部类方法,否则可能得到一个无限循环。
            Callee2.this.increment();
        }
    }
    Incrementable getCallbackReference(){
        return new Closure();
    }
}

class Caller{
    private Incrementable callbackReference;
    public Caller(Incrementable cbr){
        callbackReference = cbr;
    }

    void go(){
        callbackReference.increment();
    }
}

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();
    }
}
/*
执行结果为:
Other operation
1   //Callee2的i值
1   //Callee1的i值
2
Other operation
2   //Callee2的i值
Other operation
3   //Callee2的i值
*/

5 内部类的继承

指向外围类对象的”秘密的“引用必须被初始化,而在导出类中不在存在可连接的默认对象。要解决这个问题,必须使用特殊的语法来明确说清它们之间的关联:

class WithInner{
    public WithInner(){
        System.out.println("WithInner()");
    }
    class Inner{

    }
}

public class InheritInner extends WithInner.Inner{
    // InheritInner(){}  需要包含WithInner.Inner的封闭实例
    InheritInner(WithInner wi){
        //必须在构造器内使用:enclosingClassReference.super() 这样才提供了必要的引用
        wi.super(); 
    }
    public static void main(String[] args){
        WithInner wi = new WithInner();
        InheritInner ih = new InheritInner(wi);
    }
}
/*
    运行结果为:
    WithInner()
*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值