Java编程思想 -- 内部类

本博客是针对Java编程思想内部类一章所做记录,内部类在Java随处可见,比如集合框架中,Android源码以及开源框架中等等。理清内部类的使用是很有必有,虽然这一章内容之前已经看过几遍,但是还是不如梳理思路来的好。

连接到外部类

当生成一个内部类的对象时,此对象与制造它的外围对象之间就有了一种联系(秘密连接到创建它的外部类),所以它能访问其外围对象的所有成员,而不需要任何条件

使用.this与.new

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

public class DotThis{
    void f(){ }
    public class Inner{
        public DotThis outer(){
            // 外部类的名字后面紧跟圆点和this
            return DotThis.this;
        }
    }
}

创建内部类对象时,必须在new表达式中提供对其他外部类对象的引用。

public class DotNew{
    public class Inner{ }

    public static void main(String[] args){
        DotNew dn = new DotNew();
        // .new、 DotNew.Inner:代表内部类
        DotNew.Inner dni = dn.new Inner();
    }
}

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

内部类与向上转型

当内部类向上转型为其基类或者接口时,内部类就有了用武之地,这是因为内部类(某个接口的实现)能够完全不可见,并且不可用,所得到的只是指向基类或者接口的引用,所以能很方便地隐藏实现细节

public interface Destination{
    String readLabel();
}

public interface Contents{
    int value();
}
class Parcel{
    private class PContents implements Contents{
        pirvate 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 Destination(s);
    }

    public Contents contents(){
        return new PContents();
    }
}
public class TestParcel{
    public static void main(String[] args){
        Parcel p = new Parcel();
        Contents c = p.contents();
        Destination d = p.destination("kk");

        // 失败
        // Parcel.PContents pc = p.new PContents();
    }
}

private内部类给设计者提供一种途径,通过这种方式可以完全阻止任何依赖于类型的编码,并且完全隐藏了实现细节

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

也称局部内部类

这么做有两个理由:
- 如前所示,你实现了某类型的接口,于是可以创建并返回对其的引用。
- 你要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但又不希望这个类公共可用

匿名内部类

如果定义一个匿名内部类,并且希望它使用一个在其外部定义的对象,那么编译器会要求其参数引用时final的

在匿名内部类中不可能有命名构造器(因为它根本没有名字),但通过实例初始化(也就是{}块),就能够达到匿名内部类创建一个构造器的效果

abstract class Base{
    public Base(int i){
        print("Base Constructor");
    }
    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(41);
        base.f();
    }
}

/*Output:
Base Constructor
Inside instance initializer
In anonymous f()
*/

匿名内部类与工厂模式

直接引用Java编程思想 – 接口 中接口与工厂模式代码,并做修改跟对比

//具体编程技能模块
interface ICode {
    void coding();
}

//创建技能工厂模块
interface IFactory {
    ICode getCodingSkill();
}

class CodeImplAndroid implements ICode {
    @Override
    public void coding() {
        System.out.println("Coding Android!");
    }

    public static IFactory getFactory(){

        return new IFractory(){
            @Override
            public ICode getCodingSkill() {
                return new CodeImplAndroid();
            }
        }
    }
}

class CodeImplPHP implements ICode {
    @Override
    public void coding() {
        System.out.println("Coding PHP!");
    }

    public static IFactory getFactory(){

        return new IFractory(){
            @Override
            public ICode getCodingSkill() {
                return new CodeImplPHP();
            }
        }
    }
}

public class Main {
    public static void coding(IFactory ifactory){
        ICode code = factory.getCodingSkill();
        code.coding();
    }
}

嵌套类

普通内部类对象隐式地保存一个引用,指向创建它的外围类对象

而嵌套类(内部类是static时),意味着:

  • 要创建嵌套类的对象,并不需要其外围类的对象
  • 不能从嵌套类的对象中访问非静态的外围类对象

嵌套类与普通的内部类还有一个区别,普通内部类的字段与方法,只能放在类的外部层次上,所以普通的内部类不能有static的数据和static字段,也不能包含嵌套类。但是,嵌套类可以包含这些东西

接口内部的类

正常情况下,不能在接口内部放置任何代码,但嵌套类可以作为接口的一部分
放到接口中的任何类都自动地是public和static的。甚至可以在内部类中实现其外围接口

public interface ClassInInterface{
    void howdy();

    class Test implements ClassInInterface{
        public void howdy(){
            System.out.println("Howdy");
        }

        public static void main(String[] args){
            new Test().howdy();
        }
    }
}

/* Output:
Howdy
*/

如果你想要创建某些公共代码,使得它们可以被某个接口的所有不同实现所共用,那么使用接口内部的嵌套类会显得很方便

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

