Java编程思想读书笔记——第十章:内部类
第十章 内部类
- 将一个类的定义放在另一个类的内部,这就是内部类
- 内部类和组合是完全不同的概念,内部类了解外部类
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 在方法和作用域内的内部类
以前没接触过,也没这么写过。但是,在方法和作用域中使用内部类,是一种更加难以理解的技术
这么做有两个理由:
- 创建某类型的接口,创建并返回对其的引用
- 要解决一个复杂的问题,想创建一个类来辅助你的解决方案,但又不希望这个类是公共可用的
直接上练习吧:
练习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。
嵌套类意味着:
- 要创建嵌套类的对象,并不需要其外围类的对象。
- 不能从嵌套类的对象中访问非静态的外围类对象。
- 嵌套类的内部可以包含static数据,包含嵌套类
练习18:略
练习19:略
10.7.1 接口内部的类
嵌套类可以作为接口的一部分,甚至可以在内部类中实现其外围接口。
比如也可以在嵌套类中写一个main函数
练习20:略
练习21:略
10.7.2 从多层嵌套类中访问外部类的成员
一个内部类被嵌套多少层并不重要——它能透明地访问所有它所嵌入的外围类的所有成员。
10.8 为什么需要内部类
内部类最吸引人的原因是:
每个内部类都能独立地继承自一个(接口的)实现,所以无论外围类是否已经继承了某个(接口的)实现,对于内部类都没有影响。
内部类使得多重继承的解决方案变得完整。内部类有效地实现了“多重继承”。
使用内部类还可以获得其他一些特性:
- 内部类可以有多个实例,每个实例都有自己的状态信息,并且与其外围类对象的信息相互独立。
- 在单个外围类中,可以让多个内部类以不同的方式实现同一个接口,或继承同一个类。
- 创建内部类对象的时刻并不依赖于外围类对象的创建
- 内部类并没有令人迷惑的 “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()来控制
反正注意内部类的两点吧:
- 内部类将实现的细节封装了起来
- 内部类能够很容易地访问外围类的任意成员,这一点很重要
控制温室的运作,这个例子里面充满了内部类,其实也不用细看,做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一个子类蛋黄,先执行父类构造方法——创建了蛋黄
- 然后再执行子类构造方法——创建了大蛋黄
- 最后调用变化的方法——执行变化方法的对象时子类蛋黄,所以——大蛋黄变了
正好借此机会在复习一下执行顺序这一块:
先不说继承,就是一个类正常的初始化:
- 肯定是先静态(静态成员和静态代码块谁在前谁先执行),将变量和代码块都看作是成员,同级的
- 然后非静态(非静态成员和非静态代码块也是谁在前谁先执行),将变量和代码块都看作是成员,同级的
- 最后是构造方法
继承的顺序是这样的:
继承的时候,记住一点,静态优先,所有就有了:
- 父类的静态成员,父类的静态代码块
- 子类的静态成员,子类的静态代码块
- 父类的成员,父类的非静态代码块,父类的构造方法
- 子类的成员,子类的非静态代码块,子类的构造方法
- 子类的有参构造方法没有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++中使用多重继承所能解决的问题。
什么时候使用接口,什么时候使用内部类,或者两者同时使用,我们自己熟悉了以后,去识别这些情况