《Java编程思想》笔记10.内部类


点击进入个人博客

能够把一个类的定义放在另外一个类的定义内部,这就是内部类。
Java最晦涩的部分之一。
内部类看起来就像是一种代码隐藏机制,将类只与其余类的内部。但远不止如此,内部类了解外部类,并能与之通讯。shell

10.1 建立内部类

建立内部类的方式就如同你想的同样——把类的定义置于外围类的里边设计模式

10.2 连接到外部类

  • 当生成一个内部类的对象时,此对象与制造它的外部对象之间就有了一种联系,因此它能访问其外围对象的全部成员。
  • 内部类还拥有其外围类的全部元素的访问权。当某个外部类当对象建立了一个内部类对象时,此内部类对象一定会秘密地捕获一个指向那个外部类对象当引用。当你访问外部类成员时,就是用那个引用来选择外部类当成员。

10.3 使用.this与.new

  • 若是你须要生成对外类对象的引用,可使用外部类.this。这样产生的引用自动地具备正确类型,这一点在编译期就被知晓并收到检查,所以没有任何运行开销。
  • 经过外部类的对象建立内部类(非static内部类)对象,能够经过外部类.new建立内部类
public class Outer {
    void func() {
        System.out.println("Test");
    }

    class Inner {
        void func() {
            System.out.println("Inner");
            // .this语法
            Outer.this.func();
        }
    }

    public static void main(String[] args) {
        // .new语法
        new Outer().new Inner().func();
    }
}

10.4 内部类与向上转型

内部类的优势:能够更好的隐藏细节
特色:安全

  1. 外部类能够访问内部类的全部元素,不管什么修饰符。
  2. 普通内部类内不能有static域和方法。
  3. 一个内部类能够被嵌套多层,并且能够访问全部外围类的成员。

10.5 局部内部类

能够在一个方法或任意做用域内定义内部类,成为局部内部类。这么作的理由:闭包

  1. 你实现了某类型的接口,因而能够建立并返回对其的引用
  2. 你要解决一个复杂的问题,想建立一个类来复制你的解决方案,但又不但愿这个类是公共可用的。
public class Outer {

    public void func() {
        // 方法内部的内部类
        class InnerMethod {
            void func() {
                System.out.println("class in method");
            }
        }
        new InnerMethod().func();
    }

    public void f() {
        if(true) {
            // 做用域内部的内部类
            class InnerScope {
                void func() {
                    System.out.println("class in scope");
                }
            }
            new InnerScope().func();
        }
    }

    public static void main(String[] args) {
        new Outer().func();
        new Outer().f();
    }
}
特色:
  1. 局部内部类相似方法的局部变量,因此在类外或者类的其余方法中不能访问这个内部类。但这并不表明局部内部类的实例和定义了它的方法中的局部变量具备相同的生命周期。
  2. 能够在同一个子目录(包)下起一个跟局部内部类相同的类,不会有冲突。
  3. InnerScope类被嵌套到if语句中,这并非说该类到建立是有条件的,他跟其余的类同样被编译过了。
  4. 由于不存在外部可见性,局部内部类不能用权限修饰符。
  5. 不能在局部内部类中使用可变的局部变量,可使用final的局部变量。
  6. 能够访问外围类的成员变量。若是是static方法,则只能访问static修饰的成员变量。
  7. 可使用finalabstract修饰。

10.6 匿名内部类

简介:
  • inner()方法将返回值的生成与表示这个返回值的类定义结合在一块儿,并且这个类没有名字。
  • 建立一个继承某个类(或者实现某个接口)的匿名类对象。
public class Outer {

    private final String outerStr = "Outer";

    class Inner {
        public Inner(String str) {
            System.out.println("Inner Constructor " + str);
        }
        public void func() {
            System.out.println("Inner");
        }
    }

    public Inner inner() {
        return new Inner("Dota") {
            {
                // 跟构造方法同样初始化
                str3 = "LOL";
            }
            private String str1 = Outer.this.outerStr;
            private String str2 = outerStr;
            private String str3;
            @Override
            public void func() {
                System.out.println(str1);
                System.out.println(str2);
                System.out.println(str3);
            }
        };
    }

