第十章 内部类

  • 将一个类的定义放在另一个类的内部,这就是内部类
  • 内部类和组合是完全不同的概念,内部类了解外部类

10.1 创建内部类

创建就好,好像没啥可说的,直接来上练习:

练习1:(1)编写一个名为Outer的类,它包含一个名为Inner的类。在Outer中添加一个方法,它返回一个Inner类型的对象。在main()中,创建并初始化一个指向某个对象的引用。

public class Outer {public Inner getInnerInstance() {return new Inner();}public static void main(String[] args) {Outer outer = new Outer();Inner inner = outer.getInnerInstance();}public class Inner {{System.out.println("创建Inner");}}}输出:
创建Inner

10.2 链接到外部类

所有内部类自动拥有对其外围类所有成员的访问权。内部类对象会秘密捕获一个指向那个外围类对象的引用。这些东西编译器都帮我们处理好了。直接来上练习:

练习2、创建一个类,持有一个String,将这个新类的对象添加到一个Sequence对象中,然后显示它们

练习3、修改练习1,使得Outer类包含一个private String域,Inner包含一个显示这个域的toString()方法,创建一个Inner类型的对象并显示它

这两个练习比较简单,略

10.3 使用.this与.new

如果需要生成对外部类的引用,直接外部类的名字后面紧跟圆点和this

要想直接创建内部类对象,不能按照我们想象的方式,必须使用外部类的对象来创建该内部类对象,在拥有外部类对象之前是不可能创建内部类对象的。这是因为内部类对象会暗暗地连接到创建它的外部类对象上

练习4、生成对外部类Sequence的引用

练习5、创建一个包含内部类的类,在另一个独立的类中,创建此内部类的实例

还是比较简单,贯彻落实使用.this和.new就OK, 略

10.4 内部类与向上转型

内部类向上转型为其基类,能够很方便地隐藏实现细节,直接上练习:

练习6:(2)在第一个包中创建一个至少有一个方法的接口。然后在第二个包内创建一个类,在其中添加一个protected的内部类以实现那个接口。在第三个包中,继承这个类,并在一个方法中返回该protected内部类的对象,在返回的时候向上转型为第一个包中的接口的类型。

// 第一个包
package one1;public interface SimpleInterface {void eat();
}// 第二个包
package two;public class TestParent {protected class Inner implements SimpleInterface {public Inner() {System.out.println("创建了内部类");}@Overridepublic void eat() {System.out.println("吃东西");}}
}// 第三个包
package one3;public class Test extends TestParent{public SimpleInterface getInner() {return new Inner();}public static void main(String[] args) {new Test().getInner().eat();}}输出:
创建了内部类
吃东西

练习7:(2)创建一个含有private域和private方法的类。创建一个内部类,它有一个方法可以用来修改外围类的域,并调用外围类的方法。在外围类的另一方法中,创建此内部类的对象,并且调用它的方法,然后说明对外围类对象的影响。

// 这个练习显示了内部类具有对外部类的透明访问,甚至是私有域和方法
public class Test {private int count = 22;;private void onUseOuter() {System.out.println("外部类被调用了");}public Inner getInnerInstance() {return new Inner();}private class Inner {public void changeValue(int i) {count = i;System.out.println(i);onUseOuter();}}public static void main(String[] args) {Test test = new Test();Inner inner = test.getInnerInstance();inner.changeValue(33);}}输出:
33
外部类被调用了

练习8:(2)确定外部类是否可以访问其内部类的private元素

// 此练习显示外部类也可以访问内部类的private元素
public class Test2 {private class Inner {private int count = 22;}private void changeInnerValue(int i) {Inner inner = new Inner();inner.count = i;System.out.println("改变后的值为" + inner.count);}public static void main(String[] args) {Test2 test2 = new Test2();test2.changeInnerValue(55);}}输出:
改变后的值为55

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

以前没接触过,也没这么写过。但是,在方法和作用域中使用内部类,是一种更加难以理解的技术

