当前位置: 首页 > news >正文

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 在方法和作用域内的内部类

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

这么做有两个理由:

  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++中使用多重继承所能解决的问题。

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

 

 


http://www.taodudu.cc/news/show-4565065.html

相关文章:

  • Python基础教程(第2版)读书笔记
  • 基于安卓的公司员工考勤系统的设计与实现
  • 职工考勤管理系统
  • 员工考勤管理系统html,一种员工考勤管理系统的制作方法
  • 内存的类型
  • Linux命令_Note1
  • 计算机体系结构——内存
  • 《Unity Shader入门精要》笔记02 第1章+第2章
  • 30天自制OS学习笔记 (四)C语言与画面显示的练习
  • 自制操作系统日志——第四天
  • Linux Graphics 周刊(第 5 期)
  • unity3D 移动平台性能优化
  • 计算机外围设备
  • 轻量级模型设计/部署
  • 30天自制操作系统学习-第8天
  • 【操作系统】30天自制操作系统--(9)叠加处理
  • C语言VRAM字符串平滑移动,航空数字化仪表中动画显示技术的应用
  • 第4天:C语言与画面显示的练习
  • 显卡 内存分配 linux,【原创】Linux环境下的图形系统和AMD R600显卡编程(4)——AMD显卡显存管理机制...
  • 用C语言开发NES游戏(CC65)03、VRAM缓冲区
  • dm8148 开发只boot启动参数vram=128简介
  • 虚拟机服务器CPU授权,vSphere5全新的许可授权方式——CPU许可+vRAM授权
  • 8位色320*200分辨率下的屏幕坐标与VRAM地址计算
  • VRAM
  • CCE to REG bundle
  • 华为云云容器引擎CCE踩坑记
  • 华为云CCE集群节点磁盘告警处理
  • LTE下行物理层传输机制(4)-CCE
  • NR中的盲检--pdcch candidate的起始CCE位置计算
  • 项目实践:基于华为CCE环境下Tomcat的关键性能指标及监控方法