    public static void main(String[] args) {
        new Outer().inner().func();
    }
}
细节:
  1. 返回的类型被自动向上转型成Inner的引用。
  2. 若是构造方法带参数,也能够在new Inner()中传递参数给基类的构造器。
  3. 在匿名内部类定义字段时,能够初始化。
  4. 在匿名内部类使用外部类的对象时,只能使用final的。
  5. str1str2是同样的
  6. 匿名内部类没有名字,也就没有构造器,可是能够经过实例初始化模拟构造器。可是你不能重载实例初始化方法,因此只能有一个这样的构造器。
  7. 变量str不要求是final的,由于str是传递给基类的构造器的,匿名内部类没法使用。
  8. 匿名内部类能够继承类或者实现接口,但不能二者兼得。

10.6.1 再访工厂方法

代码更加简洁框架

10.7 嵌套类(静态内部类)

嵌套类:

若是不须要内部类对象与其外围对象之间有联系,那么能够将内部类声明为staticide

特色:
  1. 普通内部类对象隐式的保存了外部类对象,但嵌套类并不是如此。
  2. 要建立嵌套类的对象,并不须要外部类的对象
  3. 不能从嵌套类对象中访问外部类的非静态对象。
  4. 普通内部类不能有static域和static方法,但嵌套类能够有。

10.7.1 接口内部的类

  • 嵌套类能够做为接口的一部分,还能够实现其外部接口。
  • 若是你想建立某些公共代码,使得它们能够被某个接口的全部不一样实现所共用,那么使用接口内部的嵌套类会很方便。
  • 可使用嵌套类的main方法来实现调试。

10.7.2 多层嵌套

  • 一个内部类能够嵌套多层
  • 一个嵌套类也能够被嵌套多层。

10.8 为何须要内部类

  1. 外部类能够有多个内部类,每一个内部类都能独立的继承自一个(接口的)实现,因此不管外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
  2. 接口解决类部分“多重继承”,内部类补充的实现了“多重继承。
  3. 内部类能够有多个实例,每一个实例都有本身的状态信息,而且与外围类对象的信息相互独立。
  4. 再单个外围类中,可让多个内部类以不一样的方式实现同一个接口。
  5. 建立内部类对象的时候并不必定依赖外部类对象的建立。
  6. 内部类并无使人迷惑的“is-a”关系,他就是一个独立的实体。

10.8.1 闭包与回调

闭包
  1. 闭包(closure)是一个可调用的对象,它记录了一些信息,这些信息来自于建立它的做用域。
  2. 经过上述定义,能够看出内部类就是面向对象的闭包,由于它不只包含外围类对象(建立内部类的做用域)的信息,还自动拥有一个指向此外围类对象的引用,在此做用域内,内部类有权操做全部成员。
  3. 经过内部类实现闭包的功能是优良的解决方案,它比指针更灵活、更安全。
回调

回调函数的定义:在计算机程序设计中,回调函数是指经过函数参数传递到其它代码的,某一块可执行代码的引用。这一设计容许了底层代码调用在高层定义的子程序。
非回调函数的场景:一个程序B有一个方法b(),要调用程序A中的另外一个方法a()。这个很简单,只须要在程序B的方法b()new A().a()就能够了。
回调函数:跟上述同样,可是程序A中的方法a()在完成任务后,还会调用一个预约义好的回调函数B在方法b()中,能够按照预约义好的回调函数接口实现相关逻辑,而后把这段逻辑传递给A,这样在B.b()调用A.a()的时候,就会执行这段逻辑。函数

// A定义好的回调接口
interface Callback {
    void callback();
}

// A定义
public class A {
    Callback callback;

    public A(Callback callback) {
        this.callback = callback;
    }

    public void a() {
        System.out.println("a");
        callback.callback();
    }
}

class B {

    public static void main(String[] args) {
        A a = new A(new Callback() {
            @Override
            public void callback() {
                System.out.println("callback");
            }
        });
        a.a();
    }
}
// Output:
a
callback