这么做有两个理由:

  1. 创建某类型的接口,创建并返回对其的引用
  2. 要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但又不希望这个类是公共可用的

直接上练习吧:

练习9:(1)创建一个至少有一个方法的接口。在某个方法内定义一个内部类以实现此接口,这个方法返回对此接口的引用。

public class Test3 {private SimpleInterface get() {class simpleImpl implements SimpleInterface {public simpleImpl() {System.out.println("创建了一个方法内定义的内部类");}@Overridepublic void f() {System.out.println("接口的f()方法");}}return new simpleImpl();}public static void main(String[] args) {SimpleInterface simpleInterface = new Test3().get();}public interface SimpleInterface {void f();}
}

练习10:(1)重复前一个练习,但将内部类定义在某个方法的一个作用域内。

// 和练习9的区别就是这个方法,加个if
private SimpleInterface get() {if(true) {class simpleImpl implements SimpleInterface {public simpleImpl() {System.out.println("创建了一个方法内定义的内部类");}@Overridepublic void f() {System.out.println("接口的f()方法");}}return new simpleImpl();}return null;}

练习11:(2)创建一个private内部类,让它实现一个public接口。写一个方法,它返回一个指向此private内部类的实例的引用,并将此引用向上转型为该接口类型。通过尝试向下转型,说明此内部类被完全隐藏了。