一个内部类不管被嵌套多少层,都能访问它所嵌入的外围类的所有成员:

class MNA{
    private void f() { } 
    class A{
        private void g() { }

        public class B{
            void h(){
                g();
                f();
            }
        }
    }
}

public class Test{
    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();
    }
}

为什么需要内部类

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

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

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

答案是:后者不是总能享用到接口带来的方便,有时需要用到接口的实现。所以使用内部类最吸引人的原因是:每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。

看例子:

比如现在必须在一个类中以某种方式实现两个接口。有两种选择,使用单一类,或者内部类:

interface A { }
interface B { }

class X implements A, B { }
ckass Y implements A{
    // 使用内部类
    B makeB(){
        return new B() { }
    }
}

public class MultiInterfaces {
    static void takesA(A a) { }
    static void takesB(B b) { }

    public static void main(String[] args){
        X x = new X();
        Y y = new Y();
        takesA(x);
        takesA(y);
        takesB(x);
        takesB(y.makeB());
    }
}

上面例子没有什么区别,都能正常运行。
但是如果拥有的是抽象的类或者具体的类,而不是接口,那就只能使用内部类才能实现多重继承。

class D { }
abstract class E { }

class Z extends D{
    E makeE(){ return new E() { }; }
}

public class MultiImplementation{
    static void takesD(D d) { }
    static void takesE(E e) { }

    public static void main(String[] args){
        Z z = new Z();
        takesD(z);
        takesE(z.makeE());
    }
}

闭包与回调

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

内部类提供闭包功能实现回调:

Interface Incrementable{
    void increment();
}

// 
class Callee1 implements Incrementable {
    private int i = 0;
    public void increment(){
        i++;
        print(i);
    }
}

class MyIncrement{
    public void increment() { print("Other operation"); }
    static void f(MyIncrement mi) { mi.increment(); }
}

// MyIncrement类与Incrementable接口的increment方法冲突
class Callee2 extends MyIncrement{
    private int i = 0;

    public void increment(){
        super.increment();
        i++;
        print(i);
    }

    // 
    private class Closure implements Incrementable{
        public void increment(){
            // 调用外围类的increment()方法  
            Callee2.this.increment();
        }
    }

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

class Caller{
    private Incrementable callbackReference;
    Caller(Incrementable cbh) { callbackReference = cbh; }
    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();
    }
}

这个例子进一步展示了外围类实现一个接口与内部类实现此接口之间的区别。

就代码而言,Callee1是简单的解决方式

Callee2继承自MyIncrement,后者已经有了一个不同的increment()方法,并且与Incrementable接口期望的increment()方法完全不相关。所以如果Callee2继承了MyIncrementable就不能为了Incrementable的用途而覆盖increment()方法,于是只能使用内部类独立地实现Incrementable。还要注意,当创建了一个内部类时,并没有在外围类的接口中添加东西,也没有修改外围类的接口。

内部类的继承

因为内部类的构造器必须链接到指向外围类对象的引用,所以在继承内部类的时候,事情会变得有点复杂。

问题在于,那个指向外围类对象的“秘密的”引用必须被初始化,而在导出类中不再存在可连接的默认对象

class WithInner{
    class Inner{ }
}

public class InheritInner extends WithInner.Inner{
    // InheritInner() { } // 不能编译
    InheritInner(WithInner wi){
        wi.super();
    }

    public static void main(String[] args){
        WithInner wi = new WithInner();
        InheritInner ii = new InheritInner(wi);
    }
}

内部类可以被覆盖吗

如果创建一个内部类,然后继承其外围类并重新定义此内部类,会发生什么??也就是说,内部类可以被覆盖吗??

class Egg{
    private Yolk y;
    protected class Yolk{
        public Yolk() { print("Egg.Yolk()"); }
    }

    public Egg(){
        print("New Egg");
        y = new Yolk();
    }
}

public calss BigEgg extends Egg{
    publci class Yolk{
        public Yolk() { print("BigEgg.Yolk()"); }
    }

    public staic void main(String[] args){
        new BigEgg();
    }
}

/*Output:
New Egg()
Egg.Yolk()
*/

这个例子说明,当继承了某个外围类的时候,内部类并没有发生什么变化。这两个内部类是两个完全独立的两个实体,各自在自己的命名空间内。

局部内部类

前面提到过,可以在代码块中创建内部类,典型的方式是在一个方法体里创建。局部内部类不能有访问说明符,因为它不是外围类的一部分;但是它可以访问当前代码块的常量,以及此外围类的所有成员。

局部内部类跟匿名内部类的区别:
局部内部类可以有构造器,而匿名内部类只能用于实例初始化。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值