10.8.2 内部类与控制框架

应用程序框架
  1. 应用程序框架就是被设计用来解决某类特定问题的一个或者一组类。
  2. 要运用某个应用程序框架,一般是继承一个或多个类,并覆盖某些方法。在覆盖后的方法中,编写代码定制应用程序框架提供的通用解决方案(这是模板方法的一个例子)。
控制框架

控制框架是一类特殊的应用程序框架,他用来解决响应事件的需求。主要用来响应事件的系统被称为事件驱动系统。this

public class Test {
    private boolean light;
    private boolean water;

    class LightEvent extends SwitchEvent {
        @Override
        public void on() {
            light = true;
        }

        @Override
        public void off() {
            light = false;
        }
    }

    class WaterEvent extends SwitchEvent {
        @Override
        public void on() {
            water = true;
        }

        @Override
        public void off() {
            water = false;
        }
    }
}

abstract class SwitchEvent {
    public abstract void on();
    public abstract void off();
}

上述代码描述了一个开关事件的抽象类,和两个继承该抽象类的内部类。这些内部类可以自由地访问Test类中的字段,无需任何条件。设计

命令设计模式

记得看!!!指针

10.9 内部类的继承

public class Test extends Outer.Inner {
    // 若是没有下面的构造方法会编译失败
    public Test(Outer outer) {
        outer.super();
    }
}

class Outer {
    class Inner {}
}
  • 能够看到Test只继承了内部类Inner,而不是外部类。
  • 当要生成一个构造器时,必需要增长这样一段代码outer.super();
  • 解释:内部类的构造器必须链接到指向外部类对象的引用,而在内部类的子类中再也不存在可链接的默认对象。因此须要在子类的构造器中包含指向外部类的引用,必须是带参数的,并且参数类型是外部类。说白了就是,内部类的对象依赖外部类的对象,内部类的子类的对象,也仍旧是依赖外部类的对象的。

10.10 内部类能够被覆盖吗

public class Test extends Outer {
    class Inner {}
}

class Outer {
    class Inner {}
}

上述代码中:Test继承了Outer并“覆盖”了Inner,但这没有用;这两个Inner是彻底绝不相干但两个类,各自活在各自的命名空间里。

public class Test extends Outer {
    class Inner extends Outer.Inner {
        @Override
        void func() {
            System.out.println("Test.Inner.func()");
        }
    }

    public Test() {
        setInner(new Inner());
    }

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

class Outer {
    private Inner inner;
    class Inner {
        void func() {
            System.out.println("Outer.Inner.func()");
        }
    }

    public Inner getInner() {
        return inner;
    }

    public void setInner(Inner inner) {
        this.inner = inner;
    }
}

上述代码中:Test继承了OuterTest.Inner继承了Outer.Inner。此时若是覆盖Inner中的方法,当构造器调用setInner(new Inner());的时候,是把Test.Inner向上转型为Outer中的引用inner

10.11 局部内部类

(见10.5)
前面提到过,能够在代码块里建立内部类,典型的方式是在方法体内。
局部内部类不能有访问说明符,由于他不是外部类的一部分。
局部内部类能够访问当前代码块内的常量,以及此外围类的成员。

局部内部类OR匿名内部类
  1. 局部内部类能够有构造器以及重载构造器,而匿名内部类只能用于实例初始化。
  2. 局部内部类能够建立多个对象,而匿名内部类最多有一个

10.12 内部类标识符

每一个类都会产生一个.class文件,其中包含了如何建立该类的对象的所有信息(此信息产生一个“meta-class”,叫作Class;对象),内部类也是如此。

类文件的命名规则
  1. 外部类的名字:外部类名.class
  2. 普通内部类:外部类名$内部类名.class
  3. 匿名内部类:外部类名$编译器分配的数字.class
  4. 多层嵌套:按从外到内用$分割.class
小问题

对于Unix shell而言,$是一个元字符,因此在列出.class文件的时候,有时会有问题。

10.13 总结

内部类涉及内容相对复杂,多花点时间吧~

相关文章
相关标签/搜索