// 此练习显示,因为内部类是私有的,只能向上转型,返回可见的基类
public class Test4 {private class Inner implements SimpleInterface {@Overridepublic void f() {}}public SimpleInterface get() {return new Inner();}public Inner get2() {return new Inner();}public interface SimpleInterface {void f();}
}public class Test5 {public static void main(String[] args) {Test4 test4 = new Test4();SimpleInterface simpleInterface = test4.get();simpleInterface = test4.get2();// 下面的直接报错,编译不通过// Inner i1 = test4.get2();// Inner i2 = (Inner)simpleInterface;}
}// 注意此例子必须再其他类中的main()方法中
// 如果仍然在Test4中的main()方法就会编译通过

10.6 匿名内部类

// 某个方法返回一个匿名内部类对象
public Contents getContents() {return new Contents() {private int i = 11;public int value() {return i;}};
}

重点,也是我们在开发中常遇到的:

  • 如果一个匿名内部类想使用一个外部定义的对象,那么编译器会要求这个参数引用必须是final的
  • 匿名类中不可能有命名构造器

直接上练习:

练习12:(1)重复练习7

练习13:(2)重复练习9

练习14:

这三个略,练习9用匿名内部类写就是我们平时写的回调,更简单了

练习15:(2)创建一个类,它有非默认的构造器(即需要参数的构造器),并且没有默认构造器(没有无参数的构造器)。创建第二个类,它包含一个方法,能够返回对第一个类的对象的引用。通过写一个继承自第一个类的匿名内部类,来创建一个返回对象。

public class NoDefault {private int i;public NoDefault(int i) {this.i = i;}public void f() {System.out.println("NoDefault");}}public class Pratice15 {public NoDefault get(int i) {return new NoDefault(i) {};}public NoDefault get2(int i) {return new NoDefault(i) {public void f() {System.out.println("NoDefault匿名内部类");}};}public static void main(String[] args) {Pratice15 pratice15 = new Pratice15();NoDefault noDefault = pratice15.get(2);noDefault.f();noDefault = pratice15.get2(3);noDefault.f();}}输出:
NoDefault
NoDefault匿名内部类

10.6.1 再访工厂方法

使用工厂方法,声明一个static的工厂匿名内部类,返回当前类的对象。感觉十分的美妙。

练习16:(1)修改第9章中练习18的解决方案,让它使用匿名内部类

        interface Cycle { int wheels(); }interface CycleFactory { Cycle getCycle(); }class Unicycle implements Cycle {public int wheels() { return 1; } public static CycleFactory factory = new CycleFactory() { public Unicycle getCycle() { return new Unicycle(); } }; } class Bicycle implements Cycle { public int wheels() { return 2; } public static CycleFactory factory = new CycleFactory() { public Bicycle getCycle() { return new Bicycle(); } }; } class Tricycle implements Cycle { public int wheels() { return 3; } public static CycleFactory factory = new CycleFactory() {public Tricycle getCycle() { return new Tricycle(); } }; }public class Pratice16 {public static void ride(CycleFactory fact) {Cycle c = fact.getCycle();System.out.println(c.wheels());}public static void main(String[] args) {ride(Unicycle.factory);ride(Bicycle.factory);ride(Tricycle.factory);}} 

练习17:略

10.7 嵌套类

如果不需要内部类对象与其外围类对象之间有联系,那么可以将内部类声明为static。

嵌套类意味着:

  1. 要创建嵌套类的对象,并不需要其外围类的对象。
  2. 不能从嵌套类的对象中访问非静态的外围类对象。
  3. 嵌套类的内部可以包含static数据,包含嵌套类

练习18:略

练习19:略

10.7.1 接口内部的类

嵌套类可以作为接口的一部分,甚至可以在内部类中实现其外围接口。

比如也可以在嵌套类中写一个main函数

练习20:略

练习21:略

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

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

10.8 为什么需要内部类

内部类最吸引人的原因是:

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

内部类使得多重继承的解决方案变得完整。内部类有效地实现了“多重继承”。

使用内部类还可以获得其他一些特性:

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

练习22:(2)实现Sequence.java中的reverseSelector()方法

// 改编一个吧,最简单的,猫狗啥的,哈哈哈
// 因为每个内部类都可以独立的实现一个接口,
// 内部类最重要突出的功能就是实现同一个接口,
// 很容易就能拥有另一个方法,暂时可以这么粗鄙的理解不知道合适不,哈哈哈
public class Pratice17 {private class Dog implements Animal {@Overridepublic void eat() {System.out.println("吃骨头");}}private class Cat implements Animal {@Overridepublic void eat() {System.out.println("吃鱼");}}public Animal getDog() {return new Dog();}public Animal getCat() {return new Cat();}public interface Animal {void eat();}public static void eatFood(Animal animal) {animal.eat();}public static void main(String[] args) {Pratice17 pratice17 = new Pratice17();eatFood(pratice17.getDog());eatFood(pratice17.getCat());    }
}

练习23:

创建一个接口U,包含三个方法。

创建一个类A,包含一个方法,在此方法中通过创建一个匿名内部类来生成一个指向U的引用。

创建一个类B,包含一个由U组成的数组。B有几个方法,第一个方法接受U的引用,并存储的数组中。第二个方法将数组的引用设置为null,第三个方法遍历此数组,并在U中调用这些方法。

在main()中,创建一组A的对象和一个B的对象。用A类对象产生的U类型的引用填充B对象的数组。使用B回调所有A的对象。再从B中移除某些U的引用。

public interface U {void f();void g();void h();
}public class A {String name;public A(String name) {this.name = name;}public U getU() {return new U() {@Overridepublic void f() {System.out.println(name + "f()");}@Overridepublic void g() {System.out.println(name + "g()");}@Overridepublic void h() {System.out.println(name + "h()");}};}
}public class B {U[] array;public B(int size) {array = new U[size];}public boolean add(U u) {for(int i = 0; i < array.length; i++) {if (array[i] == null) {array[i] = u;return true;}}return false;}public boolean setNull(int i) {if (i < 0 || i >= array.length) {return false;}array[i] = null;return true;}public void callMethods() {for (int i = 0; i < array.length; i++) {if (array[i] != null) {array[i].f();array[i].g();array[i].h();}}}
}public class Test {public static void main(String[] args) {A[] aa = {new A("哈登"), new A("保罗"), new A("卡佩拉")};B b = new B(3);for (int i = 0; i < aa.length; i++) {b.add(aa[i].getU());}System.out.println("所有的--------------------");b.callMethods();System.out.println("移除后--------------------\n");b.setNull(1);b.setNull(2);b.callMethods();}}// 输出所有的--------------------
哈登f()
哈登g()
哈登h()
保罗f()
保罗g()
保罗h()
卡佩拉f()
卡佩拉g()
卡佩拉h()移除后--------------------
哈登f()
哈登g()
哈登h()

10.8.1 闭包与回调

闭包是一个可调用的对象,它记录了一些信息,这些信息来自于创建它的作用域。

那么基于这个定义,内部类是面向对象的闭包。

在我们做Android开发的时候,各种点击事件,各种网络回调,很常见的,在匿名内部类当中我们可以随意的使用当前类的成员变量。我想这样更容易理解。

书中的例子就不说了,讲的很好,但是没有一定的实践和经验也不好理解。

Java更加小心仔细,没有在语言中使用指针,而使用了回调。

内部类提供的闭包功能是优良的解决方案,它比指针更灵活、更安全。

10.8.2 内部类与控制框架

这节内容挺抽象的其实,哈哈,控制框架,用来解决响应事件的需求

书中的例子,Event构造方法中传入延迟的时间,start()方法获得结束时间,ready()方法返回是否到达时间,抽象出一个action()去做动作,Controller()来控制

反正注意内部类的两点吧:

  1. 内部类将实现的细节封装了起来
  2. 内部类能够很容易地访问外围类的任意成员,这一点很重要

控制温室的运作,这个例子里面充满了内部类,其实也不用细看,做andorid的,点击事件匿名内部类是经常的,要么写一个内部类

练习24:略

练习25:略

10.9 内部类的继承

这个在平时的开发也用的少

结合练习简单说一下吧

练习26:创建一个包含内部类的类,此内部类有一个非默认构造器(需要的构造器 ),创建另一个也包含内部类的类,此内部类继承自第一个内部类。

public class WithNonDefault {class Inner{int i;public Inner(int i) {this.i = i;}public Inner() {i = 47;}public void say() {System.out.println("父内部类的方法");}}
}public class Test {class InnerSon extends WithNonDefault.Inner {// 这个直接报错了,也就是说,要想继承一个内部类,必须在构造方法传入此内部类的外围类的引用
//      public InnerSon(int i) {
//          super(i);
//      }public InnerSon(WithNonDefault wnd, int i) {wnd.super(i);}public void say() {System.out.println("子类的方法");super.say();}}public static void main(String[] args) {Test test = new Test();InnerSon innerSon = test.new InnerSon(new WithNonDefault(),2);innerSon.say();}}

10.10 内部类可以被覆盖吗

假设这样一个场景,创建一个内部类,继承外围类并定义此内部类时,会发生什么呢

// 鸡蛋和蛋黄
public class Egg {private Yolk yolk;public Egg() {System.out.println("创建了鸡蛋");yolk = new Yolk();}protected class Yolk {public Yolk() {System.out.println("创建了蛋黄");}}
}// 大鸡蛋继承鸡蛋
public class BigEgg extends Egg{public class Yolk {public Yolk() {System.out.println("创建了大蛋黄");}}public static void main(String[] args) {new BigEgg();}
}输出:
创建了鸡蛋
创建了蛋黄告诉我们一个道理:
类似这种像覆盖方法一样去覆盖内部类,是无法覆盖的
这两个内部类是完全独立的两个实体,各自在自己的命名空间内

明确的继承某个内部类是可以的:

package com__;public class Egg {private Yolk yolk = new Yolk();public Egg() {System.out.println("创建了鸡蛋");}public void insertYolk(Yolk y) {yolk = y;}public void changeYolk() {yolk.change();}protected class Yolk {public Yolk() {System.out.println("创建了蛋黄");}public void change() {System.err.println("蛋黄变了");}}
}public class BigEgg extends Egg{public BigEgg() {insertYolk(new Yolk());}public class Yolk extends Egg.Yolk {public Yolk() {System.out.println("创建了大蛋黄");}@Overridepublic void change() {System.out.println("大蛋黄变了");}}public static void main(String[] args) {Egg egg = new BigEgg();egg.changeYolk();}
}输出:
创建了蛋黄
创建了鸡蛋
创建了蛋黄
创建了大蛋黄
大蛋黄变了

是这样执行的:

  • new一个子类,肯定先执行父类的成员变量,先初始化Yolk,那么执行了Yolk的构造方法——创建了蛋黄
  • 然后是父类的构造方法——创建了鸡蛋
  • 然后是子类的构造方法,调用插入蛋黄的方法,又new了一个蛋黄,此蛋黄是子类的蛋黄,继承了父类中的蛋黄,所以new一个子类蛋黄,先执行父类构造方法——创建了蛋黄
  • 然后再执行子类构造方法——创建了大蛋黄
  • 最后调用变化的方法——执行变化方法的对象时子类蛋黄,所以——大蛋黄变了

正好借此机会在复习一下执行顺序这一块:

先不说继承,就是一个类正常的初始化:

  1. 肯定是先静态(静态成员和静态代码块谁在前谁先执行),将变量和代码块都看作是成员,同级的
  2. 然后非静态(非静态成员和非静态代码块也是谁在前谁先执行),将变量和代码块都看作是成员,同级的
  3. 最后是构造方法

继承的顺序是这样的:

继承的时候,记住一点,静态优先,所有就有了:

  1. 父类的静态成员,父类的静态代码块
  2. 子类的静态成员,子类的静态代码块
  3. 父类的成员,父类的非静态代码块,父类的构造方法
  4. 子类的成员,子类的非静态代码块,子类的构造方法
  5. 子类的有参构造方法没有super父类的构造方法,那么子类执行有参构造方法会默认调用父类的无参构造方法

10.11 局部内部类

局部内部类其实,哈哈,我没用过,用的比较少,一般都是匿名内部类

使用的方法,典型的方法就是在方法体内创建,局部内部类不能有访问修饰符,可以访问当前方法的常量,和外围类的所有成员

// 举个例子
public class Test1 {// 局部内部类,使用局部内部了的唯一理由是,我们需要一个已命名的构造器,或者需要重载构造器public Count getCount() {class LocalCount implements Count {public LocalCount() {// 重载构造器}public int next() {return 1;}}return new LocalCount();}// 匿名内部类,只能实例初始化public Count getCount1() {return new Count() {{// 不过匿名内部类可以有代码块,这个操作以前没考虑过,哈哈}@Overridepublic int next() {return 0;}};}public interface Count {int next();}
}

10.12 内部类标识符

每个类都会生产.class文件,匿名内部类会在$后跟个数字,如果是内部类嵌套在别的内部类当中,名字加在外围类名字与“$”的后面

10.13 总结

接口和内部类其实挺抽象挺复杂的,这两者结合起来能解决C++中使用多重继承所能解决的问题。

什么时候使用接口,什么时候使用内部类,或者两者同时使用,我们自己熟悉了以后,去识别这些情况

Java编程思想读书笔记——第十章:内部类相关推荐

  1. JAVA编程思想读书笔记(三)--RTTI

    接上篇JAVA编程思想读书笔记(二) 第十一章 运行期类型判定 No1: 对于作为程序一部分的每个类,它们都有一个Class对象.换言之,每次写一个新类时,同时也会创建一个Class对象(更恰当的说, ...

  2. Java编程思想读书笔记(七)

    点击蓝字 关注我们 今天是端午节,在这里祝大家端午节安康.过节的同时也不要忘记知识储备,今天我 为大家带来了Java编程思想第七节多形性读书笔记.请大家一起品鉴,如果发现里面有啥写的不对的地方,请大家 ...

  3. Java编程思想读书笔记一:并发

    1. Thread.yield( )方法 当调用yield()时,即在建议具有相同优先级的其他线程可以运行了,但是注意的是,仅仅是建议,没有任何机制保证你这个建议会被采纳 .一般情况下,对于任何重要的 ...

  4. Java 编程思想 读书笔记

    第一章 对象导论 一.抽象过程 所有编程语言都提供抽象机制,人们所能解决的问题的复杂性直接取决于抽象的类型和质量. 1.1 只针对问题来进行建模 早期:基于约束条件编程的语言和通过图形符号操作来实现的 ...

  5. java编程思想读书笔记

    多态 任何域的访问操作都将有编译器解析,如果某个方法是静态的,它的行为就不具有多态性 java默认对象的销毁顺序与初始化顺序相反 编写构造器时有一条有效的准则:"尽可能用简单的方法使对象进入 ...

  6. Java编程思想读书笔记(02)

    第十三章字符串 字符串是不可变的:是final类固不能继承它:也不能通过指向它的引用来修改它的内容. StringBuilder是Java SE5引用的,在这之前用的是StringBuffer.后者是 ...

  7. JAVA编程思想——读书笔记 对象的容纳

    文章目录 对象的容纳 1.数组(array) 数组和第一类对象 基本数据类型集合 数组的返回 2.集合(collection) 集合的缺点:类型未知 它不适用于一下场合: 1.错误有时显露不出来 生成 ...

  8. JAVA编程思想——读书笔记 多态

    文章目录 多态性(Polymorphism) 1.向上转型 ·为什么要向上转型 2.深入理解(绑定) 方法调用的绑定(Binding) 如何产生正确的行为 3.过载与覆盖 4.抽象类和方法 5.接口( ...

  9. java编程思想读书笔记汇总

    第七章:复用类(上) http://blog.csdn.net/u014115673/article/details/53350009 第七章:复用类(下)http://blog.csdn.net/u ...

最新文章

  1. 【廖雪峰python进阶笔记】模块
  2. 近期活动盘点:工业大数据讲座、大数据自杀风险感知讲座、数据法学研讨会、海外学者短期讲学(12.3-12.13)
  3. Java 技术篇-mac操作系统JRE、JDK环境的配置演示
  4. hust 1605 bfs
  5. Vue.js-Day01-PM【事件绑定(事件传参、事件对象、传参+获取事件对象)、样式处理操作(模板、事件、属性绑定)、Tab切换(原生js实现、Vue.js实现)、js中的this详解关键字】
  6. 【Visual C++】游戏开发笔记四十 浅墨DirectX教程之八 绘制真实质感的三维世界:光照与材质专场...
  7. Qt工作笔记-undefined reference to `vtable for MyObject'及对moc文件的进一步理解
  8. Java字符串基本认识
  9. PowerPoint 2010新功能应用
  10. 本地VM安装虚拟机,使用xshell连接
  11. idea中Gsonformat插件工具使用
  12. pink老师学习之Echarts
  13. elasticsearch查询报错411状态码 The requested URL could not be retrieved
  14. .net framework4.0 安装回滚问题
  15. 原来,《心灵奇旅》竟是用VR制作的
  16. 实验1 算法设计与分析
  17. 赞爆!全国计算机专业数据库系统工程师考试指定教程(第三版)
  18. mybatisplus解除分页限制
  19. 基于深度学习的服装图像分类与检索
  20. android afw 权限,如何从adb shell命令启动AFW徽章应用程序?

热门文章

  1. 6S管理的导入怎么做才能行之有效?
  2. 很多人读书,追求的是干货,寻求的是立刻行之有效的解决方案。        其实这是一种留在舒适区的阅读方法。         在这个充满不确定的年代,答案不会简单的出现在书里
  3. Dendro NH2-PEG-Dendro NH2,Dendro Amine-PEG-Dendro Amine,PEG超支化树状胺
  4. informatica笔记
  5. CentOS 7配置httpd服务器
  6. VGGNet结构分析
  7. 网络创业者之家:在家就能做的兼职项目推荐
  8. 四世同堂!Anders Liu给您拜年了!(此文纯水,不喜勿入)
  9. Android 华为平行视界适配(左右分屏)
  10. 如何使用VNC进行远程桌面控制