11.内部类

内部类

11.2链接外部类

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

11.3使用.this.new

如果需要生成对外部类对象的引用,可以使用外部类的名字后面紧跟.this。这样产生的引用自动地具有正确的类型,这一点在编译期就知晓并受到检查,因此没有任何运行时开销。

// 如何使用.this
// Accessing 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();
    }
}

有时,可能想要告知某些其他对象,去创建某个内部类的对象。要实现此目的,必须在new表达式中提供对其他外部类对象的引用,这时需要使用.new语法。

// Creating an inner class directly using .new syntax
public class DotNew {
    public class Inner {}

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

要想直接创建内部类的对象,必须使用外部类的对象来创建。

11.4内部类与向上转型

在内部类中,可以使某个接口的实现能够完全不可见,并且不可用。所得到的的只是指向基类或接口的引用,所以能够很方便地隐藏实现细节。

// 示例的接口
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
        // Parcel4.PContents pc = p.new PContents();
    }
}

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

11.5内部类方法和作用域

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

// Nesting a class within a method
public class Parcel5 {
    public Destination destination(String s) {
        // 并不意味着destination方法执行完毕,PDestination就不可用了
        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");
    }
}

在任意的作用域内嵌入一个内部类:

// Nesting a class within a scope
public class Parcel6 {
    private void internalTracking(boolean b) {
        // 并不是说TrackingSlip类的创建是有条件的,它其实与别的类一起编译过了
        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匿名内部类
// Returning an instance of an anonymous inner class
public class Parcel7 {
    public Contents contents() {
        // 创建一个继承自Contents的匿名类的对象
        return new Contents() { // Insert class definition
            private int i = 11;

            @Override
            public int value() {
                return i;
            }
        };  // Semicolon required
    }

    public static void main(String[] args) {
        Parcel7 p = new Parcel7();
        Contents c = p.contents();
    }
}

如果基类需要一个有参数的构造器:

public class Parcel8 {
    public Wrapping wrapping(int x) {
        // Base constructor call
        return new Wrapping(x) {
            @Override
            public int value() {
                return super.value() * 47;
            }
        };
    }

    public static void main(String[] args) {
        Parcel8 p = new Parcel8();
        Wrapping w = p.wrapping(10);
    }
}

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 "effectiviely 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");
    }
}

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用是final的。
为了在匿名内部类中实现类似于构造器的行为,可以通过实例初始化:

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

    public abstract void f();
}

public class AnonymousConstructor {
    // 在此例中,不要求变量一定是final的,因为被传递给匿名类的基类构造器,
    // 它并不会在匿名类内部被直接使用
    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();
    }
}

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

11.7嵌套类

如果不需要内部类对象与其外部类对象之间有联系,那么可以将内部类声明为static,这通常称为嵌套类。普通的内部类对象隐式保存了一个引用,指向创建它的外部类对象。
嵌套类意味着:

  1. 要创建嵌套类的对象,并不需要其外部类的对象。
  2. 不能从嵌套类的对象中访问非静态的外部类对象。
11.8为什么需要内部类

一般来说,内部类继承自某个类或实现某个接口,内部类的代码操作创建它的外部类的对象。所以可以认为内部类提供了某种进入其外部类的窗口。
使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外部类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
内部类使得多重继承的解决方案变得完整。接口解决了部分问题,而内部类有效地实现了多重继承。也就是说,内部类允许继承多个非接口类型(类或抽象类)。

11.8.1闭包与回调

闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。通过这个定义,可以看出内部类是面向对象的闭包,因为它不仅包含外部类对象(创建内部类的作用域)的信息,还自动拥有一个指向此外部类对象的引用,在此作用域内,内部类有权操作所有的成员,包括private成员。

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();

    }
}

interface Incrementable {
    void increment();
}

// Very simple to just implement the interface
class Callee1 implements Incrementable {
    private int i = 0;

    @Override
    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();
    }
}

// If your class must implement increment() in some other way, you must use an inner class
class Callee2 extends MyIncrement {
    private int i = 0;

    @Override
    public void increment() {
        super.increment();
        i++;
        System.out.println(i);
    }

    private class Closure implements Incrementable {
        @Override
        public void increment() {
            // Specify outer-class method, otherwise you'll get an interface recursion
            Callee2.this.increment();
        }
    }

    Incrementable getCallbackReference() {
        return new Closure();
    }
}

class Caller {
    private Incrementable callbackReference;

    Caller(Incrementable cbh) {
        callbackReference = cbh;
    }

    void go() {
        callbackReference.increment();
    }
}
11.9继承内部类

因为内部类的构造器必须连接到指向其外部类的引用,所以在继承内部类的时候,事情会变得有点复杂。问题在于,那个指向外部类对象的秘密的引用必须被初始化,而在派生类中不再存在可连接的默认对象。要解决这个问题,必须使用特殊的语法。

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);
    }
}

class WithInner {
    class Inner {

    }
}
11.10内部类可以被覆盖么

覆盖内部类就好像它是外部类的一个方法,其实并不起什么作用。

// An inner class cannot be overriden like a method
class Egg {
    private Yolk y;

    protected class Yolk {
        public Yolk() {
            System.out.println("Egg.Yolk()");
        }
    }

    Egg() {
        System.out.println("New Egg()");
        y = new Yolk();
    }
}

public class BigEgg extends Egg {
    public class Yolk {
        public Yolk() {
            System.out.println("BigEgg.Yolk()");
        }
    }

    public static void main(String[] args) {
        new BigEgg();
    }
}
// 输出为:
// New Egg()
// Egg.Yolk()

当然,明确地继承某个内部类也是可以的:

public class BigEgg2 extends Egg2 {
    public class Yolk extends Egg2.Yolk {
        // 先调用父类Egg2.Yolk的构造器
        public Yolk() {
            System.out.println("BigEgg2.Yolk()");
        }

        @Override
        public void f() {
            System.out.println("BigEgg2.Yolk.f()");
        }
    }

    public BigEgg2() {
        insertYolk(new Yolk());
    }

    public static void main(String[] args) {
        Egg2 e2 = new BigEgg2();
        e2.g();
    }
}

class Egg2 {
    protected class Yolk {
        public Yolk() {
            System.out.println("Egg2.Yolk()");
        }

        public void f() {
            System.out.println("Egg2.Yolk.f()");
        }
    }

    private Yolk y = new Yolk();

    Egg2() {
        System.out.println("new Egg2()");
    }

    public void insertYolk(Yolk yy) {
        y = yy;
    }

    public void g() {
        y.f();
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值